diff --git a/CHANGELOG.md b/CHANGELOG.md
index 866e8dd62..72841345d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
* Support label references to user defined listings environment
* Add option to disable automatic compilation in power save mode
* Convert automatic compilation settings to a combobox
+* Add inspection to check for LaTeX package updates
* Add checkboxes to graphic insertion wizard for relative width or height
### Fixed
diff --git a/Writerside/topics/Packages.md b/Writerside/topics/Packages.md
index b6ddc6e02..d5eaf019a 100644
--- a/Writerside/topics/Packages.md
+++ b/Writerside/topics/Packages.md
@@ -13,6 +13,13 @@ This inspection is for TeX Live only, MiKTeX automatically installs packages on
When using `\usepackage` or `\RequirePackage`, TeXiFy checks if the packages is installed.
If it isn’t installed, it provides a quick fix to install the package.
+## Package update available
+_Since b0.9.10_
+
+When a package has an update available on CTAN, this inspection will provide a quickfix to update the package.
+Currently, it only works when tlmgr (TeX Live manager) is installed.
+The list of available package updates is cached until IntelliJ is restarted or the quickfix is used.
+
## Package name does not match file name
_Since b0.6.10_
diff --git a/resources/META-INF/extensions/inspections/latex/probablebugs/packages.xml b/resources/META-INF/extensions/inspections/latex/probablebugs/packages.xml
index 9f46f9a2b..db654fe87 100644
--- a/resources/META-INF/extensions/inspections/latex/probablebugs/packages.xml
+++ b/resources/META-INF/extensions/inspections/latex/probablebugs/packages.xml
@@ -8,6 +8,10 @@
groupPath="LaTeX" groupName="Probable bugs" displayName="Package is not installed"
enabledByDefault="true"
level="WARNING" />
+
-
\ No newline at end of file
diff --git a/resources/inspectionDescriptions/LatexPackageUpdate.html b/resources/inspectionDescriptions/LatexPackageUpdate.html
new file mode 100644
index 000000000..5fd73b15b
--- /dev/null
+++ b/resources/inspectionDescriptions/LatexPackageUpdate.html
@@ -0,0 +1,8 @@
+
+
+
+The package given in a \usepackage or \RequirePackage has an update available.
+
+This inspection only checks installed packages on texlive systems (with tlmgr).
+
+
\ No newline at end of file
diff --git a/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/packages/LatexPackageNotInstalledInspection.kt b/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/packages/LatexPackageNotInstalledInspection.kt
index be2869d0d..9f9902f04 100644
--- a/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/packages/LatexPackageNotInstalledInspection.kt
+++ b/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/packages/LatexPackageNotInstalledInspection.kt
@@ -1,6 +1,7 @@
package nl.hannahsten.texifyidea.inspections.latex.probablebugs.packages
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
+import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
import com.intellij.codeInspection.InspectionManager
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
@@ -17,10 +18,13 @@ import com.intellij.psi.SmartPsiElementPointer
import nl.hannahsten.texifyidea.index.LatexDefinitionIndex
import nl.hannahsten.texifyidea.inspections.InsightGroup
import nl.hannahsten.texifyidea.inspections.TexifyInspectionBase
+import nl.hannahsten.texifyidea.lang.commands.LatexGenericRegularCommand
import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.reference.InputFileReference
import nl.hannahsten.texifyidea.settings.sdk.LatexSdkUtil
+import nl.hannahsten.texifyidea.settings.sdk.TexliveSdk
import nl.hannahsten.texifyidea.util.TexLivePackages
+import nl.hannahsten.texifyidea.util.magic.cmd
import nl.hannahsten.texifyidea.util.parser.childrenOfType
import nl.hannahsten.texifyidea.util.parser.requiredParameter
import nl.hannahsten.texifyidea.util.projectSearchScope
@@ -49,48 +53,54 @@ class LatexPackageNotInstalledInspection : TexifyInspectionBase() {
override fun inspectFile(file: PsiFile, manager: InspectionManager, isOntheFly: Boolean): List {
val descriptors = descriptorList()
// We have to check whether tlmgr is installed, for those users who don't want to install TeX Live in the official way
- if (LatexSdkUtil.isTlmgrAvailable(file.project)) {
- val installedPackages = TexLivePackages.packageList
- val customPackages = LatexDefinitionIndex.Util.getCommandsByName(
- "\\ProvidesPackage", file.project,
- file.project
- .projectSearchScope
- )
- .map { it.requiredParameter(0) }
- .mapNotNull { it?.lowercase(Locale.getDefault()) }
- val packages = installedPackages + customPackages
-
- val commands = file.childrenOfType(LatexCommands::class)
- .filter { it.name == "\\usepackage" || it.name == "\\RequirePackage" }
-
- for (command in commands) {
- @Suppress("ktlint:standard:property-naming")
- val `package` = command.getRequiredParameters().firstOrNull()?.lowercase(Locale.getDefault()) ?: continue
- if (`package` !in packages) {
- // Use the cache or check if the file reference resolves (in the same way we resolve for the gutter icon).
- if (
- knownNotInstalledPackages.contains(`package`) ||
- command.references.filterIsInstance().mapNotNull { it.resolve() }.isEmpty()
- ) {
- descriptors.add(
- manager.createProblemDescriptor(
- command,
- "Package is not installed or \\ProvidesPackage is missing",
- InstallPackage(
- SmartPointerManager.getInstance(file.project).createSmartPsiElementPointer(file),
- `package`,
- knownNotInstalledPackages
- ),
- ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
- isOntheFly
- )
+ if (!LatexSdkUtil.isTlmgrAvailable(file.project)) return descriptors
+
+ if (TexLivePackages.packageList.isEmpty() && TexliveSdk.Cache.isAvailable) {
+ val result = "tlmgr list --only-installed".runCommand() ?: return emptyList()
+ TexLivePackages.packageList = Regex("i\\s(.*):").findAll(result)
+ .map { it.groupValues.last() }.toMutableList()
+ }
+
+ val installedPackages = TexLivePackages.packageList
+ val customPackages = LatexDefinitionIndex.Util.getCommandsByName(
+ LatexGenericRegularCommand.PROVIDESPACKAGE.cmd, file.project,
+ file.project
+ .projectSearchScope
+ )
+ .map { it.requiredParameter(0) }
+ .mapNotNull { it?.lowercase(Locale.getDefault()) }
+ val packages = installedPackages + customPackages
+
+ val commands = file.childrenOfType(LatexCommands::class)
+ .filter { it.name == LatexGenericRegularCommand.USEPACKAGE.cmd || it.name == LatexGenericRegularCommand.REQUIREPACKAGE.cmd }
+
+ for (command in commands) {
+ @Suppress("ktlint:standard:property-naming")
+ val `package` = command.getRequiredParameters().firstOrNull()?.lowercase(Locale.getDefault()) ?: continue
+ if (`package` !in packages) {
+ // Use the cache or check if the file reference resolves (in the same way we resolve for the gutter icon).
+ if (
+ knownNotInstalledPackages.contains(`package`) ||
+ command.references.filterIsInstance().mapNotNull { it.resolve() }.isEmpty()
+ ) {
+ descriptors.add(
+ manager.createProblemDescriptor(
+ command,
+ "Package is not installed or \\ProvidesPackage is missing",
+ InstallPackage(
+ SmartPointerManager.getInstance(file.project).createSmartPsiElementPointer(file),
+ `package`,
+ knownNotInstalledPackages
+ ),
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ isOntheFly
)
- knownNotInstalledPackages.add(`package`)
- }
- else {
- // Apparently the package is installed, but was not found initially by the TexLivePackageListInitializer (for example stackrel, contained in the oberdiek bundle)
- TexLivePackages.packageList.add(`package`)
- }
+ )
+ knownNotInstalledPackages.add(`package`)
+ }
+ else {
+ // Apparently the package is installed, but was not found initially by the TexLivePackageListInitializer (for example stackrel, contained in the oberdiek bundle)
+ TexLivePackages.packageList.add(`package`)
}
}
}
@@ -101,6 +111,11 @@ class LatexPackageNotInstalledInspection : TexifyInspectionBase() {
override fun getFamilyName(): String = "Install $packageName"
+ override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo {
+ // Nothing is modified
+ return IntentionPreviewInfo.EMPTY
+ }
+
/**
* Install the package in the background and add it to the list of installed
* packages when done.
diff --git a/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/packages/LatexPackageUpdateInspection.kt b/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/packages/LatexPackageUpdateInspection.kt
new file mode 100644
index 000000000..10bfc761d
--- /dev/null
+++ b/src/nl/hannahsten/texifyidea/inspections/latex/probablebugs/packages/LatexPackageUpdateInspection.kt
@@ -0,0 +1,136 @@
+package nl.hannahsten.texifyidea.inspections.latex.probablebugs.packages
+
+import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
+import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
+import com.intellij.codeInspection.InspectionManager
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.notification.Notification
+import com.intellij.notification.NotificationType
+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 com.intellij.psi.PsiFile
+import com.intellij.psi.SmartPointerManager
+import com.intellij.psi.SmartPsiElementPointer
+import nl.hannahsten.texifyidea.inspections.InsightGroup
+import nl.hannahsten.texifyidea.inspections.TexifyInspectionBase
+import nl.hannahsten.texifyidea.psi.LatexCommands
+import nl.hannahsten.texifyidea.settings.sdk.LatexSdkUtil
+import nl.hannahsten.texifyidea.settings.sdk.TexliveSdk
+import nl.hannahsten.texifyidea.util.magic.CommandMagic
+import nl.hannahsten.texifyidea.util.parser.childrenOfType
+import nl.hannahsten.texifyidea.util.parser.requiredParameter
+import nl.hannahsten.texifyidea.util.runCommand
+import nl.hannahsten.texifyidea.util.runCommandWithExitCode
+
+/**
+ * Check for available updates for LaTeX packages.
+ * Also see [LatexPackageNotInstalledInspection].
+ */
+class LatexPackageUpdateInspection : TexifyInspectionBase() {
+
+ object Cache {
+ /** Map package name to old and new revision number */
+ var availablePackageUpdates = mapOf>()
+ }
+
+ override val inspectionGroup = InsightGroup.LATEX
+
+ override val inspectionId = "PackageUpdate"
+
+ override fun getDisplayName() = "Package has an update available"
+
+ override fun inspectFile(file: PsiFile, manager: InspectionManager, isOntheFly: Boolean): List {
+ if (!LatexSdkUtil.isTlmgrAvailable(file.project) || !TexliveSdk.Cache.isAvailable) return emptyList()
+
+ if (Cache.availablePackageUpdates.isEmpty()) {
+ val tlmgrExecutable = LatexSdkUtil.getExecutableName("tlmgr", file.project)
+ val result = runCommand(tlmgrExecutable, "update", "--list") ?: return emptyList()
+ Cache.availablePackageUpdates = """update:\s*(?[^ ]+).*local:\s*(?\d+), source:\s*(?