diff --git a/.github/actions/insert-youtrack-link/package-lock.json b/.github/actions/insert-youtrack-link/package-lock.json index 6967b932d5..dc00e53f9e 100644 --- a/.github/actions/insert-youtrack-link/package-lock.json +++ b/.github/actions/insert-youtrack-link/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", - "@octokit/rest": "^20.1.0" + "@octokit/rest": "^20.1.1" } }, "node_modules/@actions/core": { @@ -188,19 +188,33 @@ } }, "node_modules/@octokit/rest": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.0.tgz", - "integrity": "sha512-STVO3itHQLrp80lvcYB2UIKoeil5Ctsgd2s1AM+du3HqZIR35ZH7WE9HLwUOLXH0myA0y3AGNPo8gZtcgIbw0g==", + "version": "20.1.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.1.tgz", + "integrity": "sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==", "dependencies": { "@octokit/core": "^5.0.2", - "@octokit/plugin-paginate-rest": "^9.1.5", + "@octokit/plugin-paginate-rest": "11.3.1", "@octokit/plugin-request-log": "^4.0.0", - "@octokit/plugin-rest-endpoint-methods": "^10.2.0" + "@octokit/plugin-rest-endpoint-methods": "13.2.2" }, "engines": { "node": ">= 18" } }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz", + "integrity": "sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, "node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz", @@ -212,6 +226,20 @@ "@octokit/core": ">=5" } }, + "node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz", + "integrity": "sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" + } + }, "node_modules/@octokit/types": { "version": "13.5.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", @@ -422,21 +450,37 @@ } }, "@octokit/rest": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.0.tgz", - "integrity": "sha512-STVO3itHQLrp80lvcYB2UIKoeil5Ctsgd2s1AM+du3HqZIR35ZH7WE9HLwUOLXH0myA0y3AGNPo8gZtcgIbw0g==", + "version": "20.1.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.1.tgz", + "integrity": "sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==", "requires": { "@octokit/core": "^5.0.2", - "@octokit/plugin-paginate-rest": "^9.1.5", + "@octokit/plugin-paginate-rest": "11.3.1", "@octokit/plugin-request-log": "^4.0.0", - "@octokit/plugin-rest-endpoint-methods": "^10.2.0" + "@octokit/plugin-rest-endpoint-methods": "13.2.2" }, "dependencies": { + "@octokit/plugin-paginate-rest": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz", + "integrity": "sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==", + "requires": { + "@octokit/types": "^13.5.0" + } + }, "@octokit/plugin-request-log": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz", "integrity": "sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA==", "requires": {} + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz", + "integrity": "sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==", + "requires": { + "@octokit/types": "^13.5.0" + } } } }, diff --git a/.github/actions/insert-youtrack-link/package.json b/.github/actions/insert-youtrack-link/package.json index 4550b7a582..372cb0e090 100644 --- a/.github/actions/insert-youtrack-link/package.json +++ b/.github/actions/insert-youtrack-link/package.json @@ -12,6 +12,6 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", - "@octokit/rest": "^20.1.0" + "@octokit/rest": "^20.1.1" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index e0fc575a47..80d03d4e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ ### Fixed +## [0.9.7-alpha.1] - 2024-06-06 + +### Added + +* Support TeX Live docker image +* Formatter support for plain TeX \if-statements +* Index files from the TEXINPUTS variable, for autocompletion + ## [0.9.6] - 2024-06-01 Welcome to TeXiFy IDEA 0.9.6! This release fixes an issue with the table insertion wizard, fixes pasting from a pdf file, and more. @@ -358,7 +366,8 @@ Thanks to @jojo2357 and @MisterDeenis for contributing to this release! * Fix some intention previews. ([#2796](https://github.com/Hannah-Sten/TeXiFy-IDEA/issues/2796)) * Other small bug fixes and improvements. ([#2776](https://github.com/Hannah-Sten/TeXiFy-IDEA/issues/2776), [#2774](https://github.com/Hannah-Sten/TeXiFy-IDEA/issues/2774), [#2765](https://github.com/Hannah-Sten/TeXiFy-IDEA/issues/2765)-[#2773](https://github.com/Hannah-Sten/TeXiFy-IDEA/issues/2773)) -[Unreleased]: https://github.com/Hannah-Sten/TeXiFy-IDEA/compare/v0.9.6...HEAD +[Unreleased]: https://github.com/Hannah-Sten/TeXiFy-IDEA/compare/v0.9.7-alpha.1...HEAD +[0.9.7-alpha.1]: https://github.com/Hannah-Sten/TeXiFy-IDEA/compare/v0.9.6...v0.9.7-alpha.1 [0.9.6]: https://github.com/Hannah-Sten/TeXiFy-IDEA/compare/v0.9.5...v0.9.6 [0.9.5]: https://github.com/Hannah-Sten/TeXiFy-IDEA/compare/v0.9.4...v0.9.5 [0.9.4]: https://github.com/Hannah-Sten/TeXiFy-IDEA/compare/v0.9.3...v0.9.4 diff --git a/Writerside/topics/Run-configuration-settings.md b/Writerside/topics/Run-configuration-settings.md index 7e471d349e..5fb8972962 100644 --- a/Writerside/topics/Run-configuration-settings.md +++ b/Writerside/topics/Run-configuration-settings.md @@ -249,6 +249,12 @@ Custom output directories are supported. * You have to login to GitHub to use the Docker image: get a github token from [https://github.com/settings/tokens,](https://github.com/settings/tokens,) save it somewhere secure and run `echo my_token | docker login https://docker.pkg.github.com -u myusername --password-stdin` See [https://help.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-to-github-packages](https://help.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-to-github-packages) for more info. +### Dockerized TeX Live + +Similar to the MiKTeX docker image, you can also use a texlive docker image if you have it installed. +By default, the official `texlive/texlive` image is used. +If you use IntelliJ, you can select a different image name by creating a LaTeX Docker SDK, see [Project configuration](Project-configuration.md#sdks). + ### TeX Live from WSL _Since b0.6.10_ diff --git a/build.gradle.kts b/build.gradle.kts index 88a3ba9f80..4384b2c457 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,7 +27,7 @@ plugins { id("org.jetbrains.kotlinx.kover") version "0.8.0" // Linting - id("org.jlleitschuh.gradle.ktlint") version "12.1.0" + id("org.jlleitschuh.gradle.ktlint") version "12.1.1" // Vulnerability scanning id("org.owasp.dependencycheck") version "9.2.0" @@ -118,7 +118,7 @@ dependencies { implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.11") // Comparing versions - implementation("org.apache.maven:maven-artifact:4.0.0-alpha-13") + implementation("org.apache.maven:maven-artifact:4.0.0-beta-3") // LaTeX rendering for preview implementation("org.scilab.forge:jlatexmath:1.0.7") diff --git a/gradle.properties b/gradle.properties index 5c711d0d8a..e1be4a4803 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -pluginVersion = 0.9.6 +pluginVersion = 0.9.7-alpha.1 # Info about build ranges: https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html # Note that an xyz branch corresponds to version 20xy.z and a since build of xyz.* diff --git a/resources/META-INF/actions/actions.xml b/resources/META-INF/actions/actions.xml index 8570b1e045..db3500a9df 100644 --- a/resources/META-INF/actions/actions.xml +++ b/resources/META-INF/actions/actions.xml @@ -18,7 +18,7 @@ - + diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 3b54072893..bf4dcfc6eb 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,2 +1,2 @@ beautifulsoup4==4.12.3 -requests==2.32.0 +requests==2.32.3 diff --git a/src/nl/hannahsten/texifyidea/action/analysis/WordCountAction.kt b/src/nl/hannahsten/texifyidea/action/analysis/WordCountAction.kt index 260b583688..a278f78148 100644 --- a/src/nl/hannahsten/texifyidea/action/analysis/WordCountAction.kt +++ b/src/nl/hannahsten/texifyidea/action/analysis/WordCountAction.kt @@ -4,6 +4,7 @@ import com.intellij.icons.AllIcons import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.ui.DialogBuilder import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile @@ -71,6 +72,8 @@ open class WordCountAction : AnAction() { // Prefer texcount, I think it is slightly more accurate val dialog = if (SystemEnvironment.isAvailable("texcount")) { val root = psiFile.findRootFile().virtualFile + // Make sure the file is written to disk before running an external tool on it + FileDocumentManager.getInstance().apply { saveDocument(getDocument(root) ?: return@apply) } val (output, exitCode) = runCommandWithExitCode("texcount", "-1", "-inc", "-sum", root.name, workingDirectory = File(root.parent.path)) if (exitCode == 0 && output?.toIntOrNull() != null) { makeDialog(psiFile, output.toInt()) diff --git a/src/nl/hannahsten/texifyidea/formatting/LatexBlock.kt b/src/nl/hannahsten/texifyidea/formatting/LatexBlock.kt index a5672c65b5..a74cd27bc2 100644 --- a/src/nl/hannahsten/texifyidea/formatting/LatexBlock.kt +++ b/src/nl/hannahsten/texifyidea/formatting/LatexBlock.kt @@ -11,10 +11,10 @@ import nl.hannahsten.texifyidea.editor.typedhandlers.LatexEnterHandler import nl.hannahsten.texifyidea.lang.commands.LatexCommand import nl.hannahsten.texifyidea.psi.* import nl.hannahsten.texifyidea.settings.codestyle.LatexCodeStyleSettings -import nl.hannahsten.texifyidea.util.parser.firstChildOfType -import nl.hannahsten.texifyidea.util.parser.firstParentOfType import nl.hannahsten.texifyidea.util.magic.CommandMagic import nl.hannahsten.texifyidea.util.magic.cmd +import nl.hannahsten.texifyidea.util.parser.firstChildOfType +import nl.hannahsten.texifyidea.util.parser.firstParentOfType import java.lang.Integer.max /** @@ -158,7 +158,7 @@ class LatexBlock( else -> shouldIndentEnvironments } - if (shouldIndentEnvironment || myNode.elementType === LatexTypes.PSEUDOCODE_BLOCK_CONTENT || + if (shouldIndentEnvironment || myNode.elementType === LatexTypes.PSEUDOCODE_BLOCK_CONTENT || myNode.elementType === LatexTypes.IF_BLOCK_CONTENT || // Fix for leading comments inside an environment, because somehow they are not placed inside environments. // Note that this does not help to insert the indentation, but at least the indent is not removed // when formatting. diff --git a/src/nl/hannahsten/texifyidea/formatting/LatexSpacingRules.kt b/src/nl/hannahsten/texifyidea/formatting/LatexSpacingRules.kt index ba44f8557c..466b39095c 100644 --- a/src/nl/hannahsten/texifyidea/formatting/LatexSpacingRules.kt +++ b/src/nl/hannahsten/texifyidea/formatting/LatexSpacingRules.kt @@ -2,16 +2,16 @@ package nl.hannahsten.texifyidea.formatting import com.intellij.formatting.Spacing import com.intellij.psi.codeStyle.CodeStyleSettings -import nl.hannahsten.texifyidea.grammar.LatexLanguage import nl.hannahsten.texifyidea.formatting.spacingrules.leftTableSpaceAlign import nl.hannahsten.texifyidea.formatting.spacingrules.rightTableSpaceAlign +import nl.hannahsten.texifyidea.grammar.LatexLanguage import nl.hannahsten.texifyidea.psi.LatexCommands import nl.hannahsten.texifyidea.psi.LatexTypes.* import nl.hannahsten.texifyidea.settings.codestyle.LatexCodeStyleSettings -import nl.hannahsten.texifyidea.util.parser.firstChildOfType -import nl.hannahsten.texifyidea.util.parser.inDirectEnvironment import nl.hannahsten.texifyidea.util.magic.CommandMagic import nl.hannahsten.texifyidea.util.magic.EnvironmentMagic +import nl.hannahsten.texifyidea.util.parser.firstChildOfType +import nl.hannahsten.texifyidea.util.parser.inDirectEnvironment import nl.hannahsten.texifyidea.util.parser.parentOfType import java.util.* @@ -49,6 +49,7 @@ fun createSpacingBuilder(settings: CodeStyleSettings): TexSpacingBuilder { between(NORMAL_TEXT_WORD, NORMAL_TEXT_WORD).spaces(1) before(ENVIRONMENT_CONTENT).lineBreakInCode() before(PSEUDOCODE_BLOCK_CONTENT).lineBreakInCode() + before(IF_BLOCK_CONTENT).lineBreakInCode() } // Newline before certain algorithm pseudocode commands @@ -75,6 +76,9 @@ fun createSpacingBuilder(settings: CodeStyleSettings): TexSpacingBuilder { inPosition(parent = PSEUDOCODE_BLOCK, left = PSEUDOCODE_BLOCK_CONTENT, right = END_PSEUDOCODE_BLOCK).spacing( Spacing.createSpacing(0, Int.MAX_VALUE, 1, latexCommonSettings.KEEP_LINE_BREAKS, latexCommonSettings.KEEP_BLANK_LINES_IN_CODE) ) + inPosition(parent = IF_BLOCK, left = IF_BLOCK_CONTENT, right = END_IF).spacing( + Spacing.createSpacing(0, Int.MAX_VALUE, 1, latexCommonSettings.KEEP_LINE_BREAKS, latexCommonSettings.KEEP_BLANK_LINES_IN_CODE) + ) } custom { diff --git a/src/nl/hannahsten/texifyidea/grammar/Latex.bnf b/src/nl/hannahsten/texifyidea/grammar/Latex.bnf index b39f7abd9c..a822c1b953 100644 --- a/src/nl/hannahsten/texifyidea/grammar/Latex.bnf +++ b/src/nl/hannahsten/texifyidea/grammar/Latex.bnf @@ -56,7 +56,8 @@ latexFile ::= content // Make sure that there is a root element with multiple children, for example for Grazie to allow ignoring certain types of no_math_content content ::= no_math_content* -no_math_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | normal_text +// When updating this list, consider updating other _content lists +no_math_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | normal_text | END_IF normal_text ::= (NORMAL_TEXT_WORD | STAR | AMPERSAND | QUOTATION_MARK | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | OPEN_PAREN | CLOSE_PAREN | OPEN_BRACKET | CLOSE_BRACKET | PIPE | EXCLAMATION_MARK | BACKSLASH | EQUALS | COMMA | ANGLE_PARAM)+ @@ -75,6 +76,12 @@ pseudocode_block ::= BEGIN_PSEUDOCODE_BLOCK parameter* pseudocode_block_content? pseudocode_block_content ::= no_math_content* +// Plain TeX \if...\fi, note that user defined ifs are not included so there may be unmatched \fi +if_block ::= START_IF if_block_content? (ELSE if_block_content?)* END_IF { pin=1 } + +// no_math_content without end_if +if_block_content ::= (raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | normal_text)* + commands ::= COMMAND_TOKEN STAR? parameter* { pin=1 elementTypeClass="nl.hannahsten.texifyidea.index.stub.LatexCommandsStubElementType" @@ -118,10 +125,10 @@ picture_param ::= OPEN_PAREN picture_param_content* CLOSE_PAREN { pin=3 } // These are like content, but no brackets and with parameter_text instead of normal_text // We have to separate optional and required parameter content, because required parameter content // can contain mismatched brackets, but optional parameters not (then we wouldn't know what to match) -optional_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | OPEN_PAREN | CLOSE_PAREN | parameter_text | BACKSLASH | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET -required_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | math_environment | COMMAND_IFNEXTCHAR | group | OPEN_PAREN | CLOSE_PAREN | parameter_text | COMMA | EQUALS | OPEN_BRACKET | CLOSE_BRACKET | BACKSLASH | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET +optional_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | OPEN_PAREN | CLOSE_PAREN | parameter_text | BACKSLASH | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | END_IF +required_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | group | OPEN_PAREN | CLOSE_PAREN | parameter_text | COMMA | EQUALS | OPEN_BRACKET | CLOSE_BRACKET | BACKSLASH | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | END_IF // Cannot contain ( or ) -picture_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | parameter_text | BACKSLASH | COMMA | EQUALS | OPEN_BRACKET | CLOSE_BRACKET | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET +picture_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | parameter_text | BACKSLASH | COMMA | EQUALS | OPEN_BRACKET | CLOSE_BRACKET | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | END_IF strict_key_val_pair ::= key_val_key EQUALS key_val_value? diff --git a/src/nl/hannahsten/texifyidea/grammar/LatexLexer.flex b/src/nl/hannahsten/texifyidea/grammar/LatexLexer.flex index 4535d7bfae..08ead29dfb 100644 --- a/src/nl/hannahsten/texifyidea/grammar/LatexLexer.flex +++ b/src/nl/hannahsten/texifyidea/grammar/LatexLexer.flex @@ -80,7 +80,7 @@ END_TOKEN="\\end" COMMAND_IFNEXTCHAR=\\@ifnextchar. COMMAND_TOKEN=\\([a-zA-Z@]+|.|\r) COMMAND_TOKEN_LATEX3=\\([a-zA-Z@_:0-9]+|.|\r) // _ and : are only LaTeX3 syntax -LATEX3_ON=\\(ExplSyntaxOn|ProvidesExplPackage) +LATEX3_ON=\\(ExplSyntaxOn|ProvidesExplPackage|ProvidesExplClass|ProvidesExplFile) LATEX3_OFF=\\ExplSyntaxOff NEWENVIRONMENT=\\(re)?newenvironment // BeforeBegin/AfterEnd are from etoolbox, and just happen to also have two parameters where the second can contain loose \begin or \end @@ -125,6 +125,11 @@ BEGIN_PSEUDOCODE_BLOCK="\\For" | "\\ForAll" | "\\If" | "\\While" | "\\Repeat" | MIDDLE_PSEUDOCODE_BLOCK="\\ElsIf" | "\\Else" END_PSEUDOCODE_BLOCK="\\EndFor" | "\\EndIf" | "\\EndWhile" | "\\Until" | "\\EndLoop" | "\\EndFunction" | "\\EndProcedure" +// See TeX by Topic chapter 13 +START_IFS=\\if | \\ifcat | \\ifx | \\ifcase | \\ifnum | \\ifodd | \\ifhmode | \\ifvmode | \\ifmmode | \\ifinner | \\ifdim | \\ifvoid | \\ifhbox | \\ifvbox | \\ifeof | \\iftrue | \\iffalse +ELSE=\\else +END_IFS=\\fi + %states INLINE_MATH INLINE_MATH_LATEX DISPLAY_MATH TEXT_INSIDE_INLINE_MATH NESTED_INLINE_MATH PARTIAL_DEFINITION %states NEW_ENVIRONMENT_DEFINITION_NAME NEW_ENVIRONMENT_DEFINITION NEW_ENVIRONMENT_SKIP_BRACE NEW_ENVIRONMENT_DEFINITION_END NEW_DOCUMENT_ENV_DEFINITION_NAME NEW_DOCUMENT_ENV_DEFINITION_ARGS_SPEC NEW_COMMAND_DEFINITION_PARAM1 NEW_COMMAND_DEFINITION_PARAM2 @@ -524,6 +529,9 @@ END_PSEUDOCODE_BLOCK="\\EndFor" | "\\EndIf" | "\\EndWhile" | "\\Until" | "\\EndL {ENDINPUT} { yypushState(OFF); return COMMAND_TOKEN; } {BEGIN_TOKEN} { yypushState(POSSIBLE_VERBATIM_BEGIN); return BEGIN_TOKEN; } {END_TOKEN} { return END_TOKEN; } +{START_IFS} { return START_IF; } +{ELSE} { return ELSE; } +{END_IFS} { return END_IF; } {COMMAND_TOKEN} { return COMMAND_TOKEN; } {COMMAND_IFNEXTCHAR} { return COMMAND_IFNEXTCHAR; } {MAGIC_COMMENT_TOKEN} { return MAGIC_COMMENT_TOKEN; } diff --git a/src/nl/hannahsten/texifyidea/grammar/LatexParserDefinition.kt b/src/nl/hannahsten/texifyidea/grammar/LatexParserDefinition.kt index 6bdc8179e1..21e00d1a3e 100644 --- a/src/nl/hannahsten/texifyidea/grammar/LatexParserDefinition.kt +++ b/src/nl/hannahsten/texifyidea/grammar/LatexParserDefinition.kt @@ -48,7 +48,7 @@ class LatexParserDefinition : ParserDefinition { val FILE: IStubFileElementType<*> = object : IStubFileElementType( "LatexStubFileElementType", Language.findInstance(LatexLanguage::class.java) ) { - override fun getStubVersion(): Int = 71 + override fun getStubVersion(): Int = 72 } } diff --git a/src/nl/hannahsten/texifyidea/highlighting/LatexPairedBraceMatcher.kt b/src/nl/hannahsten/texifyidea/highlighting/LatexPairedBraceMatcher.kt index 7de7367dd7..b209242ec8 100644 --- a/src/nl/hannahsten/texifyidea/highlighting/LatexPairedBraceMatcher.kt +++ b/src/nl/hannahsten/texifyidea/highlighting/LatexPairedBraceMatcher.kt @@ -18,7 +18,8 @@ class LatexPairedBraceMatcher : PairedBraceMatcher { BracePair(LatexTypes.OPEN_PAREN, LatexTypes.CLOSE_PAREN, false), BracePair(LatexTypes.OPEN_BRACE, LatexTypes.CLOSE_BRACE, false), BracePair(LatexTypes.OPEN_BRACKET, LatexTypes.CLOSE_BRACKET, false), - BracePair(LatexTypes.BEGIN_PSEUDOCODE_BLOCK, LatexTypes.END_PSEUDOCODE_BLOCK, false) + BracePair(LatexTypes.BEGIN_PSEUDOCODE_BLOCK, LatexTypes.END_PSEUDOCODE_BLOCK, false), + BracePair(LatexTypes.START_IF, LatexTypes.END_IF, false), ) override fun getPairs() = bracePairs diff --git a/src/nl/hannahsten/texifyidea/highlighting/LatexSyntaxHighlighter.kt b/src/nl/hannahsten/texifyidea/highlighting/LatexSyntaxHighlighter.kt index 3c7fd8e96c..84411fb7c9 100644 --- a/src/nl/hannahsten/texifyidea/highlighting/LatexSyntaxHighlighter.kt +++ b/src/nl/hannahsten/texifyidea/highlighting/LatexSyntaxHighlighter.kt @@ -83,7 +83,10 @@ class LatexSyntaxHighlighter : SyntaxHighlighterBase() { LatexTypes.END_TOKEN, LatexTypes.BEGIN_PSEUDOCODE_BLOCK, LatexTypes.MIDDLE_PSEUDOCODE_BLOCK, - LatexTypes.END_PSEUDOCODE_BLOCK + LatexTypes.END_PSEUDOCODE_BLOCK, + LatexTypes.START_IF, + LatexTypes.ELSE, + LatexTypes.END_IF, ) /* diff --git a/src/nl/hannahsten/texifyidea/index/file/LatexIndexableSetContributor.kt b/src/nl/hannahsten/texifyidea/index/file/LatexIndexableSetContributor.kt index f6d31a06e0..80aa88f49c 100644 --- a/src/nl/hannahsten/texifyidea/index/file/LatexIndexableSetContributor.kt +++ b/src/nl/hannahsten/texifyidea/index/file/LatexIndexableSetContributor.kt @@ -4,11 +4,13 @@ import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task.Backgroundable import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.indexing.IndexableSetContributor import nl.hannahsten.texifyidea.settings.TexifySettings import nl.hannahsten.texifyidea.settings.sdk.LatexSdkUtil import nl.hannahsten.texifyidea.util.Log +import nl.hannahsten.texifyidea.util.getTexinputsPaths import nl.hannahsten.texifyidea.util.isTestProject import org.codehaus.plexus.archiver.ArchiverException import org.codehaus.plexus.archiver.tar.TarBZip2UnArchiver @@ -58,6 +60,8 @@ class LatexIndexableSetContributor : IndexableSetContributor() { // Unfortunately, since .sty is a LaTeX file type, these will all be parsed, which will take an enormous amount of time. // Note that using project-independent getAdditionalRootsToIndex does not fix this roots.addAll(LatexSdkUtil.getSdkSourceRoots(project) { sdkType, homePath -> sdkType.getDefaultStyleFilesPath(homePath) }) + + roots.addAll(getTexinputsPaths(project, rootFiles = listOf(), expandPaths = false).mapNotNull { LocalFileSystem.getInstance().findFileByPath(it) }) Log.debug("Indexing source roots $roots") return roots diff --git a/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexNonMatchingIfInspection.kt b/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexNonMatchingIfInspection.kt index 1ec4312bee..dfe5545b59 100644 --- a/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexNonMatchingIfInspection.kt +++ b/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexNonMatchingIfInspection.kt @@ -3,16 +3,22 @@ package nl.hannahsten.texifyidea.inspections.latex.probablebugs import com.intellij.codeInspection.InspectionManager import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.impl.source.tree.LeafPsiElement import nl.hannahsten.texifyidea.inspections.InsightGroup import nl.hannahsten.texifyidea.inspections.TexifyInspectionBase +import nl.hannahsten.texifyidea.lang.LatexPackage import nl.hannahsten.texifyidea.lang.commands.LatexNewDefinitionCommand import nl.hannahsten.texifyidea.psi.LatexCommands +import nl.hannahsten.texifyidea.psi.LatexTypes import nl.hannahsten.texifyidea.util.files.commandsInFile +import nl.hannahsten.texifyidea.util.includedPackages import nl.hannahsten.texifyidea.util.magic.CommandMagic import nl.hannahsten.texifyidea.util.magic.GeneralMagic import nl.hannahsten.texifyidea.util.magic.PatternMagic import nl.hannahsten.texifyidea.util.matches +import nl.hannahsten.texifyidea.util.parser.childrenOfType import nl.hannahsten.texifyidea.util.parser.previousCommand import java.util.* @@ -30,12 +36,19 @@ open class LatexNonMatchingIfInspection : TexifyInspectionBase() { override fun inspectFile(file: PsiFile, manager: InspectionManager, isOntheFly: Boolean): List { val descriptors = descriptorList() + // etoolbox has many \if... commands without a \fi + if (file.includedPackages().contains(LatexPackage.ETOOLBOX)) { + return emptyList() + } + // Find matches. - val stack = ArrayDeque() - val commands = file.commandsInFile().sortedBy { it.textOffset } - for (command in commands) { - val name = command.name - if (command.name in CommandMagic.endIfs) { + val stack = ArrayDeque() + val commands = file.commandsInFile() + val ifs = file.childrenOfType().filter { it.elementType == LatexTypes.END_IF || it.elementType == LatexTypes.START_IF } + val all = (commands + ifs).sortedBy { it.textOffset } + for (command in all) { + val name = if (command is LatexCommands) command.name else command.text + if (command is LeafPsiElement && command.elementType == LatexTypes.END_IF) { // Non-opened fi. if (stack.isEmpty()) { descriptors.add( diff --git a/src/nl/hannahsten/texifyidea/lang/LatexPackage.kt b/src/nl/hannahsten/texifyidea/lang/LatexPackage.kt index 770e38d1dd..7795821995 100644 --- a/src/nl/hannahsten/texifyidea/lang/LatexPackage.kt +++ b/src/nl/hannahsten/texifyidea/lang/LatexPackage.kt @@ -43,6 +43,7 @@ open class LatexPackage @JvmOverloads constructor( val COLOR = LatexPackage("color") val COMMENT = LatexPackage("comment") val CSQUOTES = LatexPackage("csquotes") + val ETOOLBOX = LatexPackage("etoolbox") val EUROSYM = LatexPackage("eurosym") val FLOAT = LatexPackage("float") val FONTENC = LatexPackage("fontenc") diff --git a/src/nl/hannahsten/texifyidea/reference/InputFileReference.kt b/src/nl/hannahsten/texifyidea/reference/InputFileReference.kt index be2fa6c88c..be828074ec 100644 --- a/src/nl/hannahsten/texifyidea/reference/InputFileReference.kt +++ b/src/nl/hannahsten/texifyidea/reference/InputFileReference.kt @@ -1,7 +1,5 @@ package nl.hannahsten.texifyidea.reference -import com.intellij.execution.RunManager -import com.intellij.execution.impl.RunManagerImpl import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.util.TextRange import com.intellij.openapi.vfs.LocalFileSystem @@ -15,12 +13,10 @@ import nl.hannahsten.texifyidea.completion.pathcompletion.LatexGraphicsPathProvi import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand import nl.hannahsten.texifyidea.psi.LatexCommands import nl.hannahsten.texifyidea.psi.LatexPsiHelper -import nl.hannahsten.texifyidea.run.latex.LatexRunConfiguration import nl.hannahsten.texifyidea.settings.sdk.LatexSdkUtil import nl.hannahsten.texifyidea.util.* import nl.hannahsten.texifyidea.util.files.* import nl.hannahsten.texifyidea.util.magic.CommandMagic -import java.io.File /** * Reference to a file, based on the command and the range of the filename within the command text. @@ -40,10 +36,6 @@ class InputFileReference( companion object { - private val texinputs by lazy { - runCommand("kpsewhich", "--expand-var", "'\$TEXINPUTS'") - } - /** * Handle element rename, but taking into account whether the given * newElementName is just a filename which we have to replace, @@ -123,31 +115,7 @@ class InputFileReference( val rootDirectories = rootFiles.mapNotNull { it.parent } // Check environment variables - val runManager = RunManagerImpl.getInstanceImpl(element.project) as RunManager - val texinputsVariable = runManager.allConfigurationsList - .filterIsInstance() - .firstOrNull { it.mainFile in rootFiles } - ?.environmentVariables - ?.envs - ?.getOrDefault("TEXINPUTS", null) - // Not sure which of these takes precedence, or if they are joined together - ?: LatexmkRcFileFinder.getTexinputsVariable(element.containingFile, null) - ?: texinputs - - for (texInputPath in texinputsVariable?.trim('\'')?.split(File.pathSeparator)?.filter { it.isNotBlank() } ?: emptyList()) { - val path = texInputPath.trimEnd(File.pathSeparatorChar) - searchPaths.add(path.trimEnd('/')) - // See the kpathsea manual, // expands to subdirs - if (path.endsWith("//")) { - LocalFileSystem.getInstance().findFileByPath(path.trimEnd('/'))?.let { parent -> - searchPaths.addAll( - parent.allChildDirectories() - .filter { it.isDirectory } - .map { it.path } - ) - } - } - } + searchPaths += getTexinputsPaths(element.project, rootFiles, expandPaths = true, latexmkSearchDirectory = element.containingFile?.virtualFile?.parent) // BIBINPUTS // Not used for building the fileset, so we can use the fileset to lookup the BIBINPUTS environment variable diff --git a/src/nl/hannahsten/texifyidea/run/compiler/LatexCompiler.kt b/src/nl/hannahsten/texifyidea/run/compiler/LatexCompiler.kt index 268279ea90..f51032d8ab 100644 --- a/src/nl/hannahsten/texifyidea/run/compiler/LatexCompiler.kt +++ b/src/nl/hannahsten/texifyidea/run/compiler/LatexCompiler.kt @@ -292,28 +292,32 @@ enum class LatexCompiler(private val displayName: String, val executableName: St val moduleRoot = fileIndex.getContentRootForFile(mainFile) // For now we disable module roots with Docker // Could be improved by mounting them to the right directory - val moduleRoots = if (runConfig.getLatexDistributionType() != LatexDistributionType.DOCKER_MIKTEX) { - rootManager.contentSourceRoots + val moduleRoots = if (runConfig.getLatexDistributionType().isDocker()) { + emptyArray() } else { - emptyArray() + rootManager.contentSourceRoots } - // If we used /miktex/work/out, an out directory would appear in the src folder on the host system - val dockerOutputDir = "/miktex/out" - val dockerAuxilDir = "/miktex/auxil" - val outputPath = if (runConfig.getLatexDistributionType() != LatexDistributionType.DOCKER_MIKTEX) { - runConfig.outputPath.getAndCreatePath()?.path?.toPath(runConfig) + val outputPath = if (runConfig.getLatexDistributionType() == LatexDistributionType.DOCKER_MIKTEX) { + // If we used /miktex/work/out, an out directory would appear in the src folder on the host system + "/miktex/out" + } + else if (runConfig.getLatexDistributionType() == LatexDistributionType.DOCKER_TEXLIVE) { + "/out" } else { - dockerOutputDir + runConfig.outputPath.getAndCreatePath()?.path?.toPath(runConfig) } - val auxilPath = if (runConfig.getLatexDistributionType() != LatexDistributionType.DOCKER_MIKTEX) { - runConfig.auxilPath.getAndCreatePath()?.path?.toPath(runConfig) + val auxilPath = if (runConfig.getLatexDistributionType() == LatexDistributionType.DOCKER_MIKTEX) { + "/miktex/auxil" + } + else if (runConfig.getLatexDistributionType() == LatexDistributionType.DOCKER_TEXLIVE) { + null } else { - dockerAuxilDir + runConfig.auxilPath.getAndCreatePath()?.path?.toPath(runConfig) } val command = createCommand( @@ -338,8 +342,8 @@ enum class LatexCompiler(private val displayName: String, val executableName: St return mutableListOf("bash", "-ic", wslCommand) } - if (runConfig.getLatexDistributionType() == LatexDistributionType.DOCKER_MIKTEX) { - createDockerCommand(runConfig, dockerAuxilDir, dockerOutputDir, mainFile, command) + if (runConfig.getLatexDistributionType().isDocker()) { + createDockerCommand(runConfig, auxilPath, outputPath, mainFile, command) } // Custom compiler arguments specified by the user @@ -368,9 +372,13 @@ enum class LatexCompiler(private val displayName: String, val executableName: St } @Suppress("SameParameterValue") - private fun createDockerCommand(runConfig: LatexRunConfiguration, dockerAuxilDir: String, dockerOutputDir: String, mainFile: VirtualFile, command: MutableList) { - // See https://hub.docker.com/r/miktex/miktex - "docker volume create --name miktex".runCommand() + private fun createDockerCommand(runConfig: LatexRunConfiguration, dockerAuxilDir: String?, dockerOutputDir: String?, mainFile: VirtualFile, command: MutableList) { + val isMiktex = runConfig.getLatexDistributionType() == LatexDistributionType.MIKTEX + + if (isMiktex) { + // See https://hub.docker.com/r/miktex/miktex + "docker volume create --name miktex".runCommand() + } // Find the sdk corresponding to the type the user has selected in the run config val sdk = ProjectJdkTable.getInstance().allJdks.firstOrNull { it.sdkType is DockerSdk } @@ -379,20 +387,37 @@ enum class LatexCompiler(private val displayName: String, val executableName: St if (sdk == null) "docker" else (sdk.sdkType as DockerSdk).getExecutableName("docker", sdk.homePath!!), "run", "--rm", - "-v", - "miktex:/miktex/.miktex", - "-v", - "${mainFile.parent.path}:/miktex/work" ) - // Avoid mounting the mainfile parent also to /miktex/work/out, - // because there may be a good reason to make the output directory the same as the source directory - if (runConfig.outputPath.getAndCreatePath() != mainFile.parent) { - parameterList.addAll(listOf("-v", "${runConfig.outputPath.getAndCreatePath()?.path}:$dockerOutputDir")) + parameterList += if (isMiktex) { + listOf( + "-v", + "miktex:/miktex/.miktex", + "-v", + "${mainFile.parent.path}:/miktex/work" + ) + } + else { + listOf( + "-v", + "${mainFile.parent.path}:/workdir" + ) + } + + if (dockerOutputDir != null) { + // Avoid mounting the mainfile parent also to /miktex/work/out, + // because there may be a good reason to make the output directory the same as the source directory + val outPath = runConfig.outputPath.getAndCreatePath() + if (outPath?.path != null && outPath != mainFile.parent) { + parameterList.addAll(listOf("-v", "${outPath.path}:$dockerOutputDir")) + } } - if (runConfig.auxilPath.getAndCreatePath() != mainFile.parent) { - parameterList.addAll(listOf("-v", "${runConfig.auxilPath.getAndCreatePath()?.path}:$dockerAuxilDir")) + if (dockerAuxilDir != null) { + val auxilPath = runConfig.auxilPath.getAndCreatePath() + if (auxilPath?.path != null && auxilPath != mainFile.parent) { + parameterList.addAll(listOf("-v", "${auxilPath.path}:$dockerAuxilDir")) + } } parameterList.add((sdk?.sdkAdditionalData as? DockerSdkAdditionalData)?.imageName ?: "miktex:latest") diff --git a/src/nl/hannahsten/texifyidea/run/latex/LatexDistributionType.kt b/src/nl/hannahsten/texifyidea/run/latex/LatexDistributionType.kt index 70ae5c2ee6..bc79ecfc94 100644 --- a/src/nl/hannahsten/texifyidea/run/latex/LatexDistributionType.kt +++ b/src/nl/hannahsten/texifyidea/run/latex/LatexDistributionType.kt @@ -16,10 +16,13 @@ enum class LatexDistributionType(val displayName: String) { MIKTEX("MiKTeX"), WSL_TEXLIVE("TeX Live using WSL"), DOCKER_MIKTEX("Dockerized MiKTeX"), + DOCKER_TEXLIVE("Dockerized TeX Live"), PROJECT_SDK("Use project SDK"); private fun isMiktex() = this == MIKTEX || this == DOCKER_MIKTEX - fun isMiktex(project: Project) = this == MIKTEX || this == DOCKER_MIKTEX || (this == PROJECT_SDK && LatexSdkUtil.getLatexProjectSdkType(project)?.getLatexDistributionType()?.isMiktex() == true) + fun isMiktex(project: Project) = this == MIKTEX || this == DOCKER_MIKTEX || (this == PROJECT_SDK && LatexSdkUtil.getLatexDistributionType(project)?.isMiktex() == true) + + fun isDocker() = this == DOCKER_MIKTEX || this == DOCKER_TEXLIVE fun isAvailable(project: Project) = LatexSdkUtil.isAvailable(this, project) diff --git a/src/nl/hannahsten/texifyidea/run/latex/LatexRunConfiguration.kt b/src/nl/hannahsten/texifyidea/run/latex/LatexRunConfiguration.kt index 22d2145a59..704a4c9ab9 100644 --- a/src/nl/hannahsten/texifyidea/run/latex/LatexRunConfiguration.kt +++ b/src/nl/hannahsten/texifyidea/run/latex/LatexRunConfiguration.kt @@ -541,7 +541,7 @@ class LatexRunConfiguration( latexDistribution } else { - LatexSdkUtil.getLatexProjectSdkType(project)?.getLatexDistributionType() ?: LatexDistributionType.TEXLIVE + LatexSdkUtil.getLatexDistributionType(project) ?: LatexDistributionType.TEXLIVE } } diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/DockerSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/DockerSdk.kt index 68ffa9a96c..d55f7c5cf7 100644 --- a/src/nl/hannahsten/texifyidea/settings/sdk/DockerSdk.kt +++ b/src/nl/hannahsten/texifyidea/settings/sdk/DockerSdk.kt @@ -8,18 +8,19 @@ import com.intellij.openapi.ui.DialogBuilder import com.intellij.openapi.util.SystemInfo import com.intellij.util.Consumer import nl.hannahsten.texifyidea.run.latex.LatexDistributionType +import nl.hannahsten.texifyidea.util.containsAny import nl.hannahsten.texifyidea.util.runCommand import org.jdom.Element import javax.swing.JComponent /** - * Currently, we only support MiKTeX Docker images, but it wouldn't be too difficult to extend for other images. + * Currently, we only support official MiKTeX/texlive Docker images, but it wouldn't be too difficult to extend for other images. */ class DockerSdk : LatexSdk("LaTeX Docker SDK") { object Availability { val isAvailable: Boolean by lazy { - getAvailableImages().any { it.contains("miktex") } + getAvailableImages().any { it.contains("miktex") || it.contains("texlive") } } fun getAvailableImages(): List = @@ -47,11 +48,18 @@ class DockerSdk : LatexSdk("LaTeX Docker SDK") { } override fun isValidSdkHome(path: String): Boolean { - // For now we only support miktex images - return "$path/docker image ls".runCommand()?.contains("miktex") ?: false + return "$path/docker image ls".runCommand()?.containsAny(setOf("miktex", "texlive")) == true } - override fun getLatexDistributionType() = LatexDistributionType.DOCKER_MIKTEX + override fun getLatexDistributionType(sdk: Sdk): LatexDistributionType { + val imageName = (sdk.sdkAdditionalData as? DockerSdkAdditionalData)?.imageName + return if (imageName?.contains("texlive") == true) { + LatexDistributionType.DOCKER_TEXLIVE + } + else { + LatexDistributionType.DOCKER_MIKTEX + } + } override fun getVersionString(sdk: Sdk): String? { val data = sdk.sdkAdditionalData as? DockerSdkAdditionalData diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdk.kt index 5218f80bba..9f7f628f7b 100644 --- a/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdk.kt +++ b/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdk.kt @@ -39,7 +39,7 @@ abstract class LatexSdk(name: String) : SdkType(name) { /** * Interface between this and [LatexDistributionType], which is used in the run configuration. */ - abstract fun getLatexDistributionType(): LatexDistributionType + abstract fun getLatexDistributionType(sdk: Sdk): LatexDistributionType /** * Construct a valid path to the executable, given the homepath. diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdkUtil.kt b/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdkUtil.kt index a2e381309d..d2bd9a3245 100644 --- a/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdkUtil.kt +++ b/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdkUtil.kt @@ -73,6 +73,7 @@ object LatexSdkUtil { if (type == LatexDistributionType.MIKTEX && isMiktexAvailable) return true if (type == LatexDistributionType.TEXLIVE && TexliveSdk.Cache.isAvailable) return true if (type == LatexDistributionType.DOCKER_MIKTEX && DockerSdk.Availability.isAvailable) return true + if (type == LatexDistributionType.DOCKER_TEXLIVE && DockerSdk.Availability.isAvailable) return true if (type == LatexDistributionType.WSL_TEXLIVE && isWslTexliveAvailable) return true return false } @@ -135,7 +136,7 @@ object LatexSdkUtil { */ fun getExecutableName(executableName: String, project: Project, latexDistributionType: LatexDistributionType? = null): String { // Prefixing the LaTeX compiler is not relevant for Docker MiKTeX (perhaps the path to the docker executable) - if (latexDistributionType == LatexDistributionType.DOCKER_MIKTEX) return executableName + if (latexDistributionType?.isDocker() == true) return executableName // Give preference to the project SDK if a valid LaTeX SDK is selected getLatexProjectSdk(project)?.let { sdk -> @@ -189,6 +190,11 @@ object LatexSdkUtil { return getLatexProjectSdk(project)?.sdkType as? LatexSdk } + fun getLatexDistributionType(project: Project): LatexDistributionType? { + val sdk = getLatexProjectSdk(project) ?: return null + return (sdk.sdkType as? LatexSdk)?.getLatexDistributionType(sdk) + } + /** * Collect SDK source paths, so paths to texmf-dist/source/latex, based on Project SDK if available (combining the default * for the SDK type and any user-added source roots) and otherwise on a random guess (ok not really). diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/MiktexLinuxSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/MiktexLinuxSdk.kt index f8515f089d..a0de6e7310 100644 --- a/src/nl/hannahsten/texifyidea/settings/sdk/MiktexLinuxSdk.kt +++ b/src/nl/hannahsten/texifyidea/settings/sdk/MiktexLinuxSdk.kt @@ -21,7 +21,7 @@ class MiktexLinuxSdk : LatexSdk("MiKTeX Mac/Linux SDK") { var version: String? = null } - override fun getLatexDistributionType() = LatexDistributionType.MIKTEX + override fun getLatexDistributionType(sdk: Sdk) = LatexDistributionType.MIKTEX override fun getExecutableName(executable: String, homePath: String): String { return "$homePath/$executable" diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/MiktexWindowsSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/MiktexWindowsSdk.kt index d30aa2fe8e..2dcdb9ca0a 100644 --- a/src/nl/hannahsten/texifyidea/settings/sdk/MiktexWindowsSdk.kt +++ b/src/nl/hannahsten/texifyidea/settings/sdk/MiktexWindowsSdk.kt @@ -21,7 +21,7 @@ class MiktexWindowsSdk : LatexSdk("MiKTeX Windows SDK") { var version: DefaultArtifactVersion? = null } - override fun getLatexDistributionType() = LatexDistributionType.MIKTEX + override fun getLatexDistributionType(sdk: Sdk) = LatexDistributionType.MIKTEX override fun getExecutableName(executable: String, homePath: String): String { val path = LatexSdkUtil.getPdflatexParentPath(Paths.get(homePath, "miktex").toString()) ?: return executable diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/TectonicSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/TectonicSdk.kt index a6bb9e9a8c..e4d3f82c7d 100644 --- a/src/nl/hannahsten/texifyidea/settings/sdk/TectonicSdk.kt +++ b/src/nl/hannahsten/texifyidea/settings/sdk/TectonicSdk.kt @@ -1,5 +1,6 @@ package nl.hannahsten.texifyidea.settings.sdk +import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile @@ -46,7 +47,7 @@ class TectonicSdk : LatexSdk("Tectonic SDK") { return Cache.fileLocationCache?.get(name) ?: "" } - override fun getLatexDistributionType() = LatexDistributionType.TEXLIVE + override fun getLatexDistributionType(sdk: Sdk) = LatexDistributionType.TEXLIVE // We assume Tectonic is in PATH. override fun getExecutableName(executable: String, homePath: String) = executable diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/TexliveSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/TexliveSdk.kt index 0efa4cbac3..d0d68f1e59 100644 --- a/src/nl/hannahsten/texifyidea/settings/sdk/TexliveSdk.kt +++ b/src/nl/hannahsten/texifyidea/settings/sdk/TexliveSdk.kt @@ -90,7 +90,7 @@ open class TexliveSdk(name: String = "TeX Live SDK") : LatexSdk(name) { override fun getInvalidHomeMessage(path: String) = "Could not find $path/bin/*/pdflatex" - override fun getLatexDistributionType() = LatexDistributionType.TEXLIVE + override fun getLatexDistributionType(sdk: Sdk) = LatexDistributionType.TEXLIVE override fun getVersionString(sdkHome: String): String { return "TeX Live " + sdkHome.split("/").lastOrNull { it.isNotBlank() } diff --git a/src/nl/hannahsten/texifyidea/util/LatexmkRcFileFinder.kt b/src/nl/hannahsten/texifyidea/util/LatexmkRcFileFinder.kt index 6096a13ff6..79ed36db2a 100644 --- a/src/nl/hannahsten/texifyidea/util/LatexmkRcFileFinder.kt +++ b/src/nl/hannahsten/texifyidea/util/LatexmkRcFileFinder.kt @@ -1,11 +1,11 @@ package nl.hannahsten.texifyidea.util import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiFile import nl.hannahsten.texifyidea.run.compiler.LatexCompiler import nl.hannahsten.texifyidea.run.latex.LatexRunConfiguration import java.io.File @@ -116,12 +116,12 @@ object LatexmkRcFileFinder { * Get the first TEXINPUTS we can find in latexmkrc files. * Cached because searching involves quite some system calls, and it's a rarely used feature. */ - fun getTexinputsVariable(someFile: PsiFile?, runConfig: LatexRunConfiguration?): String? { + fun getTexinputsVariable(directory: VirtualFile, runConfig: LatexRunConfiguration?, project: Project): String? { return if (usesLatexmkrc == false) { null } else { - val texinputs = getTexinputsVariableNoCache(someFile, runConfig) + val texinputs = getTexinputsVariableNoCache(directory, runConfig, project) if (usesLatexmkrc == null) { usesLatexmkrc = texinputs != null } @@ -132,27 +132,24 @@ object LatexmkRcFileFinder { /** * Check the (first) latexmkrc file for any additions to TEXINPUTS and return that if present. * - * @param someFile Any file, the parent directories will be searched. * @param runConfig Run configuration to check for working directory and arguments. */ - private fun getTexinputsVariableNoCache(someFile: PsiFile?, runConfig: LatexRunConfiguration?): String? { + private fun getTexinputsVariableNoCache(directory: VirtualFile, runConfig: LatexRunConfiguration?, project: Project): String? { systemLatexmkRcFile?.let { return getTexinputs(it) } if (runConfig != null) { getLocalLatexmkRcFile(runConfig.compilerArguments, runConfig.mainFile?.parent?.path)?.let { return getTexinputs(it) } } // File could be anywhere if run configurations are not used, but searching the whole project could be too expensive - someFile?.virtualFile?.parent?.let { parentDir -> - parentDir.findChild(".latexmkrc")?.let { return getTexinputs(it) } - parentDir.findChild("latexmkrc")?.let { return getTexinputs(it) } - } - val project = someFile?.project ?: return null + directory.findChild(".latexmkrc")?.let { return getTexinputs(it) } + directory.findChild("latexmkrc")?.let { return getTexinputs(it) } + val projectDir = project.guessProjectDir() if (projectDir?.isValid == false) return null projectDir?.findChild(".latexmkrc")?.let { return getTexinputs(it) } projectDir?.findChild("latexmkrc")?.let { return getTexinputs(it) } - projectDir?.children?.forEach { directory -> - directory?.findChild(".latexmkrc")?.let { return getTexinputs(it) } - directory?.findChild("latexmkrc")?.let { return getTexinputs(it) } + projectDir?.children?.forEach { childDir -> + childDir?.findChild(".latexmkrc")?.let { return getTexinputs(it) } + childDir?.findChild("latexmkrc")?.let { return getTexinputs(it) } } return null } diff --git a/src/nl/hannahsten/texifyidea/util/SystemEnvironment.kt b/src/nl/hannahsten/texifyidea/util/SystemEnvironment.kt index 6074a96446..cd55d0bfdc 100644 --- a/src/nl/hannahsten/texifyidea/util/SystemEnvironment.kt +++ b/src/nl/hannahsten/texifyidea/util/SystemEnvironment.kt @@ -1,8 +1,16 @@ package nl.hannahsten.texifyidea.util +import com.intellij.execution.RunManager import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.impl.RunManagerImpl import com.intellij.execution.process.ProcessNotCreatedException +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFile +import nl.hannahsten.texifyidea.run.latex.LatexRunConfiguration +import nl.hannahsten.texifyidea.util.files.* import org.apache.maven.artifact.versioning.DefaultArtifactVersion import java.io.File import java.io.IOException @@ -53,6 +61,10 @@ class SystemEnvironment { val evinceVersion: DefaultArtifactVersion by lazy { DefaultArtifactVersion("evince --version".runCommand()?.split(" ")?.lastOrNull() ?: "") } + + val texinputs by lazy { + runCommand("kpsewhich", "--expand-var", "'\$TEXINPUTS'") + } } } @@ -146,3 +158,50 @@ private fun readInputStream(nonBlocking: Boolean, proc: Process): String { } return output } + +/** + * Collect texinputs from various places + * + * @param rootFiles If provided, filter run configurations + * @param expandPaths Expand subdirectories + */ +fun getTexinputsPaths( + project: Project, + rootFiles: Collection, + expandPaths: Boolean = true, + latexmkSearchDirectory: VirtualFile? = null +): List { + val searchPaths = mutableListOf() + val runManager = RunManagerImpl.getInstanceImpl(project) as RunManager + val allConfigurations = runManager.allConfigurationsList + .filterIsInstance() + val selectedConfiguratios = if (rootFiles.isEmpty()) allConfigurations else allConfigurations.filter { it.mainFile in rootFiles } + val configurationTexinputsVariables = selectedConfiguratios.map { it.environmentVariables.envs }.mapNotNull { it.getOrDefault("TEXINPUTS", null) } + // Not sure which of these takes precedence, or if they are joined together + val texinputsVariables = configurationTexinputsVariables + + selectedConfiguratios.map { LatexmkRcFileFinder.getTexinputsVariable(latexmkSearchDirectory ?: project.guessProjectDir() ?: return@map null, it, project) } + + listOf(if (expandPaths) SystemEnvironment.texinputs else System.getenv("TEXINPUTS")) + + for (texinputsVariable in texinputsVariables.filterNotNull()) { + for (texInputPath in texinputsVariable.trim('\'').split(File.pathSeparator).filter { it.isNotBlank() }) { + val path = texInputPath.trimEnd(File.pathSeparatorChar) + searchPaths.add(path.trimEnd('/')) + // See the kpathsea manual, // expands to subdirs + if (path.endsWith("//")) { + LocalFileSystem.getInstance().findFileByPath(path.trimEnd('/'))?.let { parent -> + if (expandPaths) { + searchPaths.addAll( + parent.allChildDirectories() + .filter { it.isDirectory } + .map { it.path } + ) + } + else { + searchPaths.add(parent.path) + } + } + } + } + } + return searchPaths +} diff --git a/src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt b/src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt index a0e328d3e9..e8cbb0f42c 100644 --- a/src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt +++ b/src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt @@ -11,7 +11,6 @@ import nl.hannahsten.texifyidea.lang.commands.LatexCommand import nl.hannahsten.texifyidea.lang.commands.LatexGenericMathCommand.* import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand.* import nl.hannahsten.texifyidea.lang.commands.LatexGlossariesCommand.* -import nl.hannahsten.texifyidea.lang.commands.LatexIfCommand.* import nl.hannahsten.texifyidea.lang.commands.LatexListingCommand.* import nl.hannahsten.texifyidea.lang.commands.LatexMathtoolsRegularCommand.* import nl.hannahsten.texifyidea.lang.commands.LatexNatbibCommand.* @@ -432,21 +431,6 @@ object CommandMagic { */ val bibliographyIncludeCommands = includeOnlyExtensions.entries.filter { it.value.contains("bib") }.map { it.key } - @Suppress("unused") - val startIfs = hashSetOf( - IF, IFCAT, IFX, - IFCASE, IFNUM, IFODD, - IFHMODE, IFVMODE, IFMMODE, - IFINNER, IFDIM, IFVOID, - IFHBOX, IFVBOX, IFEOF, - IFTRUE, IFFALSE - ).map { it.cmd } - - /** - * All commands that end if. - */ - val endIfs = hashSetOf(FI.cmd) - /** * All commands that at first glance look like \if-esque commands, but that actually aren't. */ diff --git a/src/nl/hannahsten/texifyidea/util/parser/LatexCommandsUtil.kt b/src/nl/hannahsten/texifyidea/util/parser/LatexCommandsUtil.kt index 8698d62097..531b53e635 100644 --- a/src/nl/hannahsten/texifyidea/util/parser/LatexCommandsUtil.kt +++ b/src/nl/hannahsten/texifyidea/util/parser/LatexCommandsUtil.kt @@ -101,7 +101,7 @@ fun LatexCommands.nextCommand(): LatexCommands? { * * @return The previous command in the file, or `null` when there is no such command. */ -fun LatexCommands.previousCommand(): LatexCommands? { +fun PsiElement.previousCommand(): LatexCommands? { val content = parentOfType(LatexNoMathContent::class) ?: return null val previous = content.previousSiblingIgnoreWhitespace() as? LatexNoMathContent ?: return null diff --git a/test/nl/hannahsten/texifyidea/formatting/LatexFormattingTest.kt b/test/nl/hannahsten/texifyidea/formatting/LatexFormattingTest.kt index 54f3fc36bd..828b987701 100644 --- a/test/nl/hannahsten/texifyidea/formatting/LatexFormattingTest.kt +++ b/test/nl/hannahsten/texifyidea/formatting/LatexFormattingTest.kt @@ -230,6 +230,57 @@ fun Int?.ifPositiveAddTwo(): Int = """.trimIndent() } + fun testPlainIf() { + """ + \ifx\mycmd\undefined + undefed + \else + \if\mycmd1 + defed, 1 + \else + defed + \fi + \fi + """.trimIndent() `should be reformatted to` """ + \ifx + \mycmd\undefined + undefed + \else + \if + \mycmd1 + defed, 1 + \else + defed + \fi + \fi + """.trimIndent() + } + + fun testPlainUnknownIf() { + """ + \ifx\mycmd\undefined + undefed + \else + \ifaxp\mycmd1%\ifaxp is not a known if + defed, 1 + \else + defed + \fi + \fi + """.trimIndent() `should be reformatted to` """ + \ifx + \mycmd\undefined + undefed + \else + \ifaxp\mycmd1%\ifaxp is not a known if + defed, 1 + \else + defed + \fi + \fi + """.trimIndent() + } + fun `test section used in command definition`() { """ \documentclass{article} diff --git a/test/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexNonMatchingIfInspectionTest.kt b/test/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexNonMatchingIfInspectionTest.kt index e17e31abdc..e74991d3de 100644 --- a/test/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexNonMatchingIfInspectionTest.kt +++ b/test/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexNonMatchingIfInspectionTest.kt @@ -11,7 +11,8 @@ class LatexNonMatchingIfInspectionTest : TexifyInspectionTestBase(LatexNonMatchi """.trimIndent() ) - fun `test if not closed`() = testHighlighting("\\if") + // This is a parse error + fun `test if not closed`() = testHighlighting("\\if") fun `test fi not opened`() = testHighlighting("\\fi")