Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full compatibility for Sumatra custom path #2781

Merged
merged 7 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationNamesInfo
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.ui.DialogBuilder
import nl.hannahsten.texifyidea.run.sumatra.isSumatraAvailable
import nl.hannahsten.texifyidea.run.linuxpdfviewer.InternalPdfViewer
import nl.hannahsten.texifyidea.run.sumatra.SumatraAvailabilityChecker
import nl.hannahsten.texifyidea.util.runCommandWithoutReturn
import nl.hannahsten.texifyidea.util.selectedRunConfig
import javax.swing.JLabel
import javax.swing.SwingConstants

Expand Down Expand Up @@ -53,10 +56,10 @@ open class ConfigureInverseSearchAction : AnAction(
// We will assume that since the user is using a 64-bit IDEA that name64 exists, this is at least true for idea64.exe and pycharm64.exe on Windows
name += "64"
// We also remove an extra "" because it opens an empty IDEA instance when present
Runtime.getRuntime().exec("cmd.exe /c start SumatraPDF -inverse-search \"\\\"$path\\$name.exe\\\" --line %l \\\"%f\\\"\"")
runCommandWithoutReturn("cmd.exe", "/C", "start", "SumatraPDF", "-inverse-search", "\"$path\\$name.exe\" --line %l \"%f\"", workingDirectory = SumatraAvailabilityChecker.getSumatraWorkingCustomDir())
}
else {
Runtime.getRuntime().exec("cmd.exe /c start SumatraPDF -inverse-search \"\\\"$path\\$name.exe\\\" \\\"\\\" --line %l \\\"%f\\\"\"")
runCommandWithoutReturn("cmd.exe", "/C", "start", "SumatraPDF", "-inverse-search", "\"$path\\$name.exe\" \"\" --line %l \"%f\"", workingDirectory = SumatraAvailabilityChecker.getSumatraWorkingCustomDir())
}

dialogWrapper.close(0)
Expand All @@ -67,6 +70,6 @@ open class ConfigureInverseSearchAction : AnAction(
}

override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = isSumatraAvailable
e.presentation.isEnabledAndVisible = e.project?.selectedRunConfig()?.pdfViewer == InternalPdfViewer.SUMATRA
MisterDeenis marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import nl.hannahsten.texifyidea.run.linuxpdfviewer.ViewerForwardSearch
import nl.hannahsten.texifyidea.run.makeindex.RunMakeindexListener
import nl.hannahsten.texifyidea.run.pdfviewer.ExternalPdfViewer
import nl.hannahsten.texifyidea.run.sumatra.SumatraForwardSearchListener
import nl.hannahsten.texifyidea.run.sumatra.isSumatraAvailable
import nl.hannahsten.texifyidea.run.sumatra.SumatraAvailabilityChecker
import nl.hannahsten.texifyidea.util.files.commandsInFileSet
import nl.hannahsten.texifyidea.util.files.psiFile
import nl.hannahsten.texifyidea.util.includedPackages
Expand Down Expand Up @@ -237,9 +237,9 @@ open class LatexCommandLineState(environment: ExecutionEnvironment, private val
handler.addProcessListener(OpenCustomPdfViewerListener(commandList.toTypedArray(), runConfig = runConfig))
}
// Do nothing if the user selected that they do not want a viewer to open.
else if (runConfig.pdfViewer == InternalPdfViewer.NONE && runConfig.sumatraPath == null) return
else if (runConfig.pdfViewer == InternalPdfViewer.NONE) return
// Sumatra does not support DVI
else if ((runConfig.sumatraPath != null || (runConfig.pdfViewer == InternalPdfViewer.SUMATRA && isSumatraAvailable)) && runConfig.outputFormat == LatexCompiler.Format.PDF) {
else if (((runConfig.pdfViewer == InternalPdfViewer.SUMATRA && SumatraAvailabilityChecker.getSumatraAvailability())) && runConfig.outputFormat == LatexCompiler.Format.PDF) {
// Open Sumatra after compilation & execute inverse search.
handler.addProcessListener(SumatraForwardSearchListener(runConfig, environment))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import nl.hannahsten.texifyidea.run.latex.ui.LatexSettingsEditor
import nl.hannahsten.texifyidea.run.linuxpdfviewer.InternalPdfViewer
import nl.hannahsten.texifyidea.run.pdfviewer.ExternalPdfViewers
import nl.hannahsten.texifyidea.run.pdfviewer.PdfViewer
import nl.hannahsten.texifyidea.run.sumatra.SumatraAvailabilityChecker
import nl.hannahsten.texifyidea.settings.TexifySettings
import nl.hannahsten.texifyidea.settings.sdk.LatexSdkUtil
import nl.hannahsten.texifyidea.util.allCommands
Expand Down Expand Up @@ -242,6 +243,9 @@ class LatexRunConfiguration constructor(
// Read SumatraPDF custom path
val sumatraPathRead = parent.getChildText(SUMATRA_PATH)
this.sumatraPath = if (sumatraPathRead.isNullOrEmpty()) null else sumatraPathRead
if (this.sumatraPath != null) {
SumatraAvailabilityChecker.isSumatraPathAvailable(this.sumatraPath)
}

// Read pdf viewer.
val viewerName = parent.getChildText(PDF_VIEWER)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package nl.hannahsten.texifyidea.run.latex.ui

import com.intellij.execution.configuration.EnvironmentVariablesComponent
import com.intellij.icons.AllIcons
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileChooser.FileTypeDescriptor
import com.intellij.openapi.options.ConfigurationException
Expand All @@ -14,6 +15,7 @@ import com.intellij.ui.SeparatorComponent
import com.intellij.ui.TitledSeparator
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBTextField
import com.intellij.ui.components.fields.ExtendableTextField
import nl.hannahsten.texifyidea.run.bibtex.BibtexRunConfigurationType
import nl.hannahsten.texifyidea.run.compiler.LatexCompiler
import nl.hannahsten.texifyidea.run.compiler.LatexCompiler.Format
Expand All @@ -26,10 +28,10 @@ import nl.hannahsten.texifyidea.run.linuxpdfviewer.InternalPdfViewer
import nl.hannahsten.texifyidea.run.makeindex.MakeindexRunConfigurationType
import nl.hannahsten.texifyidea.run.pdfviewer.ExternalPdfViewers
import nl.hannahsten.texifyidea.run.pdfviewer.PdfViewer
import nl.hannahsten.texifyidea.run.sumatra.SumatraAvailabilityChecker
import nl.hannahsten.texifyidea.settings.sdk.LatexSdkUtil
import java.awt.event.ItemEvent
import javax.swing.JComponent
import javax.swing.JPanel
import javax.swing.*

/**
* @author Sten Wessel
Expand Down Expand Up @@ -378,10 +380,50 @@ class LatexSettingsEditor(private var project: Project?) : SettingsEditor<LatexR
panel.add(compilerPath)
}

private fun updatePdfViewerComboBox() {
val viewers = InternalPdfViewer.availableSubset().filter { it != InternalPdfViewer.NONE } +
ExternalPdfViewers.getExternalPdfViewers() +
listOf(InternalPdfViewer.NONE)

pdfViewer.component.removeAllItems()
for (i in viewers.indices) {
(pdfViewer.component as ComboBox<PdfViewer>).addItem(viewers[i])
}
pdfViewer.updateUI()
}

/**
* Optional custom path for SumatraPDF.
*/
private fun addSumatraPathField(panel: JPanel) {
class PathInputVerifier : InputVerifier() {

@Deprecated("Deprecated in Java")
override fun shouldYieldFocus(input: JComponent?): Boolean {
if (!verify(input)) {
DialogBuilder().apply {
setTitle("SumatraPDF Custom Path Invalid")
setCenterPanel(
JLabel(
"<html>Custom Path given in run configuration of SumatraPDF doesn't contain SumatraPDF.exe. Input a valid path or leave it empty.</html>",
AllIcons.General.WarningDialog,
SwingConstants.LEADING
)
)
show()
}
}

updatePdfViewerComboBox()

return true
}

override fun verify(input: JComponent?): Boolean {
PHPirates marked this conversation as resolved.
Show resolved Hide resolved
return SumatraAvailabilityChecker.isSumatraPathAvailable((input as ExtendableTextField).text).first
MisterDeenis marked this conversation as resolved.
Show resolved Hide resolved
}
}

if (SystemInfo.isWindows) {
enableSumatraPath = JBCheckBox("Select custom path to SumatraPDF")
panel.add(enableSumatraPath)
Expand All @@ -404,7 +446,15 @@ class LatexSettingsEditor(private var project: Project?) : SettingsEditor<LatexR
}
}

enableSumatraPath.addItemListener { e -> sumatraPath.isEnabled = e.stateChange == ItemEvent.SELECTED }
sumatraPath.textField.inputVerifier = PathInputVerifier()

enableSumatraPath.addItemListener { e ->
if (e.stateChange != ItemEvent.SELECTED) {
SumatraAvailabilityChecker.isSumatraPathAvailable("")
updatePdfViewerComboBox()
}
sumatraPath.isEnabled = e.stateChange == ItemEvent.SELECTED
}

panel.add(sumatraPath)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import nl.hannahsten.texifyidea.run.linuxpdfviewer.okular.OkularConversation
import nl.hannahsten.texifyidea.run.linuxpdfviewer.skim.SkimConversation
import nl.hannahsten.texifyidea.run.linuxpdfviewer.zathura.ZathuraConversation
import nl.hannahsten.texifyidea.run.pdfviewer.PdfViewer
import nl.hannahsten.texifyidea.run.sumatra.SumatraAvailabilityChecker
import nl.hannahsten.texifyidea.run.sumatra.SumatraConversation
import nl.hannahsten.texifyidea.run.sumatra.isSumatraAvailable
import nl.hannahsten.texifyidea.util.runCommand

/**
Expand All @@ -27,10 +27,10 @@ enum class InternalPdfViewer(
OKULAR("okular", "Okular", OkularConversation),
ZATHURA("zathura", "Zathura", ZathuraConversation),
SKIM("skim", "Skim", SkimConversation),
SUMATRA("sumatra", "Sumatra", SumatraConversation()),
SUMATRA("sumatra", "Sumatra", SumatraConversation),
NONE("", "No PDF viewer", null);

override fun isAvailable(): Boolean = availability[this] ?: false
override fun isAvailable(): Boolean = availability()[this] ?: false

/**
* Check if the viewer is installed and available from the path.
Expand All @@ -41,7 +41,7 @@ enum class InternalPdfViewer(
true
}
else if (SystemInfo.isWindows && this == SUMATRA) {
isSumatraAvailable
SumatraAvailabilityChecker.getSumatraAvailability()
}
// Only support Evince and Okular on Linux, although they can be installed on other systems like Mac.
else if (SystemInfo.isLinux) {
Expand All @@ -64,8 +64,8 @@ enum class InternalPdfViewer(

companion object {

private val availability: Map<InternalPdfViewer, Boolean> by lazy {
values().associateWith {
private fun availability(): Map<InternalPdfViewer, Boolean> {
return values().associateWith {
it.checkAvailability()
}
}
Expand Down
110 changes: 70 additions & 40 deletions src/nl/hannahsten/texifyidea/run/sumatra/SumatraConversation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.pretty_tools.dde.client.DDEClientConversation
import nl.hannahsten.texifyidea.TeXception
import nl.hannahsten.texifyidea.run.linuxpdfviewer.ViewerConversation
import nl.hannahsten.texifyidea.util.Log
import nl.hannahsten.texifyidea.util.runCommandWithoutReturn
import nl.hannahsten.texifyidea.util.runCommandWithExitCode
import nl.hannahsten.texifyidea.util.runCommand
import java.io.File
Expand All @@ -16,48 +17,76 @@ import java.io.File
* Is computed once at initialization (for performance), which means that the IDE needs to be restarted when users
* install SumatraPDF while running TeXiFy.
*/
val isSumatraAvailable: Boolean by lazy {
if (!SystemInfo.isWindows || !isSumatraInstalled()) return@lazy false
object SumatraAvailabilityChecker {
MisterDeenis marked this conversation as resolved.
Show resolved Hide resolved

// Try if native bindings are available
try {
DDEClientConversation()
}
catch (e: UnsatisfiedLinkError) {
Log.info("Native library DLLs could not be found.")
return@lazy false
private var isSumatraAvailable: Boolean = false

private var sumatraWorkingCustomDir: File? = null

private val isSumatraAvailableInit: Boolean by lazy {
if (!SystemInfo.isWindows || !isSumatraInstalled()) return@lazy false

// Try if native bindings are available
try {
DDEClientConversation()
}
catch (e: UnsatisfiedLinkError) {
Log.info("Native library DLLs could not be found.")
return@lazy false
}
catch (e: NoClassDefFoundError) {
Log.info("Native library DLLs could not be found.")
return@lazy false
}

true
}
catch (e: NoClassDefFoundError) {
Log.info("Native library DLLs could not be found.")
return@lazy false

init {
isSumatraAvailable = isSumatraAvailableInit
}

true
}
fun getSumatraAvailability(): Boolean {
return isSumatraAvailable
}

/**
* Checks if Sumatra can be found in a global PATH or in a directory (with sumatraCustomPath)
* Verifies that sumatraCustomPath is a directory, non-null and non-empty before checking in the directory for Sumatra.
*/
private fun isSumatraPathAvailable(sumatraCustomPath: String? = null): Pair<Boolean, File?> {
var workingDir: File? = null
if (!sumatraCustomPath.isNullOrEmpty() && File(sumatraCustomPath).isDirectory) {
workingDir = File(sumatraCustomPath)
fun getSumatraWorkingCustomDir(): File? {
return sumatraWorkingCustomDir
}

return Pair(runCommandWithExitCode("where", "SumatraPDF", workingDirectory = workingDir).second == 0, workingDir)
}
/**
* Checks if Sumatra can be found in a global PATH or in a directory (with sumatraCustomPath)
* Verifies that sumatraCustomPath is a directory, non-null and non-empty before checking in the directory for Sumatra.
*/
fun isSumatraPathAvailable(sumatraCustomPath: String? = null, assignNewAvailability: Boolean = true): Pair<Boolean, File?> {
var workingDir: File? = null
if (!sumatraCustomPath.isNullOrEmpty() && File(sumatraCustomPath).isDirectory) {
workingDir = File(sumatraCustomPath)
}

private fun isSumatraInstalled(): Boolean {
// Try some SumatraPDF registry keys
// For some reason this first one isn't always present anymore, it used to be
val regQuery1 = runCommand("reg", "query", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\SumatraPDF.exe", "/ve")?.startsWith("ERROR:") == false
val regQuery2 = runCommand("reg", "query", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\SumatraPDF.pdf", "/ve")?.startsWith("ERROR:") == false
val availabilityParams = Pair(runCommandWithExitCode("where", "SumatraPDF", workingDirectory = workingDir).second == 0, workingDir)

if (regQuery1 || regQuery2) return true
if (assignNewAvailability && !isSumatraAvailableInit) {
isSumatraAvailable = availabilityParams.first
if (isSumatraAvailable && workingDir != null) {
sumatraWorkingCustomDir = workingDir
}
}

// Try if Sumatra is in PATH
return isSumatraPathAvailable().first
return availabilityParams
}

private fun isSumatraInstalled(): Boolean {
// Try some SumatraPDF registry keys
// For some reason this first one isn't always present anymore, it used to be
val regQuery1 = runCommand("reg", "query", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\SumatraPDF.exe", "/ve")?.startsWith("ERROR:") == false
val regQuery2 = runCommand("reg", "query", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\SumatraPDF.pdf", "/ve")?.startsWith("ERROR:") == false

if (regQuery1 || regQuery2) return true

// Try if Sumatra is in PATH
return isSumatraPathAvailable(sumatraCustomPath = null, assignNewAvailability = false).first
}
}

/**
Expand All @@ -68,14 +97,14 @@ private fun isSumatraInstalled(): Boolean {
* @author Sten Wessel
* @since b0.4
*/
class SumatraConversation : ViewerConversation() {
object SumatraConversation : ViewerConversation() {

private val server = "SUMATRA"
private val topic = "control"
private const val server = "SUMATRA"
private const val topic = "control"
private var conversation: DDEClientConversation? = null

init {
if (isSumatraAvailable) {
private fun openConversation() {
if (SumatraAvailabilityChecker.getSumatraAvailability()) {
try {
conversation = DDEClientConversation()
}
Expand All @@ -89,14 +118,15 @@ class SumatraConversation : ViewerConversation() {
* Open a file in SumatraPDF, starting it if it is not running yet.
*/
fun openFile(pdfFilePath: String, newWindow: Boolean = false, focus: Boolean = false, forceRefresh: Boolean = false, sumatraPath: String? = null) {
openConversation()
MisterDeenis marked this conversation as resolved.
Show resolved Hide resolved
try {
execute("Open(\"$pdfFilePath\", ${newWindow.bit}, ${focus.bit}, ${forceRefresh.bit})")
}
catch (e: TeXception) {
// Added checks when sumatraPath doesn't exist (not a directory), so Windows popup error doesn't appear
val (pathAvailable, workingDir) = isSumatraPathAvailable(sumatraPath)
if (isSumatraAvailable || pathAvailable) {
runCommand("cmd.exe", "/C", "start", "SumatraPDF", "-reuse-instance", pdfFilePath, workingDirectory = workingDir)
val (_, workingDir) = SumatraAvailabilityChecker.isSumatraPathAvailable(sumatraPath)
if (SumatraAvailabilityChecker.getSumatraAvailability()) {
runCommandWithoutReturn("cmd.exe", "/C", "start", "SumatraPDF", "-reuse-instance", pdfFilePath, workingDirectory = workingDir)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ class SumatraForwardSearchListener(

override fun processTerminated(event: ProcessEvent) {
// First check if the user provided a custom path to SumatraPDF, if not, check if it is installed
if (event.exitCode == 0 && (runConfig.sumatraPath != null || isSumatraAvailable)) {
if (event.exitCode == 0 && (runConfig.sumatraPath != null || SumatraAvailabilityChecker.getSumatraAvailability())) {
try {
SumatraConversation().openFile(runConfig.outputFilePath, sumatraPath = runConfig.sumatraPath)
SumatraConversation.openFile(runConfig.outputFilePath, sumatraPath = runConfig.sumatraPath)
}
catch (ignored: TeXception) {
}
Expand Down Expand Up @@ -68,7 +68,7 @@ class SumatraForwardSearchListener(
// Otherwise the person is out of luck ¯\_(ツ)_/¯
Thread.sleep(1250)
// Never focus, because forward search will work fine without focus, and the user might want to continue typing after doing forward search/compiling
SumatraConversation().forwardSearch(sourceFilePath = psiFile.virtualFile.path, line = line, focus = false)
SumatraConversation.forwardSearch(sourceFilePath = psiFile.virtualFile.path, line = line, focus = false)
}
catch (ignored: TeXception) {
}
Expand Down
Loading