Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for automatic detection of custom command aliases for include commands #3812

Merged
merged 5 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
### Added
* Change order in structure view to match source file and sectioning level
* Add command redefinitions to command definition filter in structure view
* Add support for automatic language injection on the minted environment
* Add support for automatic detection of custom command aliases for include commands

### Fixed

## [0.9.10-alpha.3] - 2024-12-15

### Added

* Add support for automatic language injection on the minted environment
* Add support for DeclareGraphicsExtensions
* Add inspection to warn about a missing reference for a glossary occurrence
* Do not fold sections in a command definition
Expand Down
4 changes: 2 additions & 2 deletions Writerside/topics/Editing-a-LaTeX-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ See [https://blog.codinghorror.com/the-problem-with-code-folding/](https://blog.

_Since b0.7_

TeXiFy supports custom definitions of `label`-like, `\ref`-like and `\cite`-like commands.
TeXiFy supports custom definitions (aliases) of `label`-like, `\ref`-like, `\cite`-like and `input`-like commands.
For example, if you write

<!-- ```latex -->
Expand All @@ -259,7 +259,7 @@ For example, if you write

For definitions like `\newcommand{\mycite}[1]{\citeauthor{#1}\cite{#1}}`, this means that you will also get autocompletion of citation labels in `\mycite` commands.

In the case of definitions including a `\label` command, we check the parameter positions as well.
In the case of definitions including a `\label` or any command that has a file parameter, we check the parameter positions as well.
For example,

<!-- ```latex -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.psi.LatexRequiredParamContent
import nl.hannahsten.texifyidea.reference.InputFileReference
import nl.hannahsten.texifyidea.util.Log
import nl.hannahsten.texifyidea.util.getOriginalCommandFromAlias
import nl.hannahsten.texifyidea.util.parser.parentOfType
import nl.hannahsten.texifyidea.util.parser.requiredParameters
import javax.swing.Icon
Expand All @@ -36,9 +37,9 @@ class LatexNavigationGutter : RelatedItemLineMarkerProvider() {
val fullCommand = command.name ?: return

// Fetch the corresponding LatexRegularCommand object.
val commandHuh = LatexCommand.lookup(fullCommand.substring(1)) ?: return
val commandHuh = LatexCommand.lookup(fullCommand.substring(1))?.first() ?: getOriginalCommandFromAlias(command.name ?: return, command.project) ?: return

val arguments = commandHuh.first().getArgumentsOf(RequiredFileArgument::class.java)
val arguments = commandHuh.getArgumentsOf(RequiredFileArgument::class.java)
if (arguments.isEmpty()) {
return
}
Expand Down
4 changes: 2 additions & 2 deletions src/nl/hannahsten/texifyidea/highlighting/LatexAnnotator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import nl.hannahsten.texifyidea.lang.commands.LatexGenericMathCommand.*
import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand
import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand.*
import nl.hannahsten.texifyidea.psi.*
import nl.hannahsten.texifyidea.util.labels.getLabelDefinitionCommands
import nl.hannahsten.texifyidea.util.labels.getLabelDefinitionCommandsNoUpdate
import nl.hannahsten.texifyidea.util.magic.CommandMagic
import nl.hannahsten.texifyidea.util.magic.cmd
import nl.hannahsten.texifyidea.util.parser.*
Expand Down Expand Up @@ -221,7 +221,7 @@ open class LatexAnnotator : Annotator {
LatexSyntaxHighlighter.LABEL_REFERENCE
}
// Label definitions.
in getLabelDefinitionCommands() -> {
in getLabelDefinitionCommandsNoUpdate() -> {
LatexSyntaxHighlighter.LABEL_DEFINITION
}
// Bibliography references (citations).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import nl.hannahsten.texifyidea.index.*
import nl.hannahsten.texifyidea.index.file.LatexIndexableSetContributor
import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.psi.impl.LatexCommandsImpl
import nl.hannahsten.texifyidea.util.getIncludeCommands
import nl.hannahsten.texifyidea.util.defaultIncludeCommands
import nl.hannahsten.texifyidea.util.magic.CommandMagic
import nl.hannahsten.texifyidea.util.parser.toStringMap
import java.io.IOException
Expand All @@ -33,8 +33,8 @@ class LatexCommandsStubElementType(debugName: String) :
return latexCommandsStub
}

override fun setName(name: String): PsiElement {
this.name = name
override fun setName(newName: String): PsiElement {
this.name = newName
return this
}

Expand Down Expand Up @@ -107,7 +107,7 @@ class LatexCommandsStubElementType(debugName: String) :

val token = latexCommandsStub.commandToken
indexSinkOccurrence(indexSink, LatexCommandsIndex.Util, token)
if (token in getIncludeCommands()) {
if (token in defaultIncludeCommands) {
indexSinkOccurrence(indexSink, LatexIncludesIndex.Util, token)
}
if (token in CommandMagic.definitions) {
Expand Down
11 changes: 6 additions & 5 deletions src/nl/hannahsten/texifyidea/lang/alias/AliasManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,15 @@ abstract class AliasManager {
* @param alias
* The alias to register for the item. This could be either
* a new item, or an existing item *E.g. `\start`*
* @param isRedefinition If the alias is being redefined, remove the original definition
*/
@Synchronized
fun registerAlias(item: String, alias: String) {
fun registerAlias(item: String, alias: String, isRedefinition: Boolean = false) {
synchronized(aliases) {
val aliasSet = aliases[item] ?: mutableSetOf()

// If the alias is already assigned: unassign it.
if (isRegistered(alias)) {
// If the alias is already assigned and we are redefining it: unassign it.
if (isRedefinition && isRegistered(alias)) {
val previousAliases = aliases[alias]
previousAliases?.remove(alias)
aliases.remove(alias)
Expand Down Expand Up @@ -139,8 +140,8 @@ abstract class AliasManager {

// Check if something has changed (the number of indexed command might be the same while the content is different), and if so, update the aliases.
// Also do this the first time something is registered, because then we have to update aliases as well
val hasNotChanged = this.indexedCommandDefinitions == indexedCommandDefinitions
if (!hasNotChanged || wasRegistered) {
val hasChanged = this.indexedCommandDefinitions != indexedCommandDefinitions
if (hasChanged || wasRegistered) {
// Update everything, since it is difficult to know beforehand what aliases could be added or not
// Alternatively we could save a numberOfIndexedCommandDefinitions per alias set, and only update the
// requested alias set (otherwise only the first alias set requesting an update will get it)
Expand Down
4 changes: 2 additions & 2 deletions src/nl/hannahsten/texifyidea/lang/alias/CommandManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ object CommandManager : Iterable<String?>, Serializable, AliasManager() {
* When the given command already exixts.
*/
@Throws(IllegalArgumentException::class)
fun registerAliasNoSlash(commandNoSlash: String, aliasNoSlash: String) {
registerAlias("\\" + commandNoSlash, "\\" + aliasNoSlash)
fun registerAliasNoSlash(commandNoSlash: String, aliasNoSlash: String, isRedefinition: Boolean = false) {
registerAlias("\\" + commandNoSlash, "\\" + aliasNoSlash, isRedefinition)
}

override fun findAllAliases(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.intellij.ide.util.treeView.smartTree.Filter
import com.intellij.ide.util.treeView.smartTree.TreeElement
import nl.hannahsten.texifyidea.TexifyIcons
import nl.hannahsten.texifyidea.structure.latex.LatexStructureViewCommandElement
import nl.hannahsten.texifyidea.util.getIncludeCommands
import nl.hannahsten.texifyidea.util.updateAndGetIncludeCommands

/**
* @author Hannah Schellekens
Expand All @@ -16,7 +16,7 @@ class IncludesFilter : Filter {
return if (treeElement !is LatexStructureViewCommandElement) {
true
}
else treeElement.commandName !in getIncludeCommands()
else treeElement.commandName !in updateAndGetIncludeCommands(treeElement.value.project)
}

override fun isReverted() = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.intellij.ide.util.treeView.smartTree.Filter
import com.intellij.ide.util.treeView.smartTree.TreeElement
import nl.hannahsten.texifyidea.TexifyIcons
import nl.hannahsten.texifyidea.structure.latex.LatexStructureViewCommandElement
import nl.hannahsten.texifyidea.util.labels.getLabelDefinitionCommands
import nl.hannahsten.texifyidea.util.labels.getLabelDefinitionCommandsNoUpdate
import javax.swing.Icon

/**
Expand All @@ -17,7 +17,7 @@ class LabelFilter : Filter {
if (treeElement !is LatexStructureViewCommandElement) {
return true
}
return !getLabelDefinitionCommands().contains(treeElement.commandName)
return !getLabelDefinitionCommandsNoUpdate().contains(treeElement.commandName)
}

override fun isReverted(): Boolean = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package nl.hannahsten.texifyidea.structure.latex
import com.intellij.navigation.ItemPresentation
import nl.hannahsten.texifyidea.TexifyIcons
import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.util.getIncludeCommands
import nl.hannahsten.texifyidea.util.parser.getIncludedFiles
import nl.hannahsten.texifyidea.util.updateAndGetIncludeCommands

/**
* @author Hannah Schellekens
Expand All @@ -14,7 +14,7 @@ class LatexIncludePresentation(labelCommand: LatexCommands) : ItemPresentation {
private val fileName: String

init {
if (labelCommand.name !in getIncludeCommands()) {
if (labelCommand.name !in updateAndGetIncludeCommands(labelCommand.project)) {
throw IllegalArgumentException("Command $labelCommand is no include command")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager
import nl.hannahsten.texifyidea.TexifyIcons
import nl.hannahsten.texifyidea.lang.alias.CommandManager
import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.util.labels.getLabelDefinitionCommands
import nl.hannahsten.texifyidea.util.labels.getLabelDefinitionCommandsNoUpdate
import nl.hannahsten.texifyidea.util.parser.requiredParameter

/**
Expand All @@ -17,7 +17,7 @@ class LatexLabelPresentation(labelCommand: LatexCommands) : ItemPresentation {
private val presentableText: String

init {
val labelingCommands = getLabelDefinitionCommands()
val labelingCommands = getLabelDefinitionCommandsNoUpdate()
if (!labelingCommands.contains(labelCommand.commandToken.text)) {
val token = labelCommand.commandToken.text
throw IllegalArgumentException("command '$token' is no \\label-command")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import nl.hannahsten.texifyidea.lang.commands.LatexNewDefinitionCommand.NEWCOMMA
import nl.hannahsten.texifyidea.lang.commands.LatexNewDefinitionCommand.RENEWCOMMAND
import nl.hannahsten.texifyidea.lang.commands.LatexXparseCommand
import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.util.getIncludeCommands
import nl.hannahsten.texifyidea.util.labels.getLabelDefinitionCommands
import nl.hannahsten.texifyidea.util.magic.cmd
import nl.hannahsten.texifyidea.util.updateAndGetIncludeCommands

/**
* @author Hannah Schellekens
Expand All @@ -37,7 +37,7 @@ object LatexPresentationFactory {
)
LABEL.cmd -> LatexLabelPresentation(commands)
BIBITEM.cmd -> BibitemPresentation(commands)
in getIncludeCommands() -> LatexIncludePresentation(commands)
in updateAndGetIncludeCommands(commands.project) -> LatexIncludePresentation(commands)
else -> LatexOtherCommandPresentation(commands, TexifyIcons.DOT_COMMAND)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import nl.hannahsten.texifyidea.psi.LatexTypes
import nl.hannahsten.texifyidea.settings.TexifySettings
import nl.hannahsten.texifyidea.structure.bibtex.BibtexStructureViewElement
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.labels.getLabelDefinitionCommandsNoUpdate
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 nl.hannahsten.texifyidea.util.updateAndGetIncludeCommands
import java.util.*

/**
Expand Down Expand Up @@ -82,8 +82,8 @@ class LatexStructureViewElement(private val element: PsiElement) : StructureView
val commands = element.allCommands()
val treeElements = ArrayList<LatexStructureViewCommandElement>()

val includeCommands = getIncludeCommands()
val labelingCommands = getLabelDefinitionCommands()
val includeCommands = updateAndGetIncludeCommands(element.project)
val labelingCommands = getLabelDefinitionCommandsNoUpdate()

// Add sectioning.
val sections = mutableListOf<LatexStructureViewCommandElement>()
Expand Down
48 changes: 48 additions & 0 deletions src/nl/hannahsten/texifyidea/util/CommandAlias.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package nl.hannahsten.texifyidea.util

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.project.Project
import nl.hannahsten.texifyidea.lang.alias.CommandManager
import nl.hannahsten.texifyidea.lang.commands.LatexCommand
import java.util.concurrent.atomic.AtomicBoolean

// Due to the update method being called many times, we need to limit the number of updates requested
var isUpdatingIncludeAliases = AtomicBoolean(false)

fun updateAndGetIncludeCommands(project: Project): Set<String> {
// For performance reasons, do not wait until the update (which requires index access) is done
updateIncludeCommandsAliasesAsync(project)
return defaultIncludeCommands.map { CommandManager.getAliases(it) }.flatten().toSet()
}

fun updateIncludeCommandsAliasesAsync(project: Project) {
if (!isUpdatingIncludeAliases.getAndSet(true)) {
// Don't run with progress indicator, because this takes a short time (a few tenths) and runs in practice on every letter typed
ApplicationManager.getApplication().invokeLater {
try {
// Because every command has different parameters and behaviour (e.g. allowed file types), we keep track of them separately
for (command in defaultIncludeCommands) {
runReadAction {
CommandManager.updateAliases(setOf(command), project)
}
}
}
finally {
isUpdatingIncludeAliases.set(false)
}
}
}
}

/**
* Given a possible alias of an include command, find a random original command it is an alias of
*/
fun getOriginalCommandFromAlias(commandName: String, project: Project): LatexCommand? {
val aliasSet = CommandManager.getAliases(commandName)
if (aliasSet.isEmpty()) {
// If we can't find anything, trigger an update so that maybe we have the information next time
updateIncludeCommandsAliasesAsync(project)
}
return LatexCommand.lookup(aliasSet.firstOrNull { it in defaultIncludeCommands })?.first()
}
15 changes: 8 additions & 7 deletions src/nl/hannahsten/texifyidea/util/Commands.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ fun Project.findCommandDefinitions(): Collection<LatexCommands> {
}

/**
* Get all commands that include other files, including backslashes.
* Get all commands that include other files, including backslashes. Use [updateAndGetIncludeCommands] to include custom commands, if possible.
*/
fun getIncludeCommands(): Set<String> {
return LatexRegularCommand.values()
.filter { command -> command.arguments.any { it is RequiredFileArgument } }
.map { "\\" + it.command }
.toSet()
}
val defaultIncludeCommands: Set<String>
by lazy {
LatexRegularCommand.values()
.filter { command -> command.arguments.any { it is RequiredFileArgument } }
.map { "\\" + it.command }
.toSet()
}

/**
* Inserts a custom c custom command definition.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fun Project.getLabelReferenceCommands(): Set<String> {
/**
* Get all commands defining labels, including user defined commands. This will not check if the aliases need to be updated.
*/
fun getLabelDefinitionCommands() = CommandManager.getAliases(CommandMagic.labelDefinitionsWithoutCustomCommands.first())
fun getLabelDefinitionCommandsNoUpdate() = CommandManager.getAliases(CommandMagic.labelDefinitionsWithoutCustomCommands.first())

/**
* Get all commands defining labels, including user defined commands.
Expand Down
21 changes: 1 addition & 20 deletions src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

package nl.hannahsten.texifyidea.util.magic

import com.intellij.openapi.project.Project
import com.intellij.ui.Gray
import nl.hannahsten.texifyidea.lang.alias.CommandManager
import nl.hannahsten.texifyidea.lang.commands.Argument
import nl.hannahsten.texifyidea.lang.commands.LatexBiblatexCommand.*
import nl.hannahsten.texifyidea.lang.commands.LatexCommand
Expand Down Expand Up @@ -220,27 +218,10 @@ object CommandMagic {

/**
* All commands that define labels and that are present by default.
* To include user defined commands, use [getLabelDefinitionCommands] (may be significantly slower).
* To include user defined commands, use [nl.hannahsten.texifyidea.util.labels.getLabelDefinitionCommands] (may be significantly slower).
*/
val labelDefinitionsWithoutCustomCommands = setOf(LABEL.cmd)

/**
* Get all commands defining labels, including user defined commands.
* If you need to know which parameters of user defined commands define a label, use [CommandManager.labelAliasesInfo].
*
* This will check if the cache of user defined commands needs to be updated, based on the given project, and therefore may take some time.
*/
fun getLabelDefinitionCommands(project: Project): Set<String> {
// Check if updates are needed
CommandManager.updateAliases(labelDefinitionsWithoutCustomCommands, project)
return CommandManager.getAliases(labelDefinitionsWithoutCustomCommands.first())
}

/**
* Get all commands defining labels, including user defined commands. This will not check if the aliases need to be updated.
*/
fun getLabelDefinitionCommands() = CommandManager.getAliases(labelDefinitionsWithoutCustomCommands.first())

/**
* All commands that define bibliography items.
*/
Expand Down
Loading
Loading