diff --git a/CHANGELOG.md b/CHANGELOG.md index 93d0ec636..42d2b6a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [Unreleased] ### Added +* Change order in structure view to match source file and sectioning level +* Add command redefinitions to command definition filter in structure view ### Fixed diff --git a/src/nl/hannahsten/texifyidea/structure/filter/CommandDefinitionFilter.kt b/src/nl/hannahsten/texifyidea/structure/filter/CommandDefinitionFilter.kt index e88ce7297..3a105d93f 100644 --- a/src/nl/hannahsten/texifyidea/structure/filter/CommandDefinitionFilter.kt +++ b/src/nl/hannahsten/texifyidea/structure/filter/CommandDefinitionFilter.kt @@ -18,8 +18,7 @@ class CommandDefinitionFilter : Filter { true } else !( - treeElement.commandName == "\\newcommand" || - treeElement.commandName in CommandMagic.mathCommandDefinitions || + treeElement.commandName in CommandMagic.commandDefinitionsAndRedefinitions || treeElement.presentation is LatexOtherCommandPresentation ) } diff --git a/src/nl/hannahsten/texifyidea/structure/latex/LatexStructureViewElement.kt b/src/nl/hannahsten/texifyidea/structure/latex/LatexStructureViewElement.kt index 1a56359d9..b958116a0 100644 --- a/src/nl/hannahsten/texifyidea/structure/latex/LatexStructureViewElement.kt +++ b/src/nl/hannahsten/texifyidea/structure/latex/LatexStructureViewElement.kt @@ -22,6 +22,7 @@ import nl.hannahsten.texifyidea.structure.latex.SectionNumbering.DocumentClass import nl.hannahsten.texifyidea.util.getIncludeCommands import nl.hannahsten.texifyidea.util.labels.getLabelDefinitionCommands import nl.hannahsten.texifyidea.util.magic.CommandMagic +import nl.hannahsten.texifyidea.util.magic.cmd import nl.hannahsten.texifyidea.util.parser.allCommands import nl.hannahsten.texifyidea.util.parser.getIncludedFiles import java.util.* @@ -79,123 +80,113 @@ class LatexStructureViewElement(private val element: PsiElement) : StructureView // Fetch all commands in the active file. val numbering = SectionNumbering(DocumentClass.getClassByName(docClass)) val commands = element.allCommands() - val treeElements = ArrayList() + val treeElements = ArrayList() - // Add includes. - addIncludes(treeElements, commands) + val includeCommands = getIncludeCommands() + val labelingCommands = getLabelDefinitionCommands() // Add sectioning. - val sections = ArrayDeque() - for (currentCmd in commands) { - val token = currentCmd.name - + val sections = mutableListOf() + for (command in commands) { // Update counter. - if (token == "\\addtocounter" || token == "\\setcounter") { - updateNumbering(currentCmd, numbering) - continue - } - - // Only consider section markers. - if (!CommandMagic.sectionMarkers.contains(token)) { - continue - } - - if (currentCmd.getRequiredParameters().isEmpty()) { - continue - } - - val child = LatexStructureViewCommandElement.newCommand(currentCmd) ?: continue - - // First section. - if (sections.isEmpty()) { - sections.addFirst(child) - treeElements.add(child) - setLevelHint(child, numbering) + if (command.name == LatexGenericRegularCommand.ADDTOCOUNTER.cmd || command.name == LatexGenericRegularCommand.SETCOUNTER.cmd) { + updateNumbering(command, numbering) continue } - val currentIndex = order(current(sections) ?: continue) - val nextIndex = order(currentCmd) - - when { - currentIndex == nextIndex -> registerSameLevel(sections, child, currentCmd, treeElements, numbering) - nextIndex > currentIndex -> registerDeeper(sections, child, numbering) - else -> registerHigher(sections, child, currentCmd, treeElements, numbering) - } - } - - // Add command definitions. - CommandMagic.commandDefinitionsAndRedefinitions.forEach { - addFromCommand(treeElements, commands, it) - } - - // Add label definitions. - addFromLabelingCommands(treeElements, commands) - - // Add bibitem definitions. - addFromCommand(treeElements, commands, "\\bibitem") - - return treeElements.toTypedArray() - } - - private fun addIncludes(treeElements: MutableList, commands: List) { - for (command in commands) { - if (command.name !in getIncludeCommands()) { - continue - } + val newElement = LatexStructureViewCommandElement.newCommand(command) ?: continue - val elt = LatexStructureViewCommandElement.newCommand(command) ?: continue - for (psiFile in command.getIncludedFiles(includeInstalledPackages = TexifySettings.getInstance().showPackagesInStructureView)) { - if (BibtexFileType == psiFile.fileType) { - elt.addChild(BibtexStructureViewElement(psiFile)) + when (command.name) { + in CommandMagic.sectionMarkers -> { + addSections(command, sections, treeElements, numbering) } - else if (LatexFileType == psiFile.fileType || StyleFileType == psiFile.fileType) { - elt.addChild(LatexStructureViewElement(psiFile)) + in labelingCommands + CommandMagic.commandDefinitionsAndRedefinitions + setOf(LatexGenericRegularCommand.BIBITEM.cmd) -> { + addAtCurrentSectionLevel(sections, treeElements, newElement) + } + in includeCommands -> { + for (psiFile in command.getIncludedFiles(includeInstalledPackages = TexifySettings.getInstance().showPackagesInStructureView)) { + if (BibtexFileType == psiFile.fileType) { + newElement.addChild(BibtexStructureViewElement(psiFile)) + } + else if (LatexFileType == psiFile.fileType || StyleFileType == psiFile.fileType) { + newElement.addChild(LatexStructureViewElement(psiFile)) + } + } + + addAtCurrentSectionLevel(sections, treeElements, newElement) } } - treeElements.add(elt) } + return treeElements.sortedBy { it.value.textOffset }.toTypedArray() } - private fun addFromCommand( - treeElements: MutableList, commands: List, - commandName: String + /** + * Add to top level or at the current sectioning level, so that all entries in the structure view are in the same order as they are in the source + */ + private fun addAtCurrentSectionLevel( + sections: MutableList, + treeElements: ArrayList, + newElement: LatexStructureViewCommandElement ) { - for (cmd in commands) { - if (cmd.commandToken.text != commandName) continue - val element = LatexStructureViewCommandElement.newCommand(cmd) ?: continue - treeElements.add(element) + if (sections.isNotEmpty()) { + sections.last().addChild(newElement) + } + else { + treeElements.add(newElement) } } - private fun addFromLabelingCommands(treeElements: MutableList, commands: List) { - val labelingCommands = getLabelDefinitionCommands() - commands.filter { labelingCommands.contains(it.commandToken.text) } - .mapNotNull { LatexStructureViewCommandElement.newCommand(it) } - .forEach { - treeElements.add(it) - } + private fun LatexStructureViewElement.addSections( + command: LatexCommands, + sections: MutableList, + treeElements: ArrayList, + numbering: SectionNumbering + ) { + if (command.getRequiredParameters().isEmpty()) { + return + } + + val child = LatexStructureViewCommandElement.newCommand(command) ?: return + + // First section. + if (sections.isEmpty()) { + sections.add(child) + treeElements.add(child) + setLevelHint(child, numbering) + return + } + + // Order of the most recently added element, which is kept at the end of the list for administrative purposes + val currentIndex = order(sections.lastOrNull() ?: return) + val nextIndex = order(command) + + when { + currentIndex == nextIndex -> registerSameLevel(sections, child, command, treeElements, numbering) + nextIndex > currentIndex -> registerDeeper(sections, child, numbering) + else -> registerHigher(sections, child, command, treeElements, numbering) + } } private fun registerHigher( - sections: Deque, + sections: MutableList, child: LatexStructureViewCommandElement, currentCmd: LatexCommands, - treeElements: MutableList, + treeElements: MutableList, numbering: SectionNumbering ) { - val indexInsert = order(currentCmd) - while (!sections.isEmpty()) { - pop(sections) - val index = current(sections)?.let { order(it) } + val currentOrder = order(currentCmd) + while (sections.isNotEmpty()) { + // The last entry is the most recently added element (as a child somewhere), remove it first + sections.removeLastOrNull() + val highestLevelOrder = sections.lastOrNull()?.let { order(it) } - if (index != null && indexInsert > index) { + if (highestLevelOrder != null && currentOrder > highestLevelOrder) { registerDeeper(sections, child, numbering) break } // Avoid that an element is not added at all by adding it one level up anyway. // If index is null, that means that the tree currently only has elements with a higher order. - else if (index == null || indexInsert == index) { + else if (highestLevelOrder == null || currentOrder == highestLevelOrder) { registerSameLevel(sections, child, currentCmd, treeElements, numbering) break } @@ -203,31 +194,32 @@ class LatexStructureViewElement(private val element: PsiElement) : StructureView } private fun registerDeeper( - sections: Deque, + sections: MutableList, child: LatexStructureViewCommandElement, numbering: SectionNumbering ) { - current(sections)?.addChild(child) ?: return - queue(child, sections) + sections.lastOrNull()?.addChild(child) ?: return + sections.add(child) setLevelHint(child, numbering) } private fun registerSameLevel( - sections: Deque, + sections: MutableList, child: LatexStructureViewCommandElement, currentCmd: LatexCommands, - treeElements: MutableList, + treeElements: MutableList, numbering: SectionNumbering ) { - sections.pollFirst() - val parent = sections.peekFirst() + // The last entry is the most recently added element (as a child somewhere), remove it first + sections.removeLastOrNull() + val parent = sections.lastOrNull() parent?.addChild(child) - sections.addFirst(child) + sections.add(child) setLevelHint(child, numbering) - if (currentCmd.commandToken.text == highestLevel(sections)) { + if (currentCmd.name == sections.minBy { order(it) }.commandName) { treeElements.add(child) } } @@ -273,27 +265,6 @@ class LatexStructureViewElement(private val element: PsiElement) : StructureView .anyMatch { l -> l.elementType == LatexTypes.STAR } } - private fun highestLevel(sections: Deque): String { - return sections.stream() - .map { this.order(it) } - .min { obj, anotherInteger -> obj.compareTo(anotherInteger) } - .map { CommandMagic.sectionMarkers[it] } - .orElse("\\section") - } - - private fun pop(sections: Deque) { - sections.removeFirst() - } - - private fun queue( - child: LatexStructureViewCommandElement, - sections: Deque - ) { - sections.addFirst(child) - } - - private fun current(sections: Deque) = sections.peekFirst() ?: null - private fun order(element: LatexStructureViewCommandElement) = order(element.commandName) private fun order(commands: LatexCommands) = order(commands.commandToken.text) diff --git a/src/nl/hannahsten/texifyidea/structure/latex/SectionNumbering.kt b/src/nl/hannahsten/texifyidea/structure/latex/SectionNumbering.kt index 271317443..5ba236965 100644 --- a/src/nl/hannahsten/texifyidea/structure/latex/SectionNumbering.kt +++ b/src/nl/hannahsten/texifyidea/structure/latex/SectionNumbering.kt @@ -33,8 +33,6 @@ class SectionNumbering(private val documentClass: DocumentClass) { } } - private fun getCounter(level: Int) = counters[level] - fun setCounter(level: Int, amount: Int) { counters[level] = amount } @@ -54,7 +52,7 @@ class SectionNumbering(private val documentClass: DocumentClass) { for (i in documentClass.startIndex..level) { sb.append(delimiter) - sb.append(getCounter(i)) + sb.append(counters[i]) delimiter = "." } diff --git a/test/nl/hannahsten/texifyidea/structure/latex/LatexStructureViewElementTest.kt b/test/nl/hannahsten/texifyidea/structure/latex/LatexStructureViewElementTest.kt index 7a99c64b2..239881ba5 100644 --- a/test/nl/hannahsten/texifyidea/structure/latex/LatexStructureViewElementTest.kt +++ b/test/nl/hannahsten/texifyidea/structure/latex/LatexStructureViewElementTest.kt @@ -57,4 +57,30 @@ class LatexStructureViewElementTest : BasePlatformTestCase() { ) } } + + fun `test labels are added in the correct place`() { + myFixture.configureByText( + LatexFileType, + """ + \section{s} + \subsection{ss1} + + \label{l} + + \subsection{ss2} + """.trimIndent() + ) + + myFixture.testStructureView { component -> + val documentChildren = (component.treeModel as LatexStructureViewModel).root.children + assertEquals( + listOf("\\section{s}"), + documentChildren.map { (it as LatexStructureViewCommandElement).value.text } + ) + assertEquals( + listOf("\\label{l}"), + documentChildren.filterIsInstance().first().children.first().children.map { (it as LatexStructureViewCommandElement).value.text } + ) + } + } } \ No newline at end of file