Skip to content

Commit c748e1f

Browse files
authored
Merge pull request #3825 from Hannah-Sten/custom-env-labels
Support label references to user defined listings environment
2 parents b410288 + 1b269be commit c748e1f

File tree

11 files changed

+115
-13
lines changed

11 files changed

+115
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44

55
### Added
6+
* Support label references to user defined listings environment
67
* Add option to disable automatic compilation in power save mode
78
* Convert automatic compilation settings to a combobox
89
* Add checkboxes to graphic insertion wizard for relative width or height

Writerside/topics/Code-navigation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ Web links in `\url` and `\href` commands are clickable using <shortcut>Ctrl + Cl
2626
By pressing <shortcut>Ctrl + B</shortcut> 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.
2727
In general, you can go back to your previous cursor location with <shortcut>Ctrl + Alt + &lt;-</shortcut>
2828

29-
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}`
29+
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}`.
30+
It also works for user defined environments that accept a label as parameter, for example using `\lstnewenvironment`.
3031

3132
![go-to-label-declaration](go-to-label-declaration.gif)
3233

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package nl.hannahsten.texifyidea.lang
2+
3+
import arrow.core.NonEmptyList
4+
5+
/**
6+
* Information about a user-defined environment which has a \label command in the definition.
7+
*/
8+
data class LabelingEnvironmentInformation(
9+
/** Parameter positions which define a label, starting from 0 (note: LaTeX starts from 1). */
10+
var positions: NonEmptyList<Int>,
11+
/** Default label prefix, for example in \newcommand{\mylabel}[1]{\label{sec:#1}} it would be sec: */
12+
var prefix: String = ""
13+
)
Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,49 @@
11
package nl.hannahsten.texifyidea.lang.alias
22

3+
import arrow.core.nonEmptyListOf
4+
import nl.hannahsten.texifyidea.lang.DefaultEnvironment
5+
import nl.hannahsten.texifyidea.lang.LabelingEnvironmentInformation
6+
import nl.hannahsten.texifyidea.lang.commands.LatexNewDefinitionCommand
37
import nl.hannahsten.texifyidea.psi.LatexCommands
48
import nl.hannahsten.texifyidea.util.containsAny
9+
import nl.hannahsten.texifyidea.util.magic.EnvironmentMagic
10+
import nl.hannahsten.texifyidea.util.magic.cmd
511
import nl.hannahsten.texifyidea.util.parser.requiredParameter
612

713
/**
814
* Similar to the [CommandManager], this manages aliases of environments.
915
*/
1016
object EnvironmentManager : AliasManager() {
1117

18+
/**
19+
* Maintain information about label parameter locations of environments for which that is applicable.
20+
* Maps environment name to parameter index of the \begin command, starting from 0 but including the first parameter which is the environment name
21+
*/
22+
val labelAliasesInfo = mutableMapOf<String, LabelingEnvironmentInformation>()
23+
1224
override fun findAllAliases(aliasSet: Set<String>, indexedDefinitions: Collection<LatexCommands>) {
1325
val firstAlias = aliasSet.first()
1426

1527
// Assume the environment that is defined is the first parameter, and that the first part of the definition is in the second
1628
// e.g. \newenvironment{mytabl}{\begin{tabular}{cc}}{\end{tabular}}
17-
indexedDefinitions.filter { definition ->
29+
val definitions = indexedDefinitions.filter { definition ->
1830
definition.requiredParameter(1)?.containsAny(aliasSet.map { "\\begin{$it}" }.toSet()) == true
31+
// This command always defines an alias for the listings environment
32+
|| (definition.name == LatexNewDefinitionCommand.LSTNEWENVIRONMENT.cmd && aliasSet.contains(DefaultEnvironment.LISTINGS.environmentName))
1933
}
34+
definitions
2035
.mapNotNull { it.requiredParameter(0) }
2136
.forEach { registerAlias(firstAlias, it) }
37+
38+
// Update label parameter position information
39+
if (aliasSet.intersect(EnvironmentMagic.labelAsParameter).isNotEmpty()) {
40+
definitions.forEach {
41+
val definedEnvironment = it.requiredParameter(0) ?: return@forEach
42+
// 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
43+
val text = it.requiredParameter(1) ?: return@forEach
44+
val index = "label\\s*=\\s*\\{?\\s*#(\\d)".toRegex().find(text)?.groupValues?.getOrNull(1)?.toInt() ?: return@forEach
45+
labelAliasesInfo[definedEnvironment] = LabelingEnvironmentInformation(nonEmptyListOf(index))
46+
}
47+
}
2248
}
2349
}

src/nl/hannahsten/texifyidea/lang/commands/LatexNewDefinitionCommand.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package nl.hannahsten.texifyidea.lang.commands
22

33
import nl.hannahsten.texifyidea.lang.LatexPackage
4+
import nl.hannahsten.texifyidea.lang.LatexPackage.Companion.LISTINGS
45
import nl.hannahsten.texifyidea.lang.LatexPackage.Companion.TCOLORBOX
56
import nl.hannahsten.texifyidea.lang.LatexPackage.Companion.XARGS
67

@@ -38,6 +39,7 @@ enum class LatexNewDefinitionCommand(
3839
DECLAREROBUSTCOMMANDX("DeclareRobustCommandx", "cmd".asRequired(), "args".asOptional(), "default".asOptional(), "def".asRequired(Argument.Type.TEXT), dependency = XARGS),
3940
NEWENVIRONMENTX("newenvironmentx", "cmd".asRequired(), "args".asOptional(), "default".asOptional(), "begdef".asRequired(Argument.Type.TEXT), "enddef".asRequired(Argument.Type.TEXT), dependency = XARGS),
4041
RENEWENVIRONMENTX("renewenvironmentx", "cmd".asRequired(), "args".asOptional(), "default".asOptional(), "begdef".asRequired(Argument.Type.TEXT), "enddef".asRequired(Argument.Type.TEXT), dependency = XARGS),
42+
LSTNEWENVIRONMENT("lstnewenvironment", "name".asRequired(), "number".asOptional(), "default arg".asOptional(), "starting code".asRequired(), "ending code".asRequired(), dependency = LISTINGS),
4143
;
4244

4345
override val identifier: String

src/nl/hannahsten/texifyidea/psi/LatexEnvironmentUtil.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ import nl.hannahsten.texifyidea.util.magic.EnvironmentMagic
99
import nl.hannahsten.texifyidea.util.parser.getOptionalParameterMapFromParameters
1010
import nl.hannahsten.texifyidea.util.parser.toStringMap
1111

12-
/*
13-
* LatexEnvironment
14-
*/
1512
/**
1613
* Find the label of the environment. The method finds labels inside the environment content as well as labels
1714
* specified via an optional parameter
15+
* Similar to LabelExtraction#extractLabelElement, but we cannot use the index here
1816
*
1917
* @return the label name if any, null otherwise
2018
*/

src/nl/hannahsten/texifyidea/reference/LatexLabelReference.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,20 @@ class LatexLabelReference(element: LatexCommands, range: TextRange?) : PsiRefere
6666
val allCommands = file.commandsInFileSet()
6767
return file.findLatexLabelingElementsInFileSet()
6868
.toSet()
69-
.mapNotNull { labelingCommand: PsiElement ->
70-
val extractedLabel = labelingCommand.extractLabelName(referencingFileSetCommands = allCommands)
69+
.mapNotNull { labelingElement: PsiElement ->
70+
val extractedLabel = labelingElement.extractLabelName(referencingFileSetCommands = allCommands)
7171
if (extractedLabel.isBlank()) return@mapNotNull null
7272

7373
LookupElementBuilder
7474
.create(extractedLabel)
7575
.bold()
7676
.withInsertHandler(LatexReferenceInsertHandler())
7777
.withTypeText(
78-
labelingCommand.containingFile.name + ":" +
78+
labelingElement.containingFile.name + ":" +
7979
(
8080
1 + StringUtil.offsetToLineNumber(
81-
labelingCommand.containingFile.text,
82-
labelingCommand.textOffset
81+
labelingElement.containingFile.text,
82+
labelingElement.textOffset
8383
)
8484
),
8585
true

src/nl/hannahsten/texifyidea/util/labels/LabelExtraction.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package nl.hannahsten.texifyidea.util.labels
33
import com.intellij.psi.PsiElement
44
import com.jetbrains.rd.util.first
55
import nl.hannahsten.texifyidea.lang.alias.CommandManager
6+
import nl.hannahsten.texifyidea.lang.alias.EnvironmentManager
67
import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand
78
import nl.hannahsten.texifyidea.psi.*
89
import nl.hannahsten.texifyidea.util.magic.CommandMagic
@@ -14,6 +15,7 @@ import nl.hannahsten.texifyidea.util.parser.toStringMap
1415

1516
/**
1617
* Extracts the label element (so the element that should be resolved to) from the PsiElement given that the PsiElement represents a label.
18+
* Also see LatexEnvironmentUtil#getLabel()
1719
*/
1820
fun PsiElement.extractLabelElement(): PsiElement? {
1921
fun getLabelParameterText(command: LatexCommandWithParams): LatexParameterText {
@@ -45,7 +47,14 @@ fun PsiElement.extractLabelElement(): PsiElement? {
4547
getLabelParameterText(beginCommand)
4648
}
4749
else {
48-
null
50+
// Check for user defined environments
51+
val labelPositions = EnvironmentManager.labelAliasesInfo.getOrDefault(getEnvironmentName(), null)
52+
if (labelPositions != null) {
53+
this.beginCommand.parameterList.getOrNull(labelPositions.positions.first())?.firstChildOfType(LatexParameterText::class)
54+
}
55+
else {
56+
null
57+
}
4958
}
5059
}
5160
else -> null
@@ -86,7 +95,14 @@ fun PsiElement.extractLabelName(referencingFileSetCommands: Collection<LatexComm
8695
}
8796
}
8897

89-
is LatexEnvironment -> this.getLabel() ?: ""
98+
is LatexEnvironment -> {
99+
this.getLabel()
100+
// Check if it is a user defined alias of a labeled environment
101+
?: EnvironmentManager.labelAliasesInfo.getOrDefault(getEnvironmentName(), null)?.let {
102+
this.beginCommand.parameterList.getOrNull(it.positions.first())?.firstChildOfType(LatexParameterText::class)?.text
103+
}
104+
?: ""
105+
}
90106
else -> text
91107
}
92108
}

src/nl/hannahsten/texifyidea/util/labels/Labels.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ package nl.hannahsten.texifyidea.util.labels
33
import com.intellij.openapi.project.Project
44
import com.intellij.psi.PsiElement
55
import com.intellij.psi.PsiFile
6+
import nl.hannahsten.texifyidea.index.LatexEnvironmentsIndex
67
import nl.hannahsten.texifyidea.index.LatexParameterLabeledCommandsIndex
78
import nl.hannahsten.texifyidea.index.LatexParameterLabeledEnvironmentsIndex
9+
import nl.hannahsten.texifyidea.lang.alias.EnvironmentManager
810
import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand
911
import nl.hannahsten.texifyidea.psi.LatexCommands
12+
import nl.hannahsten.texifyidea.psi.getEnvironmentName
1013
import nl.hannahsten.texifyidea.reference.InputFileReference
1114
import nl.hannahsten.texifyidea.util.files.commandsInFile
1215
import nl.hannahsten.texifyidea.util.files.commandsInFileSet
1316
import nl.hannahsten.texifyidea.util.files.psiFile
17+
import nl.hannahsten.texifyidea.util.magic.EnvironmentMagic
1418

1519
/**
1620
* Finds all the defined labels in the fileset of the file.
@@ -46,9 +50,19 @@ fun PsiFile.findLatexLabelingElementsInFile(): Sequence<PsiElement> = sequenceOf
4650
fun PsiFile.findLatexLabelingElementsInFileSet(): Sequence<PsiElement> = sequenceOf(
4751
findLabelingCommandsInFileSet(),
4852
LatexParameterLabeledEnvironmentsIndex.Util.getItemsInFileSet(this).asSequence(),
49-
LatexParameterLabeledCommandsIndex.Util.getItemsInFileSet(this).asSequence()
53+
LatexParameterLabeledCommandsIndex.Util.getItemsInFileSet(this).asSequence(),
54+
findLabeledEnvironments(this),
5055
).flatten()
5156

57+
/**
58+
* All environments with labels, including user defined
59+
*/
60+
fun findLabeledEnvironments(file: PsiFile): Sequence<PsiElement> {
61+
EnvironmentManager.updateAliases(EnvironmentMagic.labelAsParameter, file.project)
62+
val allEnvironments = EnvironmentManager.getAliases(EnvironmentMagic.labelAsParameter.first())
63+
return LatexEnvironmentsIndex.Util.getItemsInFileSet(file).filter { it.getEnvironmentName() in allEnvironments }.asSequence()
64+
}
65+
5266
/**
5367
* 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
5468
* optional parameter.

src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ object CommandMagic {
326326
NEWTCOLORBOX_,
327327
PROVIDETCOLORBOX,
328328
NEWENVIRONMENTX,
329+
LSTNEWENVIRONMENT,
329330
).map { it.cmd }
330331

331332
/**

test/nl/hannahsten/texifyidea/inspections/latex/probablebugs/LatexUnresolvedReferenceInspectionTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.mockk.mockkStatic
55
import nl.hannahsten.texifyidea.file.LatexFileType
66
import nl.hannahsten.texifyidea.inspections.TexifyInspectionTestBase
77
import nl.hannahsten.texifyidea.lang.alias.CommandManager
8+
import nl.hannahsten.texifyidea.lang.alias.EnvironmentManager
89
import nl.hannahsten.texifyidea.util.runCommandWithExitCode
910
import org.junit.Test
1011

@@ -100,6 +101,35 @@ class LatexUnresolvedReferenceInspectionTest : TexifyInspectionTestBase(LatexUnr
100101
myFixture.checkHighlighting()
101102
}
102103

104+
fun testFigureReferencedCustomListingsEnvironment() {
105+
myFixture.configureByText(
106+
LatexFileType,
107+
"""
108+
\lstnewenvironment{java}[2][]{
109+
\lstset{
110+
captionpos=b,
111+
language=Java,
112+
% other style attributes
113+
caption={#1},
114+
label={#2},
115+
}
116+
}{}
117+
118+
\begin{java}[Test]{lst:test}
119+
class Main {
120+
public static void main(String[] args) {
121+
return "HelloWorld";
122+
}
123+
}
124+
\end{java}
125+
126+
\ref{lst:test}
127+
""".trimIndent()
128+
)
129+
EnvironmentManager.updateAliases(setOf("lstlisting"), project)
130+
myFixture.checkHighlighting()
131+
}
132+
103133
fun testComma() {
104134
myFixture.configureByText(LatexFileType, """\input{name,with,.tex}""")
105135
myFixture.checkHighlighting()

0 commit comments

Comments
 (0)