Skip to content

Commit

Permalink
Merge pull request #3467 from Hannah-Sten/run-config-completion
Browse files Browse the repository at this point in the history
Autocompletion for compiler arguments in run configuration settings
  • Loading branch information
PHPirates authored Feb 28, 2024
2 parents 59cf505 + e5911b9 commit 469221d
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 8 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions Writerside/topics/Run-configuration-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ Select a path to a LaTeX compiler.
Extra arguments to pass to the compiler.
It depends on the compiler which ones are there by default.
For more info, check the implementation at [https://github.com/Hannah-Sten/TeXiFy-IDEA/blob/master/src/nl/hannahsten/texifyidea/run/compiler/LatexCompiler.kt](https://github.com/Hannah-Sten/TeXiFy-IDEA/blob/master/src/nl/hannahsten/texifyidea/run/compiler/LatexCompiler.kt)
This field has autocompletion, where the available options depend on the compiler that was selected when you opened the dialog.

![Compiler autocompletion](run-config-autocomplete.png)

## Environment variables

Expand Down
108 changes: 108 additions & 0 deletions src/nl/hannahsten/texifyidea/run/latex/LatexCommandLineOptionsCache.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package nl.hannahsten.texifyidea.run.latex

import arrow.atomic.AtomicBoolean
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task.Backgroundable
import com.intellij.openapi.project.Project
import nl.hannahsten.texifyidea.run.compiler.LatexCompiler
import nl.hannahsten.texifyidea.util.remove
import nl.hannahsten.texifyidea.util.runCommandWithExitCode
import org.apache.commons.cli.Option
import org.apache.commons.cli.Options

/**
* Automatically get available command line options for all the LaTeX compilers.
*/
object LatexCommandLineOptionsCache {
// Map compiler name to list of (name, description) pairs where name is without the - or -- prefix
val cache = mutableMapOf<String, Options>()

private val isCacheFillInProgress = AtomicBoolean(false)

/**
* Get the options for the given compiler, or fill the cache if it is empty.
* Cache fill is done in the background because it requires system calls so may take significant time.
*/
fun getOptionsOrFillCache(givenCompiler: String, project: Project): Options {
if (cache.isNotEmpty()) {
return cache[givenCompiler] ?: Options()
}

if (isCacheFillInProgress.compareAndSet(expected = true, new = true)) {
return Options()
}
isCacheFillInProgress.getAndSet(true)

fillCache(project)
return Options()
}

private fun getOptions(optionsList: List<Pair<String, String>>): Options {
return Options().apply {
for ((option, description) in optionsList) {
// option is with one - and .longOpt is with two --, but both are possible it seems with pdflatex
addOption(Option.builder().longOpt(option).desc(description).build())
}
}
}

private fun fillCache(project: Project): List<Pair<String, String>> {
ProgressManager.getInstance().run(object : Backgroundable(project, "Retrieving available command line options for LaTeX compilers...") {
override fun run(indicator: ProgressIndicator) {
try {
for (compiler in LatexCompiler.values()) {
val (output, _) = runCommandWithExitCode(compiler.executableName, "--help")
if (output != null) {
val optionsList = parseHelpOutput(compiler.executableName, output)
cache[compiler.executableName] = getOptions(optionsList)
}
else {
cache[compiler.executableName] = Options()
}
}
}
finally {
isCacheFillInProgress.getAndSet(false)
}
}
})

return emptyList()
}

/**
* Parse the output of pdflatex --help to get available compiler options.
* These are slightly different per compiler.
* Tested with pdflatex, lualatex, xelatex and latexmk
*/
fun parseHelpOutput(compiler: String, text: String): List<Pair<String, String>> {
return text.split("\n")
.asSequence()
.map { it.trim(' ').split(if (compiler == "latexmk") " - " else " ") }
.filter { it.size >= 2 }
.map { Pair(it.first(), it.drop(1).joinToString(" ").trim(' ')) }
.flatMap { (option, description) ->
// [-no] for pdflatex, --[no-] for lualatex
if (option.contains("[-no]") || option.contains("[no-]")) {
val cleanedOption = option.remove("[-no]").remove("[no-]").trim('-')
listOf(
Pair(cleanedOption, description),
Pair("no-$cleanedOption", description)
)
}
// latexmk
else if (option.contains(" or ")) {
option.split(" or ").map { singleOption -> Pair(singleOption, description) }
}
else if (option.startsWith("-")) {
listOf(Pair(option, description))
}
else {
emptyList()
}
}
.map { Pair(it.first.trim(' ').trimStart('-'), it.second) }
.toList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package nl.hannahsten.texifyidea.run.latex.ui

import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.openapi.externalSystem.service.execution.cmd.CommandLineCompletionProvider
import org.apache.commons.cli.Options

/**
* Simple way to add autocompletion to a command line editor.
* Based on MavenArgumentsCompletionProvider used in MavenBeforeRunTasksProvider
* Note that there is a similar (and better) solution for fragments, see MavenRunConfigurationSettingsEditor#addCommandLineFragment
*/
class LatexArgumentsCompletionProvider(options: Options) : CommandLineCompletionProvider(options) {

override fun addArgumentVariants(result: CompletionResultSet) {
// Here we can add things to the autocompletion without the - or -- prefix, for example:
// result.addAllElements(listOf("one", "two").map { LookupElementBuilder.create(it) })
}
}
23 changes: 15 additions & 8 deletions src/nl/hannahsten/texifyidea/run/latex/ui/LatexSettingsEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ 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.fileTypes.PlainTextFileType
import com.intellij.openapi.options.ConfigurationException
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.ui.*
import com.intellij.openapi.util.SystemInfo
import com.intellij.ui.RawCommandLineEditor
import com.intellij.ui.EditorTextField
import com.intellij.ui.SeparatorComponent
import com.intellij.ui.TitledSeparator
import com.intellij.ui.components.JBCheckBox
Expand All @@ -18,6 +19,7 @@ import nl.hannahsten.texifyidea.run.bibtex.BibtexRunConfigurationType
import nl.hannahsten.texifyidea.run.compiler.LatexCompiler
import nl.hannahsten.texifyidea.run.compiler.LatexCompiler.Format
import nl.hannahsten.texifyidea.run.compiler.LatexCompiler.PDFLATEX
import nl.hannahsten.texifyidea.run.latex.LatexCommandLineOptionsCache
import nl.hannahsten.texifyidea.run.latex.LatexDistributionType
import nl.hannahsten.texifyidea.run.latex.LatexOutputPath
import nl.hannahsten.texifyidea.run.latex.LatexRunConfiguration
Expand All @@ -40,7 +42,7 @@ class LatexSettingsEditor(private var project: Project?) : SettingsEditor<LatexR
private lateinit var compiler: LabeledComponent<ComboBox<LatexCompiler>>
private lateinit var enableCompilerPath: JBCheckBox
private lateinit var compilerPath: TextFieldWithBrowseButton
private lateinit var compilerArguments: LabeledComponent<RawCommandLineEditor>
private lateinit var compilerArguments: EditorTextField
private lateinit var environmentVariables: EnvironmentVariablesComponent
private lateinit var mainFile: LabeledComponent<ComponentWithBrowseButton<*>>
private lateinit var outputPath: LabeledComponent<ComponentWithBrowseButton<*>>
Expand Down Expand Up @@ -91,7 +93,7 @@ class LatexSettingsEditor(private var project: Project?) : SettingsEditor<LatexR

// Reset compiler arguments
val args = runConfiguration.compilerArguments
compilerArguments.component.text = args ?: ""
compilerArguments.text = args ?: ""

// Reset environment variables
environmentVariables.envData = runConfiguration.environmentVariables
Expand Down Expand Up @@ -193,7 +195,7 @@ class LatexSettingsEditor(private var project: Project?) : SettingsEditor<LatexR
runConfiguration.viewerCommand = if (enableViewerCommand.isSelected) viewerCommand.text else null

// Apply custom compiler arguments
runConfiguration.compilerArguments = compilerArguments.component.text
runConfiguration.compilerArguments = compilerArguments.text

// Apply environment variables
runConfiguration.environmentVariables = environmentVariables.envData
Expand Down Expand Up @@ -266,10 +268,16 @@ class LatexSettingsEditor(private var project: Project?) : SettingsEditor<LatexR
addPdfViewerCommandField(panel)

// Optional custom compiler arguments
val argumentsTitle = "Custom compiler arguments"
val argumentsField = RawCommandLineEditor()
val argumentsLabel = JLabel("Custom compiler arguments")
val argumentsEditor = EditorTextField("", project, PlainTextFileType.INSTANCE)
argumentsLabel.labelFor = argumentsEditor
val selectedCompiler = compiler.component.selectedItem as LatexCompiler
project?.let { project ->
val options = LatexCommandLineOptionsCache.getOptionsOrFillCache(selectedCompiler.executableName, project)
LatexArgumentsCompletionProvider(options).apply(argumentsEditor)
}

compilerArguments = LabeledComponent.create(argumentsField, argumentsTitle)
compilerArguments = argumentsEditor
panel.add(compilerArguments)

environmentVariables = EnvironmentVariablesComponent()
Expand Down Expand Up @@ -298,7 +306,6 @@ class LatexSettingsEditor(private var project: Project?) : SettingsEditor<LatexR
panel.add(compileTwice)

// Output format.
val selectedCompiler = compiler.component.selectedItem as LatexCompiler
val cboxFormat = ComboBox(selectedCompiler.outputFormats)
outputFormat = LabeledComponent.create(cboxFormat, "Output format")
outputFormat.setSize(128, outputFormat.height)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package nl.hannahsten.texifyidea.run

import com.intellij.testFramework.fixtures.BasePlatformTestCase
import nl.hannahsten.texifyidea.run.latex.LatexCommandLineOptionsCache

class LatexCompilerOptionsParserTest : BasePlatformTestCase() {

fun testPdflatex() {
val output = """
Usage: pdftex [OPTION]... [TEXNAME[.tex]] [COMMANDS]
or: pdftex [OPTION]... \FIRST-LINE
or: pdftex [OPTION]... &FMT ARGS
Run pdfTeX on TEXNAME, usually creating TEXNAME.pdf.
If no arguments or options are specified, prompt for input.
(...)
-cnf-line=STRING parse STRING as a configuration file line
-etex enable e-TeX extensions
[-no]-file-line-error disable/enable file:line:error style messages
""".trimIndent()
val options = LatexCommandLineOptionsCache.parseHelpOutput("pdflatex", output)
assertEquals(
listOf(
Pair("cnf-line=STRING", "parse STRING as a configuration file line"),
Pair("etex", "enable e-TeX extensions"),
Pair("file-line-error", "disable/enable file:line:error style messages"),
Pair("no-file-line-error", "disable/enable file:line:error style messages"),
),
options
)
}

fun testLualatex() {
val output = """
Usage: luahbtex --lua=FILE [OPTION]... [TEXNAME[.tex]] [COMMANDS]
or: luahbtex --lua=FILE [OPTION]... \FIRST-LINE
or: luahbtex --lua=FILE [OPTION]... &FMT ARGS
Run LuaHBTeX on TEXNAME, usually creating TEXNAME.pdf.
(...)
--cnf-line =STRING parse STRING as a configuration file line
--debug-format enable format debugging
--[no-]file-line-error disable/enable file:line:error style messages
""".trimIndent()
val options = LatexCommandLineOptionsCache.parseHelpOutput("lualatex", output)
assertEquals(
listOf(
// I don't know, maybe a typo in lualatex?
Pair("cnf-line", "=STRING parse STRING as a configuration file line"),
Pair("debug-format", "enable format debugging"),
Pair("file-line-error", "disable/enable file:line:error style messages"),
Pair("no-file-line-error", "disable/enable file:line:error style messages"),
),
options
)
}

fun testLatexmk() {
val output = """
Latexmk 4.83: Automatic LaTeX document generation routine
Usage: latexmk [latexmk_options] [filename ...]
Latexmk_options:
-bibtex - use bibtex when needed (default)
-bibtex- - never use bibtex
-bibfudge- or -bibtexfudge- - don't change directory when running bibtex
""".trimIndent()
val options = LatexCommandLineOptionsCache.parseHelpOutput("latexmk", output)
assertEquals(
listOf(
Pair("bibtex", "use bibtex when needed (default)"),
Pair("bibtex-", "never use bibtex"),
Pair("bibfudge-", "don't change directory when running bibtex"),
Pair("bibtexfudge-", "don't change directory when running bibtex"),
),
options
)
}
}

0 comments on commit 469221d

Please sign in to comment.