Skip to content

Commit

Permalink
#9: Support documentation for built-in commands and variables (#54)
Browse files Browse the repository at this point in the history
* #9: Support documentation for built-in commands and variables
  • Loading branch information
amynbe authored May 6, 2021
1 parent e3390e2 commit 4db83b6
Show file tree
Hide file tree
Showing 12 changed files with 363 additions and 3 deletions.
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@ end_of_line = unset
insert_final_newline = unset
indent_size = unset
max_line_length = unset

[*.chm]
trim_trailing_whitespace = unset
end_of_line = unset
insert_final_newline = unset
indent_size = unset
max_line_length = unset
2 changes: 1 addition & 1 deletion .github/workflows/build_plugin.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Build Plugin

on: [push]
on: [push, pull_request]

jobs:
# Run Gradle Wrapper Validation Action to verify the wrapper's checksum
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### Added
- Documentation popup (View->Quick Documentation) for built-in commands and variables.

## [0.7.0] - 2021-04-26
#### (compatibility: 2020.1 - 2021.1.*)
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ _All package listed in the table below are prefixed with `src\main\kotlin\de\nor
| New Ahk File action in project tree context menu | `ide.actions.AhkCreateFileAction` |
| Run button in editor gutter | `ide.linemarkers.AhkExecutableRunLineMarkerContributor` |
| Notification when editor opened without Ahk runners set | `ide.notifications.MissingAhkSdkNotificationProvider` |
| Documentation popup | `ide.documentation.AhkDocumentationProvider` |


#### Execution-related Features
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,18 @@ A simple plugin for developing AutoHotkey scripts. The following features are av

- Syntax highlighting (under construction; no error checking available currently)
- Run configurations
- More to come in the future...
- Quick documentation
- More to come in the future...

Dependencies:

- Archive Browser plugin (for documentation assistance)

<p><i>Note: This plugin is under development and does not have a stable release yet. Please report any issues to the plugin's <a href="https://github.com/Nordgedanken/intellij-autohotkey/issues">GitHub page</a>.</i></p>
<!-- Plugin description end -->

<!-- [![Plugin Screenshot][product-screenshot]](https://plugins.jetbrains.com/plugin/13945-autohotkey-language) -->


<!-- GETTING STARTED -->
## Getting Started
To get a local copy up and running, follow these simple steps:
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ dependencies {
intellij {
version = "2020.1"
type = "PC"
setPlugins("com.github.b3er.idea.plugins.arc.browser:0.23")
}

val generateAhkLexer = task<GenerateLexer>("generateAhkLexer") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package de.nordgedanken.auto_hotkey.ide.documentation

import com.intellij.ide.BrowserUtil
import com.intellij.lang.documentation.DocumentationProvider
import com.intellij.lang.documentation.ExternalDocumentationHandler
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.vfs.impl.ArchiveHandler
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.source.tree.LeafPsiElement
import de.nordgedanken.auto_hotkey.lang.psi.AhkTokenType

/**
* Provides commands/directives documentation by extracting them from the
* AutoHotkey.chm help file present in the AutoHotkey home directory
*/
class AhkDocumentationProvider : DocumentationProvider, ExternalDocumentationHandler {

override fun getCustomDocumentationElement(
editor: Editor,
file: PsiFile,
contextElement: PsiElement?,
targetOffset: Int
): PsiElement? = contextElement

override fun getUrlFor(element: PsiElement?, originalElement: PsiElement?): MutableList<String>? {
val url = generateAhkDocumentationReferenceUrlFor(element) ?: return null
return mutableListOf(url)
}

private fun generateAhkDocumentationReferenceUrlFor(element: PsiElement?): String? {
element ?: return null

val chm = try {
ChmArchiveUtil.getChmArchiveHandler(element.project)
} catch (e: IllegalStateException) {
return null
}

if (element.text.startsWith("A_"))
return "https://www.autohotkey.com/docs/Variables.htm#" + element.text.drop(2)

val pathInChm = ChmArchiveUtil.getPathInChm(chm, element.text)
?: return null

return "https://www.autohotkey.com/$pathInChm"
}

override fun generateDoc(element: PsiElement?, originalElement: PsiElement?): String? {
if (element !is LeafPsiElement) return null
if (element.elementType !is AhkTokenType) return null

val chm = try {
ChmArchiveUtil.getChmArchiveHandler(element.project)
} catch (e: IllegalStateException) {
return e.message
}

if (element.text.startsWith("A_"))
return extractVariableText(chm, element.text)

val pathInChm = ChmArchiveUtil.getPathInChm(chm, element.text)
?: return null

return extractHtmlText(chm, pathInChm)
}

private fun extractVariableText(chm: ArchiveHandler, variableName: String): String? = runCatching {
val variablesPage = ChmArchiveUtil.getPathInChm(chm, "Variables")
?: return null

String(chm.contentsToByteArray(variablesPage))
.substringAfter("<td>$variableName</td>")
.substringBefore("</tr>")
.replace("<td>", "<p>")
.replace("</td>", "</p>")
// disable anchor links
.replace("<a href=\"#", "<span href=\"")
}.onFailure { it.toString() }.getOrNull()

private fun extractHtmlText(chm: ArchiveHandler, pathInChm: String) = runCatching {
String(chm.contentsToByteArray(pathInChm))
// disable anchor links
.replace("<a href=\"#", "<span href=\"")
}.onFailure { it.toString() }.getOrThrow()

override fun handleExternal(element: PsiElement?, originalElement: PsiElement?): Boolean {
val url = generateAhkDocumentationReferenceUrlFor(element) ?: return false
BrowserUtil.browse(url)
return true
}

override fun handleExternalLink(
psiManager: PsiManager?,
link: String?,
context: PsiElement?
): Boolean {
if (link?.startsWith("http") == true) {
BrowserUtil.browse(link)
}
return true
}

override fun canFetchDocumentationLink(link: String?): Boolean =
link != null && !link.startsWith("#") && !link.startsWith("http")

override fun fetchExternalDocumentation(link: String, element: PsiElement?): String {

element ?: return "Cannot fetch documentation"

val chm = try {
ChmArchiveUtil.getChmArchiveHandler(element.project)
} catch (e: IllegalStateException) {
return e.message ?: "Error fetching documentation"
}

val page = link.substringAfterLast('/')
.substringBefore('#')
.substringBeforeLast('.')

val pathInChm = ChmArchiveUtil.getPathInChm(chm, page)
?: return "Cannot find file in chm file for $page"

return extractHtmlText(chm, pathInChm)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package de.nordgedanken.auto_hotkey.ide.documentation

import com.github.b3er.idea.plugins.arc.browser.formats.SevenZipArchiveFileSystemImpl
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.impl.ArchiveHandler
import com.intellij.openapi.vfs.newvfs.impl.StubVirtualFile
import de.nordgedanken.auto_hotkey.project.settings.defaultAhkSdk
import de.nordgedanken.auto_hotkey.util.AhkBundle

/**
* Helpers to manipulate the AutoHotkey.chm file
*/
object ChmArchiveUtil {

fun getChmArchiveHandler(project: Project): ArchiveHandler {
val ahkSdk = project.defaultAhkSdk
?: error(AhkBundle.msg("documentation.error.no.runner.configured"))

val homeDirectory: VirtualFile = ahkSdk.homeDirectory
?: error(AhkBundle.msg("documentation.error.cannot.access.runner.home.dir"))

val chmFile: VirtualFile = homeDirectory.findFileByRelativePath("AutoHotkey.chm")
?: error(AhkBundle.msg("documentation.error.chm.file.not.found.in.home.dir"))

val stub = object : StubVirtualFile() {
override fun getPath(): String = "${chmFile.path}!/"
override fun getParent(): VirtualFile? = null
}

return SevenZipArchiveFileSystemImpl.instance.getHandlerForFile(stub)
?: error("Error initializing 7zip file system")
}

fun getPathInChm(chm: ArchiveHandler, approximateTitle: String?): String? {
val paths = arrayOf(
"docs/commands",
"docs/misc",
"docs/objects",
"docs",
"docs/scripts"
)

for (path in paths) {
val files = chm.list(path)
for (fileName in files) {
if (fileName.equals("$approximateTitle.htm", true) ||
fileName.equals("_$approximateTitle.htm", true)
)
return "$path/$fileName"
}
}
return null
}
}
5 changes: 5 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html on how to target different products -->
<depends>com.intellij.modules.platform</depends>
<depends>com.github.b3er.idea.plugins.arc.browser</depends>

<resource-bundle>localization.AhkBundle</resource-bundle>

Expand Down Expand Up @@ -48,6 +49,10 @@
<!-- <lang.findUsagesProvider language="AutoHotKey"-->
<!-- implementationClass="de.nordgedanken.auto_hotkey.ide.search.AHKFindUsagesProvider"/>-->
<!-- <psi.referenceContributor implementation="de.nordgedanken.auto_hotkey.AHKReferenceContributor"/>-->

<lang.documentationProvider language="AutoHotkey"
implementationClass="de.nordgedanken.auto_hotkey.ide.documentation.AhkDocumentationProvider"/>

</extensions>

<actions>
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/localization/AhkBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ settings.ahksdktable.edit.buttonlabel=Edit Runner Name
settings.ahksdktable.edit.error.alreadyexists.dialogtitle=Name Already Exists!
settings.ahksdktable.edit.error.alreadyexists.dialogmsg=The runner name you provided, "%s", already exists in the IDE! Please choose a unique name.
settings.general.thankyou.label=Thank you for installing the IntelliJ AutoHotkey plugin! This plugin is completely open-source, written by fellow AutoHotkey users like you. Please visit the plugin's <a href="https://github.com/Nordgedanken/intellij-autohotkey">GitHub page</a> to view the code or report a bug. (You can also star/watch the repository to get the latest updates before they're published to the Plugin Marketplace!)

documentation.error.no.runner.configured=No AutoHotkey runner configured. Please configure one first
documentation.error.cannot.access.runner.home.dir=Cannot access Autohotkey runner home directory
documentation.error.chm.file.not.found.in.home.dir=AutoHotkey.chm not found in Autohotkey runner home directory
Loading

0 comments on commit 4db83b6

Please sign in to comment.