Skip to content

Commit

Permalink
Merge pull request #3812 from Hannah-Sten/input-alias
Browse files Browse the repository at this point in the history
Add support for automatic detection of custom command aliases for include commands
  • Loading branch information
PHPirates authored Dec 19, 2024
2 parents 64d6cc0 + 81115fc commit b4cdbb5
Show file tree
Hide file tree
Showing 20 changed files with 122 additions and 69 deletions.
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
5 changes: 3 additions & 2 deletions src/nl/hannahsten/texifyidea/gutter/LatexNavigationGutter.kt
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
4 changes: 2 additions & 2 deletions src/nl/hannahsten/texifyidea/structure/filter/LabelFilter.kt
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
2 changes: 1 addition & 1 deletion src/nl/hannahsten/texifyidea/util/labels/ProjectLabels.kt
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

0 comments on commit b4cdbb5

Please sign in to comment.