Skip to content

Commit

Permalink
Merge pull request #3811 from Hannah-Sten/structure-view
Browse files Browse the repository at this point in the history
Improve structure view ordering and filtering
  • Loading branch information
PHPirates authored Dec 18, 2024
2 parents ca10d91 + 01f3bbe commit 64d6cc0
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 121 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -79,155 +80,146 @@ 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<TreeElement>()
val treeElements = ArrayList<LatexStructureViewCommandElement>()

// Add includes.
addIncludes(treeElements, commands)
val includeCommands = getIncludeCommands()
val labelingCommands = getLabelDefinitionCommands()

// Add sectioning.
val sections = ArrayDeque<LatexStructureViewCommandElement>()
for (currentCmd in commands) {
val token = currentCmd.name

val sections = mutableListOf<LatexStructureViewCommandElement>()
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<TreeElement>, commands: List<LatexCommands>) {
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<TreeElement>, commands: List<LatexCommands>,
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<LatexStructureViewCommandElement>,
treeElements: ArrayList<LatexStructureViewCommandElement>,
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<TreeElement>, commands: List<LatexCommands>) {
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<LatexStructureViewCommandElement>,
treeElements: ArrayList<LatexStructureViewCommandElement>,
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<LatexStructureViewCommandElement>,
sections: MutableList<LatexStructureViewCommandElement>,
child: LatexStructureViewCommandElement,
currentCmd: LatexCommands,
treeElements: MutableList<TreeElement>,
treeElements: MutableList<LatexStructureViewCommandElement>,
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
}
}
}

private fun registerDeeper(
sections: Deque<LatexStructureViewCommandElement>,
sections: MutableList<LatexStructureViewCommandElement>,
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<LatexStructureViewCommandElement>,
sections: MutableList<LatexStructureViewCommandElement>,
child: LatexStructureViewCommandElement,
currentCmd: LatexCommands,
treeElements: MutableList<TreeElement>,
treeElements: MutableList<LatexStructureViewCommandElement>,
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)
}
}
Expand Down Expand Up @@ -273,27 +265,6 @@ class LatexStructureViewElement(private val element: PsiElement) : StructureView
.anyMatch { l -> l.elementType == LatexTypes.STAR }
}

private fun highestLevel(sections: Deque<LatexStructureViewCommandElement>): 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<LatexStructureViewCommandElement>) {
sections.removeFirst()
}

private fun queue(
child: LatexStructureViewCommandElement,
sections: Deque<LatexStructureViewCommandElement>
) {
sections.addFirst(child)
}

private fun current(sections: Deque<LatexStructureViewCommandElement>) = sections.peekFirst() ?: null

private fun order(element: LatexStructureViewCommandElement) = order(element.commandName)

private fun order(commands: LatexCommands) = order(commands.commandToken.text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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 = "."
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<LatexStructureViewCommandElement>().first().children.first().children.map { (it as LatexStructureViewCommandElement).value.text }
)
}
}
}

0 comments on commit 64d6cc0

Please sign in to comment.