From 481cdcf078743df2eb1ac399f7f272e21af31c1f Mon Sep 17 00:00:00 2001 From: Thomas Schouten Date: Thu, 26 Dec 2024 20:51:25 +0100 Subject: [PATCH 1/4] Alias listings environments --- .../texifyidea/lang/alias/EnvironmentManager.kt | 5 +++++ .../lang/commands/LatexNewDefinitionCommand.kt | 2 ++ .../texifyidea/reference/LatexLabelReference.kt | 10 +++++----- .../hannahsten/texifyidea/util/labels/Labels.kt | 16 +++++++++++++++- .../texifyidea/util/magic/CommandMagic.kt | 1 + 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/nl/hannahsten/texifyidea/lang/alias/EnvironmentManager.kt b/src/nl/hannahsten/texifyidea/lang/alias/EnvironmentManager.kt index 58f525981..f99afa7e1 100644 --- a/src/nl/hannahsten/texifyidea/lang/alias/EnvironmentManager.kt +++ b/src/nl/hannahsten/texifyidea/lang/alias/EnvironmentManager.kt @@ -1,7 +1,10 @@ package nl.hannahsten.texifyidea.lang.alias +import nl.hannahsten.texifyidea.lang.DefaultEnvironment +import nl.hannahsten.texifyidea.lang.commands.LatexNewDefinitionCommand import nl.hannahsten.texifyidea.psi.LatexCommands import nl.hannahsten.texifyidea.util.containsAny +import nl.hannahsten.texifyidea.util.magic.cmd import nl.hannahsten.texifyidea.util.parser.requiredParameter /** @@ -16,6 +19,8 @@ object EnvironmentManager : AliasManager() { // e.g. \newenvironment{mytabl}{\begin{tabular}{cc}}{\end{tabular}} indexedDefinitions.filter { definition -> definition.requiredParameter(1)?.containsAny(aliasSet.map { "\\begin{$it}" }.toSet()) == true + // This command always defines an alias for the listings environment + || (definition.name == LatexNewDefinitionCommand.LSTNEWENVIRONMENT.cmd && aliasSet.contains(DefaultEnvironment.LISTINGS.environmentName)) } .mapNotNull { it.requiredParameter(0) } .forEach { registerAlias(firstAlias, it) } diff --git a/src/nl/hannahsten/texifyidea/lang/commands/LatexNewDefinitionCommand.kt b/src/nl/hannahsten/texifyidea/lang/commands/LatexNewDefinitionCommand.kt index 55009a442..73c5c745e 100644 --- a/src/nl/hannahsten/texifyidea/lang/commands/LatexNewDefinitionCommand.kt +++ b/src/nl/hannahsten/texifyidea/lang/commands/LatexNewDefinitionCommand.kt @@ -1,6 +1,7 @@ package nl.hannahsten.texifyidea.lang.commands import nl.hannahsten.texifyidea.lang.LatexPackage +import nl.hannahsten.texifyidea.lang.LatexPackage.Companion.LISTINGS import nl.hannahsten.texifyidea.lang.LatexPackage.Companion.TCOLORBOX import nl.hannahsten.texifyidea.lang.LatexPackage.Companion.XARGS @@ -38,6 +39,7 @@ enum class LatexNewDefinitionCommand( DECLAREROBUSTCOMMANDX("DeclareRobustCommandx", "cmd".asRequired(), "args".asOptional(), "default".asOptional(), "def".asRequired(Argument.Type.TEXT), dependency = XARGS), NEWENVIRONMENTX("newenvironmentx", "cmd".asRequired(), "args".asOptional(), "default".asOptional(), "begdef".asRequired(Argument.Type.TEXT), "enddef".asRequired(Argument.Type.TEXT), dependency = XARGS), RENEWENVIRONMENTX("renewenvironmentx", "cmd".asRequired(), "args".asOptional(), "default".asOptional(), "begdef".asRequired(Argument.Type.TEXT), "enddef".asRequired(Argument.Type.TEXT), dependency = XARGS), + LSTNEWENVIRONMENT("lstnewenvironment", "name".asRequired(), "number".asOptional(), "default arg".asOptional(), "starting code".asRequired(), "ending code".asRequired(), dependency = LISTINGS), ; override val identifier: String diff --git a/src/nl/hannahsten/texifyidea/reference/LatexLabelReference.kt b/src/nl/hannahsten/texifyidea/reference/LatexLabelReference.kt index 7b92e0acb..c79606df0 100644 --- a/src/nl/hannahsten/texifyidea/reference/LatexLabelReference.kt +++ b/src/nl/hannahsten/texifyidea/reference/LatexLabelReference.kt @@ -66,8 +66,8 @@ class LatexLabelReference(element: LatexCommands, range: TextRange?) : PsiRefere val allCommands = file.commandsInFileSet() return file.findLatexLabelingElementsInFileSet() .toSet() - .mapNotNull { labelingCommand: PsiElement -> - val extractedLabel = labelingCommand.extractLabelName(referencingFileSetCommands = allCommands) + .mapNotNull { labelingElement: PsiElement -> + val extractedLabel = labelingElement.extractLabelName(referencingFileSetCommands = allCommands) if (extractedLabel.isBlank()) return@mapNotNull null LookupElementBuilder @@ -75,11 +75,11 @@ class LatexLabelReference(element: LatexCommands, range: TextRange?) : PsiRefere .bold() .withInsertHandler(LatexReferenceInsertHandler()) .withTypeText( - labelingCommand.containingFile.name + ":" + + labelingElement.containingFile.name + ":" + ( 1 + StringUtil.offsetToLineNumber( - labelingCommand.containingFile.text, - labelingCommand.textOffset + labelingElement.containingFile.text, + labelingElement.textOffset ) ), true diff --git a/src/nl/hannahsten/texifyidea/util/labels/Labels.kt b/src/nl/hannahsten/texifyidea/util/labels/Labels.kt index a80998d7a..66cbb6ef2 100644 --- a/src/nl/hannahsten/texifyidea/util/labels/Labels.kt +++ b/src/nl/hannahsten/texifyidea/util/labels/Labels.kt @@ -3,14 +3,18 @@ package nl.hannahsten.texifyidea.util.labels import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import nl.hannahsten.texifyidea.index.LatexEnvironmentsIndex import nl.hannahsten.texifyidea.index.LatexParameterLabeledCommandsIndex import nl.hannahsten.texifyidea.index.LatexParameterLabeledEnvironmentsIndex +import nl.hannahsten.texifyidea.lang.alias.EnvironmentManager import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand import nl.hannahsten.texifyidea.psi.LatexCommands +import nl.hannahsten.texifyidea.psi.getEnvironmentName import nl.hannahsten.texifyidea.reference.InputFileReference import nl.hannahsten.texifyidea.util.files.commandsInFile import nl.hannahsten.texifyidea.util.files.commandsInFileSet import nl.hannahsten.texifyidea.util.files.psiFile +import nl.hannahsten.texifyidea.util.magic.EnvironmentMagic /** * Finds all the defined labels in the fileset of the file. @@ -46,9 +50,19 @@ fun PsiFile.findLatexLabelingElementsInFile(): Sequence = sequenceOf fun PsiFile.findLatexLabelingElementsInFileSet(): Sequence = sequenceOf( findLabelingCommandsInFileSet(), LatexParameterLabeledEnvironmentsIndex.Util.getItemsInFileSet(this).asSequence(), - LatexParameterLabeledCommandsIndex.Util.getItemsInFileSet(this).asSequence() + LatexParameterLabeledCommandsIndex.Util.getItemsInFileSet(this).asSequence(), + findLabeledEnvironments(this), ).flatten() +/** + * All environments with labels, including user defined + */ +fun findLabeledEnvironments(file: PsiFile): Sequence { + EnvironmentManager.updateAliases(EnvironmentMagic.labelAsParameter, file.project) + val allEnvironments = EnvironmentManager.getAliases(EnvironmentMagic.labelAsParameter.first()) + return LatexEnvironmentsIndex.Util.getItemsInFileSet(file).filter { it.getEnvironmentName() in allEnvironments }.asSequence() +} + /** * Make a sequence of all commands in the file set that specify a label. This does not include commands which define a label via an * optional parameter. diff --git a/src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt b/src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt index 948615a13..3adf327dd 100644 --- a/src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt +++ b/src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt @@ -326,6 +326,7 @@ object CommandMagic { NEWTCOLORBOX_, PROVIDETCOLORBOX, NEWENVIRONMENTX, + LSTNEWENVIRONMENT, ).map { it.cmd } /** From a6d4bbd3a7327f9e528fea748cd3bfb252294fde Mon Sep 17 00:00:00 2001 From: Thomas Schouten Date: Fri, 27 Dec 2024 12:05:09 +0100 Subject: [PATCH 2/4] Support label references to user defined listings environment --- CHANGELOG.md | 1 + .../lang/LabelingEnvironmentInformation.kt | 13 ++++++++ .../lang/alias/EnvironmentManager.kt | 23 +++++++++++++- .../texifyidea/psi/LatexEnvironmentUtil.kt | 4 +-- .../texifyidea/util/labels/LabelExtraction.kt | 20 +++++++++++-- .../LatexUnresolvedReferenceInspectionTest.kt | 30 +++++++++++++++++++ 6 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 src/nl/hannahsten/texifyidea/lang/LabelingEnvironmentInformation.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 105ed6434..866e8dd62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Added +* Support label references to user defined listings environment * Add option to disable automatic compilation in power save mode * Convert automatic compilation settings to a combobox * Add checkboxes to graphic insertion wizard for relative width or height diff --git a/src/nl/hannahsten/texifyidea/lang/LabelingEnvironmentInformation.kt b/src/nl/hannahsten/texifyidea/lang/LabelingEnvironmentInformation.kt new file mode 100644 index 000000000..9f9ac8527 --- /dev/null +++ b/src/nl/hannahsten/texifyidea/lang/LabelingEnvironmentInformation.kt @@ -0,0 +1,13 @@ +package nl.hannahsten.texifyidea.lang + +import arrow.core.NonEmptyList + +/** + * Information about a user-defined environment which has a \label command in the definition. + */ +data class LabelingEnvironmentInformation( + /** Parameter positions which define a label, starting from 0 (note: LaTeX starts from 1). */ + var positions: NonEmptyList, + /** Default label prefix, for example in \newcommand{\mylabel}[1]{\label{sec:#1}} it would be sec: */ + var prefix: String = "" +) diff --git a/src/nl/hannahsten/texifyidea/lang/alias/EnvironmentManager.kt b/src/nl/hannahsten/texifyidea/lang/alias/EnvironmentManager.kt index f99afa7e1..1a27d5f51 100644 --- a/src/nl/hannahsten/texifyidea/lang/alias/EnvironmentManager.kt +++ b/src/nl/hannahsten/texifyidea/lang/alias/EnvironmentManager.kt @@ -1,9 +1,12 @@ package nl.hannahsten.texifyidea.lang.alias +import arrow.core.nonEmptyListOf import nl.hannahsten.texifyidea.lang.DefaultEnvironment +import nl.hannahsten.texifyidea.lang.LabelingEnvironmentInformation import nl.hannahsten.texifyidea.lang.commands.LatexNewDefinitionCommand import nl.hannahsten.texifyidea.psi.LatexCommands import nl.hannahsten.texifyidea.util.containsAny +import nl.hannahsten.texifyidea.util.magic.EnvironmentMagic import nl.hannahsten.texifyidea.util.magic.cmd import nl.hannahsten.texifyidea.util.parser.requiredParameter @@ -12,17 +15,35 @@ import nl.hannahsten.texifyidea.util.parser.requiredParameter */ object EnvironmentManager : AliasManager() { + /** + * Maintain information about label parameter locations of environments for which that is applicable. + * Maps environment name to parameter index of the \begin command, starting from 0 but including the first parameter which is the environment name + */ + val labelAliasesInfo = mutableMapOf() + override fun findAllAliases(aliasSet: Set, indexedDefinitions: Collection) { val firstAlias = aliasSet.first() // Assume the environment that is defined is the first parameter, and that the first part of the definition is in the second // e.g. \newenvironment{mytabl}{\begin{tabular}{cc}}{\end{tabular}} - indexedDefinitions.filter { definition -> + val definitions = indexedDefinitions.filter { definition -> definition.requiredParameter(1)?.containsAny(aliasSet.map { "\\begin{$it}" }.toSet()) == true // This command always defines an alias for the listings environment || (definition.name == LatexNewDefinitionCommand.LSTNEWENVIRONMENT.cmd && aliasSet.contains(DefaultEnvironment.LISTINGS.environmentName)) } + definitions .mapNotNull { it.requiredParameter(0) } .forEach { registerAlias(firstAlias, it) } + + // Update label parameter position information + if (aliasSet.intersect(EnvironmentMagic.labelAsParameter).isNotEmpty()) { + definitions.forEach { + val definedEnvironment = it.requiredParameter(0) ?: return@forEach + // The label may be in an optional parameter of an environment, but it may also be in other places like a \lstset, so for now we do a text-based search + val text = it.requiredParameter(1) ?: return@forEach + val index = "label\\s*=\\s*\\{?\\s*#(\\d)".toRegex().find(text)?.groupValues?.getOrNull(1)?.toInt() ?: return@forEach + labelAliasesInfo[definedEnvironment] = LabelingEnvironmentInformation(nonEmptyListOf(index)) + } + } } } \ No newline at end of file diff --git a/src/nl/hannahsten/texifyidea/psi/LatexEnvironmentUtil.kt b/src/nl/hannahsten/texifyidea/psi/LatexEnvironmentUtil.kt index 3ca79ec66..276234f80 100644 --- a/src/nl/hannahsten/texifyidea/psi/LatexEnvironmentUtil.kt +++ b/src/nl/hannahsten/texifyidea/psi/LatexEnvironmentUtil.kt @@ -9,12 +9,10 @@ import nl.hannahsten.texifyidea.util.magic.EnvironmentMagic import nl.hannahsten.texifyidea.util.parser.getOptionalParameterMapFromParameters import nl.hannahsten.texifyidea.util.parser.toStringMap -/* -* LatexEnvironment -*/ /** * Find the label of the environment. The method finds labels inside the environment content as well as labels * specified via an optional parameter + * Similar to LabelExtraction#extractLabelElement, but we cannot use the index here * * @return the label name if any, null otherwise */ diff --git a/src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt b/src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt index 6fa734a9d..859c689e6 100644 --- a/src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt +++ b/src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt @@ -3,6 +3,7 @@ package nl.hannahsten.texifyidea.util.labels import com.intellij.psi.PsiElement import com.jetbrains.rd.util.first import nl.hannahsten.texifyidea.lang.alias.CommandManager +import nl.hannahsten.texifyidea.lang.alias.EnvironmentManager import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand import nl.hannahsten.texifyidea.psi.* import nl.hannahsten.texifyidea.util.magic.CommandMagic @@ -14,6 +15,7 @@ import nl.hannahsten.texifyidea.util.parser.toStringMap /** * Extracts the label element (so the element that should be resolved to) from the PsiElement given that the PsiElement represents a label. + * Also see LatexEnvironmentUtil#getLabel() */ fun PsiElement.extractLabelElement(): PsiElement? { fun getLabelParameterText(command: LatexCommandWithParams): LatexParameterText { @@ -45,7 +47,14 @@ fun PsiElement.extractLabelElement(): PsiElement? { getLabelParameterText(beginCommand) } else { - null + // Check for user defined environments + val labelPositions = EnvironmentManager.labelAliasesInfo.get(getEnvironmentName()) + if (labelPositions != null) { + this.beginCommand.parameterList.getOrNull(labelPositions.positions.first())?.firstChildOfType(LatexParameterText::class) + } + else { + null + } } } else -> null @@ -86,7 +95,14 @@ fun PsiElement.extractLabelName(referencingFileSetCommands: Collection this.getLabel() ?: "" + is LatexEnvironment -> { + this.getLabel() + // Check if it is a user defined alias of a labeled environment + ?: EnvironmentManager.labelAliasesInfo.get(getEnvironmentName())?.let { + this.beginCommand.parameterList.getOrNull(it.positions.first())?.firstChildOfType(LatexParameterText::class)?.text + } + ?: "" + } else -> text } } \ No newline at end of file diff --git a/test/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexUnresolvedReferenceInspectionTest.kt b/test/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexUnresolvedReferenceInspectionTest.kt index 757fcc659..368098172 100644 --- a/test/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexUnresolvedReferenceInspectionTest.kt +++ b/test/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexUnresolvedReferenceInspectionTest.kt @@ -5,6 +5,7 @@ import io.mockk.mockkStatic import nl.hannahsten.texifyidea.file.LatexFileType import nl.hannahsten.texifyidea.inspections.TexifyInspectionTestBase import nl.hannahsten.texifyidea.lang.alias.CommandManager +import nl.hannahsten.texifyidea.lang.alias.EnvironmentManager import nl.hannahsten.texifyidea.util.runCommandWithExitCode import org.junit.Test @@ -100,6 +101,35 @@ class LatexUnresolvedReferenceInspectionTest : TexifyInspectionTestBase(LatexUnr myFixture.checkHighlighting() } + fun testFigureReferencedCustomListingsEnvironment() { + myFixture.configureByText( + LatexFileType, + """ + \lstnewenvironment{java}[2][]{ + \lstset{ + captionpos=b, + language=Java, + % other style attributes + caption={#1}, + label={#2}, + } + }{} + + \begin{java}[Test]{lst:test} + class Main { + public static void main(String[] args) { + return "HelloWorld"; + } + } + \end{java} + + \ref{lst:test} + """.trimIndent() + ) + EnvironmentManager.updateAliases(setOf("lstlisting"), project) + myFixture.checkHighlighting() + } + fun testComma() { myFixture.configureByText(LatexFileType, """\input{name,with,.tex}""") myFixture.checkHighlighting() From 5a254597ff303a83ad9969f84779d03098daad7c Mon Sep 17 00:00:00 2001 From: Thomas Schouten Date: Fri, 27 Dec 2024 12:07:50 +0100 Subject: [PATCH 3/4] Update documentation --- Writerside/topics/Code-navigation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Writerside/topics/Code-navigation.md b/Writerside/topics/Code-navigation.md index 13860b36e..859940c0c 100644 --- a/Writerside/topics/Code-navigation.md +++ b/Writerside/topics/Code-navigation.md @@ -26,7 +26,8 @@ Web links in `\url` and `\href` commands are clickable using Ctrl + Cl By pressing Ctrl + B on a reference to a label, or a citation of a bibtex entry, your cursor will go to the declaration of the reference or citation. In general, you can go back to your previous cursor location with Ctrl + Alt + <- -This also works with usages of commands defined with `\newcommand` definitions (in your fileset, not in LaTeX packages), but only if your command definition includes braces, like `\newcommand{\mycommand}{definition}` +This also works with usages of commands defined with `\newcommand` definitions (in your fileset, not in LaTeX packages), but only if your command definition includes braces, like `\newcommand{\mycommand}{definition}`. +It also works for user defined environments that accept a label as parameter, for example using `\lstnewenvironment`. ![go-to-label-declaration](go-to-label-declaration.gif) From 1b269be0013a9f0898b6ad14fc7fd27565b449f0 Mon Sep 17 00:00:00 2001 From: Thomas Schouten Date: Fri, 27 Dec 2024 13:32:58 +0100 Subject: [PATCH 4/4] Formatting --- src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt b/src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt index 859c689e6..3ad2d006b 100644 --- a/src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt +++ b/src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt @@ -48,7 +48,7 @@ fun PsiElement.extractLabelElement(): PsiElement? { } else { // Check for user defined environments - val labelPositions = EnvironmentManager.labelAliasesInfo.get(getEnvironmentName()) + val labelPositions = EnvironmentManager.labelAliasesInfo.getOrDefault(getEnvironmentName(), null) if (labelPositions != null) { this.beginCommand.parameterList.getOrNull(labelPositions.positions.first())?.firstChildOfType(LatexParameterText::class) } @@ -98,7 +98,7 @@ fun PsiElement.extractLabelName(referencingFileSetCommands: Collection { this.getLabel() // Check if it is a user defined alias of a labeled environment - ?: EnvironmentManager.labelAliasesInfo.get(getEnvironmentName())?.let { + ?: EnvironmentManager.labelAliasesInfo.getOrDefault(getEnvironmentName(), null)?.let { this.beginCommand.parameterList.getOrNull(it.positions.first())?.firstChildOfType(LatexParameterText::class)?.text } ?: ""