Skip to content

Commit

Permalink
Merge branch 'master' into minted-env
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
  • Loading branch information
PHPirates committed Dec 15, 2024
2 parents 512e44d + e536848 commit 3cef82a
Show file tree
Hide file tree
Showing 25 changed files with 264 additions and 74 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### 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
* Include optional parameters in spellcheck, if it contains text

Expand Down
8 changes: 7 additions & 1 deletion Writerside/topics/Typesetting-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ For example, instead of `(\frac 1 2)` write `\left(\frac 1 2\right)`.

## Citations must be placed before interpunction

Use `Sentence~\cite{knuth1990}.` and not `Sentence.~\cite{knuth1990}`
Use `Sentence~\cite{knuth1990}.` and not `Sentence.~\cite{knuth1990}`

## Missing glossary reference

When using a glossary, it is good practice to reference every glossary entry with a \gls-like command.
This makes sure that the list of pages with occurrences in the glossary is complete.
For examples on how to use a glossary, see [External tools](External-tools.md#glossary-examples).
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ dependencies {
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.Plugin.Java)

intellijIdeaCommunity("2024.2.3")
intellijIdeaCommunity("2024.3.1")

// Docs: https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties
// All snapshot versions: https://www.jetbrains.com/intellij-repository/snapshots/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,9 @@
groupPath="LaTeX" groupName="Typesetting issues" displayName="Incorrectly typeset quotation marks"
enabledByDefault="true"
level="WARNING" />
<localInspection language="Latex" implementationClass="nl.hannahsten.texifyidea.inspections.latex.typesetting.LatexMissingGlossaryReferenceInspection"
groupPath="LaTeX" groupName="Code maturity" displayName="Missing glossary reference"
enabledByDefault="true"
level="WARNING" />
</extensions>
</idea-plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<body>
Reports occurrences of glossary entries that are not marked with a \gls command.
<!-- tooltip end -->
<p>
When using a glossary, it is good practice to reference every glossary entry with a \gls-like command.
This makes sure that the list of pages with occurrences in the glossary is complete.
</p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nl.hannahsten.texifyidea.index.file

import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task.Backgroundable
Expand All @@ -17,6 +18,7 @@ import nl.hannahsten.texifyidea.util.getTexinputsPaths
import nl.hannahsten.texifyidea.util.isTestProject
import nl.hannahsten.texifyidea.util.magic.CommandMagic
import nl.hannahsten.texifyidea.util.parser.requiredParameter
import nl.hannahsten.texifyidea.util.runInBackground
import org.codehaus.plexus.archiver.ArchiverException
import org.codehaus.plexus.archiver.tar.TarBZip2UnArchiver
import org.codehaus.plexus.archiver.tar.TarXZUnArchiver
Expand Down Expand Up @@ -73,31 +75,25 @@ class LatexIndexableSetContributor : IndexableSetContributor() {
roots.addAll(getTexinputsPaths(project, rootFiles = listOf(), expandPaths = false).mapNotNull { LocalFileSystem.getInstance().findFileByPath(it) })

// Using the index while building it may be problematic, cache the result and hope it doesn't create too much trouble
if (Cache.externalDirectFileInclusions == null) {
if (!DumbService.isDumb(project)) {
try {
// For now, just do this for bibliography and direct input commands, as there this is most common
val externalFiles = LatexIncludesIndex.Util.getCommandsByNames(CommandMagic.includeOnlyExtensions.entries.filter { it.value.contains("bib") || it.value.contains("tex") }.map { it.key }.toSet(), project, GlobalSearchScope.projectScope(project))
// We can't add single files, so take the parent
.mapNotNull {
val path = it.requiredParameter(0) ?: return@mapNotNull null
if (File(path).isAbsolute) {
LocalFileSystem.getInstance().findFileByPath(path)?.parent
}
else {
it.containingFile.parent?.virtualFile?.findFileByRelativePath(path)?.parent
}
if (Cache.externalDirectFileInclusions == null && !DumbService.isDumb(project)) {
runInBackground(project, "Searching for external bib files...") {
// For now, just do this for bibliography and direct input commands, as there this is most common
val commandNames = CommandMagic.includeOnlyExtensions.entries.filter { it.value.contains("bib") || it.value.contains("tex") }.map { it.key }.toSet()
val externalFiles = runReadAction {
LatexIncludesIndex.Util.getCommandsByNames(commandNames, project, GlobalSearchScope.projectScope(project))
}
// We can't add single files, so take the parent
.mapNotNull {
val path = runReadAction { it.requiredParameter(0) } ?: return@mapNotNull null
val file = if (File(path).isAbsolute) {
LocalFileSystem.getInstance().findFileByPath(path)
}
Cache.externalDirectFileInclusions = externalFiles.toSet()
} catch (e: Throwable) {
// This is very rare, but it can happen, in which case we will ignore and try again later
if (e.message?.contains("Indexing process should not rely on non-indexed file data") == true) {
Log.warn("Ignored index not ready: " + e.message)
}
else {
throw e
else {
runReadAction { it.containingFile.parent }?.virtualFile?.findFileByRelativePath(path)
}
runReadAction { file?.parent }
}
}
Cache.externalDirectFileInclusions = externalFiles.toSet()
}
}
roots.addAll(Cache.externalDirectFileInclusions?.filter { it.exists() } ?: emptyList())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ open class LatexFileNotFoundInspection : TexifyInspectionBase() {

// Find expected extension
val extension = fileName.getFileExtension().ifEmpty {
reference.defaultExtension
}
reference.extensions.firstOrNull()
} ?: "tex"

descriptors.add(
manager.createProblemDescriptor(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package nl.hannahsten.texifyidea.inspections.latex.typesetting

import com.intellij.codeInspection.InspectionManager
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import nl.hannahsten.texifyidea.index.LatexGlossaryEntryIndex
import nl.hannahsten.texifyidea.inspections.InsightGroup
import nl.hannahsten.texifyidea.inspections.TexifyInspectionBase
import nl.hannahsten.texifyidea.lang.commands.LatexGlossariesCommand
import nl.hannahsten.texifyidea.psi.LatexNormalText
import nl.hannahsten.texifyidea.psi.LatexPsiHelper
import nl.hannahsten.texifyidea.util.parser.childrenOfType
import nl.hannahsten.texifyidea.util.toTextRange

/**
* Glossary entries should be referenced for all occurrences.
*/
class LatexMissingGlossaryReferenceInspection : TexifyInspectionBase() {
override val inspectionGroup = InsightGroup.LATEX
override val inspectionId = "MissingGlossaryReference"
override fun getDisplayName() = "Missing glossary reference"

override fun inspectFile(file: PsiFile, manager: InspectionManager, isOntheFly: Boolean): List<ProblemDescriptor> {
val descriptors = mutableListOf<ProblemDescriptor>()
val names = LatexGlossaryEntryIndex.Util.getItemsInFileSet(file).mapNotNull { LatexGlossariesCommand.extractGlossaryName(it) }
// Unfortunately the lowest level we have is a block of text, so we have to do a text-based search
file.childrenOfType<LatexNormalText>().forEach { textElement ->
val text = textElement.text
names.forEach { name ->
val correctOccurrences = "\\\\gls[^{]+\\{($name)}".toRegex().findAll(text).mapNotNull { it.groups.firstOrNull()?.range }
val allOccurrences = name.toRegex().findAll(text).map { it.range }
allOccurrences.filter { !correctOccurrences.contains(it) }.forEach { range ->
descriptors.add(
manager.createProblemDescriptor(
textElement,
range.toTextRange(),
"Missing glossary reference",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
isOntheFly,
AddGlsFix(),
)
)
}
}
}
return descriptors
}

private class AddGlsFix : LocalQuickFix {
override fun getFamilyName() = "Add \\gls command"

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val range = descriptor.textRangeInElement
val newText = descriptor.psiElement.text.replaceRange(range.endOffset, range.endOffset, "}")
.replaceRange(range.startOffset, range.startOffset, "\\gls{")

val newElement = LatexPsiHelper(project).createFromText(newText).firstChild
descriptor.psiElement.replace(newElement)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ enum class LatexGenericRegularCommand(
TEXT_DAGGER("dag", display = ""),
TEXT_DOUBLE_DAGGER("ddag", display = ""),
DATE("date", "text".asRequired(Argument.Type.TEXT)),
DECLAREGRAPHICSEXTENSIONS("DeclareGraphicsExtensions", "extensions".asRequired(), dependency = GRAPHICX),
DECLARE_MATH_OPERATOR("DeclareMathOperator", "command".asRequired(), "operator".asRequired(Argument.Type.TEXT), dependency = AMSMATH),
DEF("def"),
DOCUMENTCLASS("documentclass", "options".asOptional(), RequiredFileArgument("class", true, false, "cls")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import nl.hannahsten.texifyidea.lang.LatexPackage
import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.psi.LatexParameterText
import nl.hannahsten.texifyidea.util.magic.CommandMagic
import nl.hannahsten.texifyidea.util.magic.cmd
import nl.hannahsten.texifyidea.util.parser.firstChildOfType
import nl.hannahsten.texifyidea.util.parser.requiredParameter
import nl.hannahsten.texifyidea.util.parser.requiredParameters

enum class LatexGlossariesCommand(
Expand Down Expand Up @@ -80,5 +82,21 @@ enum class LatexGlossariesCommand(
if (!CommandMagic.glossaryEntry.contains(command.name)) return null
return command.requiredParameters()[0].firstChildOfType(LatexParameterText::class)
}

/**
* Find the name, which is the text that will appear in the document, from the given glossary entry definition.
*/
fun extractGlossaryName(command: LatexCommands): String? {
if (setOf(NEWGLOSSARYENTRY, LONGNEWGLOSSARYENTRY).map { it.cmd }.contains(command.name)) {
val keyValueList = command.requiredParameter(1) ?: return null
return "name=\\{([^}]+)}".toRegex().find(keyValueList)?.groupValues?.get(1)
}
else if (setOf(NEWACRONYM, NEWABBREVIATION).map { it.cmd }.contains(command.name)) {
return command.requiredParameter(1)
}
else {
return null
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import java.util.regex.Pattern
open class RequiredFileArgument(name: String?, open val isAbsolutePathSupported: Boolean = true, open val commaSeparatesArguments: Boolean, vararg extensions: String, open val supportsAnyExtension: Boolean = true) : RequiredArgument(name!!, Type.FILE), FileNameMatcher, FileExtensionMatcher {

lateinit var supportedExtensions: List<String>
lateinit var defaultExtension: String
private set
private var pattern: Pattern? = null

init {
Expand All @@ -44,12 +42,9 @@ open class RequiredFileArgument(name: String?, open val isAbsolutePathSupported:
if (extensions.isEmpty()) {
setRegex(regex.toString())
this.supportedExtensions = supportedExtensions
this.defaultExtension = ""
return
}
else {
defaultExtension = extensions[0]
}

regex.append("(")
for (extension in extensions) {
regex.append("\\.")
Expand Down
28 changes: 25 additions & 3 deletions src/nl/hannahsten/texifyidea/reference/InputFileReference.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,27 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiReferenceBase
import com.intellij.psi.search.GlobalSearchScope
import nl.hannahsten.texifyidea.algorithm.BFS
import nl.hannahsten.texifyidea.completion.pathcompletion.LatexGraphicsPathProvider
import nl.hannahsten.texifyidea.index.LatexCommandsIndex
import nl.hannahsten.texifyidea.lang.LatexPackage
import nl.hannahsten.texifyidea.lang.commands.LatexCommand
import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand
import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.psi.LatexPsiHelper
import nl.hannahsten.texifyidea.util.*
import nl.hannahsten.texifyidea.util.files.*
import nl.hannahsten.texifyidea.util.magic.CommandMagic
import nl.hannahsten.texifyidea.util.parser.requiredParameter

/**
* Reference to a file, based on the command and the range of the filename within the command text.
*
* @param defaultExtension Default extension of the command in which this reference is, in case the argument does not have an extension.
*/
class InputFileReference(
element: LatexCommands,
val range: TextRange,
val extensions: List<String>,
val defaultExtension: String,
val supportsAnyExtension: Boolean,
) : PsiReferenceBase<LatexCommands>(element) {

Expand Down Expand Up @@ -122,6 +124,26 @@ class InputFileReference(
}
}

// Overrides the default for commands from the graphicx package
val extensions = if (!isBuildingFileset) {
val command = LatexCommand.lookup(element.name)?.firstOrNull()
if (command?.dependency == LatexPackage.GRAPHICX) {
// We cannot use the file set at this point, so we take the first command in the project and hope for the best
LatexCommandsIndex.Util.getCommandsByName(LatexGenericRegularCommand.DECLAREGRAPHICSEXTENSIONS.command, element.project, GlobalSearchScope.projectScope(element.project))
.firstOrNull()
?.requiredParameter(0)
?.split(",")
// Graphicx requires the dot to be included
?.map { it.trim(' ', '.') } ?: extensions
}
else {
extensions
}
}
else {
extensions
}

var processedKey = expandCommandsOnce(key, element.project, file = rootFiles.firstOrNull()?.psiFile(element.project)) ?: key
// Leading and trailing whitespaces seem to be ignored, at least it holds for \include-like commands
processedKey = processedKey.trim()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package nl.hannahsten.texifyidea.run.bibtex

import com.intellij.execution.configuration.EnvironmentVariablesComponent
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileChooser.FileTypeDescriptor
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
Expand Down Expand Up @@ -108,7 +108,9 @@ class BibtexSettingsEditor(private val project: Project) : SettingsEditor<Bibtex
val mainFileField = TextFieldWithBrowseButton().apply {
addBrowseFolderListener(
TextBrowseFolderListener(
FileTypeDescriptor("Choose the Main .tex File", "tex")
FileChooserDescriptorFactory.createSingleFileDescriptor()
.withTitle("Choose the Main .tex File")
.withExtensionFilter("tex")
.withRoots(*ProjectRootManager.getInstance(project).contentRootsFromAllModules.toSet().toTypedArray())
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nl.hannahsten.texifyidea.run.latex.externaltool

import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileChooser.FileTypeDescriptor
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
Expand Down Expand Up @@ -53,7 +53,9 @@ class ExternalToolSettingsEditor(private val project: Project) : SettingsEditor<
val mainFileField = TextFieldWithBrowseButton().apply {
addBrowseFolderListener(
TextBrowseFolderListener(
FileTypeDescriptor("Choose Main LaTeX File", "tex")
FileChooserDescriptorFactory.createSingleFileDescriptor()
.withTitle("Choose Main LaTeX File")
.withExtensionFilter("tex")
.withRoots(*ProjectRootManager.getInstance(project).contentRootsFromAllModules.toSet().toTypedArray())
)
)
Expand Down
17 changes: 10 additions & 7 deletions src/nl/hannahsten/texifyidea/run/latex/ui/LatexSettingsEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package nl.hannahsten.texifyidea.run.latex.ui

import com.intellij.execution.configuration.EnvironmentVariablesComponent
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileChooser.FileTypeDescriptor
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.fileTypes.PlainTextFileType
import com.intellij.openapi.options.ConfigurationException
import com.intellij.openapi.options.SettingsEditor
Expand Down Expand Up @@ -33,7 +33,11 @@ import nl.hannahsten.texifyidea.run.sumatra.SumatraAvailabilityChecker
import nl.hannahsten.texifyidea.settings.sdk.LatexSdkUtil
import nl.hannahsten.texifyidea.util.runInBackground
import java.awt.event.ItemEvent
import javax.swing.*
import javax.swing.InputVerifier
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JPanel
import kotlin.Throws

/**
* @author Sten Wessel
Expand Down Expand Up @@ -302,11 +306,10 @@ class LatexSettingsEditor(private var project: Project?) : SettingsEditor<LatexR
val mainFileField = TextFieldWithBrowseButton()
mainFileField.addBrowseFolderListener(
TextBrowseFolderListener(
FileTypeDescriptor("Choose a File to Compile", "tex")
.withRoots(
*ProjectRootManager.getInstance(project!!)
.contentRootsFromAllModules.toSet().toTypedArray()
)
FileChooserDescriptorFactory.createSingleFileDescriptor()
.withTitle("Choose a File to Compile")
.withExtensionFilter("tex")
.withRoots(*ProjectRootManager.getInstance(project!!).contentRootsFromAllModules.toSet().toTypedArray())
)
)
mainFile = LabeledComponent.create(mainFileField, "Main file to compile")
Expand Down
Loading

0 comments on commit 3cef82a

Please sign in to comment.