Skip to content

Commit

Permalink
Merge branch 'master' into declare-graphics-extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
PHPirates authored Dec 15, 2024
2 parents 76c3ded + 99c20e4 commit f8959d3
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added
* 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).
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
@@ -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 @@ -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
}
}
}
}
1 change: 1 addition & 0 deletions src/nl/hannahsten/texifyidea/util/magic/CommandMagic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ object CommandMagic {

/**
* All commands that define a glossary entry of the glossaries package (e.g. \newacronym).
* When adding a command, define how to get the glossary name in [nl.hannahsten.texifyidea.lang.commands.LatexGlossariesCommand.extractGlossaryName].
*/
val glossaryEntry =
hashSetOf(NEWGLOSSARYENTRY, LONGNEWGLOSSARYENTRY, NEWACRONYM, NEWABBREVIATION).map { it.cmd }.toSet()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package nl.hannahsten.texifyidea.inspections.latex.typesetting

import nl.hannahsten.texifyidea.file.LatexFileType
import nl.hannahsten.texifyidea.inspections.TexifyInspectionTestBase

class LatexMissingGlossaryReferenceInspectionTest : TexifyInspectionTestBase(LatexMissingGlossaryReferenceInspection()) {

fun testMissingReference() {
myFixture.configureByText(LatexFileType, """\newglossaryentry{sample}{name={sample},description={an example}} \gls{sample} \Glslink{sample} <warning descr="Missing glossary reference">sample</warning>""")
myFixture.checkHighlighting()
}

fun testAddGls() {
testQuickFix(
"""
\newglossaryentry{sample}{name={sample},description={an example}} \gls{sample} sample text
""".trimIndent(),
"""
\newglossaryentry{sample}{name={sample},description={an example}} \gls{sample} \gls{sample} text
""".trimIndent()
)
}
}

0 comments on commit f8959d3

Please sign in to comment.