Backport: 13.0.0

This commit is contained in:
FalsePattern 2024-03-12 17:15:00 +01:00
commit 319dbecfe7
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
123 changed files with 6079 additions and 885 deletions

View file

@ -18,10 +18,13 @@ Changelog structure reference:
## [Unreleased] ## [Unreleased]
## [13.0.0]
### Added ### Added
- Debugging - Debugging
- Debugging support for tests when launched using the ZigTest task type (and with the gutter icons in the editor) - Debugging support for tests when launched using the ZigTest task type (and with the gutter icons in the editor)
- Debugging support on Windows systems
- Project - Project
- Added `zig init` as a new project creation option - Added `zig init` as a new project creation option
@ -30,6 +33,9 @@ Changelog structure reference:
- Zig - Zig
- Updated semantic highlighting to latest ZLS protocol - Updated semantic highlighting to latest ZLS protocol
- ZLS
- ZLS configuration is now partially editable through the GUI
### Fixed ### Fixed
- Project - Project
@ -38,6 +44,14 @@ Changelog structure reference:
- Plugin - Plugin
- Removed a bunch of write action locking, the editor should feel more responsive now - Removed a bunch of write action locking, the editor should feel more responsive now
- Zig
- Error highlighting was breaking all the time
### Removed
- Project
- !!!BREAKING CHANGE!!! There is now no arbitrary "zig execution" task, all zig tasks have been categorized into Zig run/build/test tasks respectively.
## [12.0.0] ## [12.0.0]
### Added ### Added

View file

@ -21,10 +21,13 @@ IC modules MUST NOT depend on CL modules, as this violates the restrictions set
### Common (IC) ### Common (IC)
### LSP (IC) ### LSP-Common (IC)
- LSP4J (EXT) - LSP4J (EXT)
- Flexmark (EXT)
### LSP (IC)
- Apache Commons Lang 3 (EXT) - Apache Commons Lang 3 (EXT)
- Common (IC)
- LSP-Common (IC)
### Zig (IC) ### Zig (IC)
- Grammarkit (EXT) - Grammarkit (EXT)
@ -39,6 +42,8 @@ IC modules MUST NOT depend on CL modules, as this violates the restrictions set
- Grammarkit (EXT) - Grammarkit (EXT)
- Common (IC) - Common (IC)
### Debugger (CL) ### Debugger (IU/CL)
- Common (IC)
- LSP-Common (IC)
- Zig (IC) - Zig (IC)
- Project (IC) - Project (IC)

View file

@ -13,11 +13,12 @@ complain about missing files
## Special Thanks ## Special Thanks
- The [ZigTools](https://github.com/zigtools/) team for developing the Zig Language Server.
- [HTGAzureX1212](https://github.com/HTGAzureX1212) for developing [intellij-zig](https://github.com/intellij-zig/intellij-zig), - [HTGAzureX1212](https://github.com/HTGAzureX1212) for developing [intellij-zig](https://github.com/intellij-zig/intellij-zig),
which served as a fantastic reference for deep IDE integration features which served as a fantastic reference for deep IDE integration features
- The members of the `Zig Programming Language` discord server's `#tooling-dev` channel for providing encouragement and - The members of the `Zig Programming Language` discord server's `#tooling-dev` channel for providing encouragement,
feedback feedback, and lots of bug reports.
- The Ballerina Platform developers for `lsp4intellij`, the language server connector between the IntelliJ platform - The Ballerina Platform developers for `lsp4intellij`, the language server connector between the IntelliJ platform
and the Eclipse LSP4J project and the Eclipse LSP4J project
@ -80,7 +81,16 @@ LSP server is running.
## Debugging ## Debugging
ZigBrains uses the CLion C++ toolchains (Settings | Build, Execution, Deployment | Toolchains) for debugging purposes, ### Windows
Due to technical limitations, the C++ toolchains cannot be used for debugging zig code on windows.
Go to `Settings | Build, Execution, Deployment | Debugger | Zig (Windows)` and follow the steps shown there to set up a
zig-compatible debugger.
### Linux / MacOS / Unix
ZigBrains uses the CLion C++ toolchains `Settings | Build, Execution, Deployment | Toolchains` for debugging purposes,
and it is fully compatible with both GDB and LLDB debuggers. and it is fully compatible with both GDB and LLDB debuggers.
Additionally, ZigBrains will prioritize a toolchain if it is called `Zig`, otherwise it will use the default toolchain. Additionally, ZigBrains will prioritize a toolchain if it is called `Zig`, otherwise it will use the default toolchain.

View file

@ -8,7 +8,7 @@
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"> >
<defs <defs
id="defs36"> id="defs36">
<linearGradient <linearGradient

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -13,6 +13,7 @@ plugins {
id("org.jetbrains.changelog") version("2.2.0") id("org.jetbrains.changelog") version("2.2.0")
id("org.jetbrains.grammarkit") version("2022.3.2.2") id("org.jetbrains.grammarkit") version("2022.3.2.2")
id("com.palantir.git-version") version("3.0.0") id("com.palantir.git-version") version("3.0.0")
id("org.jetbrains.kotlin.jvm") version("1.9.22") //Only used by backport module
} }
val gitVersion: groovy.lang.Closure<String> by extra val gitVersion: groovy.lang.Closure<String> by extra
@ -43,15 +44,26 @@ tasks {
} }
} }
fun pluginVersion(): Provider<String> { fun pluginVersionGit(): Provider<String> {
return provider { return provider {
System.getenv("RELEASE_VERSION")
}.orElse(provider {
try { try {
gitVersion() gitVersion()
} catch (_: java.lang.Exception) { } catch (_: java.lang.Exception) {
error("Git version not found and RELEASE_VERSION environment variable is not set!") error("Git version not found and RELEASE_VERSION environment variable is not set!")
} }
}
}
fun pluginVersion(): Provider<String> {
return provider {
System.getenv("RELEASE_VERSION")
}.orElse(pluginVersionGit().map {
val suffix = "-" + properties("pluginSinceBuild").get()
if (it.endsWith(suffix)) {
it.substring(0, it.length - suffix.length)
} else {
it
}
}) })
} }
@ -63,6 +75,7 @@ allprojects {
apply { apply {
plugin("org.jetbrains.grammarkit") plugin("org.jetbrains.grammarkit")
plugin("org.jetbrains.intellij") plugin("org.jetbrains.intellij")
plugin("org.jetbrains.kotlin.jvm")
} }
repositories { repositories {
mavenCentral() mavenCentral()
@ -97,6 +110,11 @@ allprojects {
targetCompatibility = javaVersion targetCompatibility = javaVersion
} }
tasks.withType(JavaCompile::class) {
options.encoding = "UTF-8"
}
group = properties("pluginGroup").get() group = properties("pluginGroup").get()
version = pluginVersionFull().get() version = pluginVersionFull().get()
@ -140,6 +158,14 @@ allprojects {
verifyPlugin { verifyPlugin {
enabled = false enabled = false
} }
compileKotlin {
enabled = false
}
compileTestKotlin {
enabled = false
}
} }
} }
@ -165,10 +191,28 @@ project(":") {
} }
} }
project(":backports") {
tasks {
compileKotlin {
enabled = true
kotlinOptions.jvmTarget = "17"
}
compileTestKotlin {
enabled = true
kotlinOptions.jvmTarget = "17"
}
}
}
project(":debugger") { project(":debugger") {
dependencies { dependencies {
implementation(project(":zig")) implementation(project(":zig"))
implementation(project(":project")) implementation(project(":project"))
implementation(project(":common"))
implementation(project(":lsp-common"))
implementation(project(":lsp"))
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.22.0")
} }
intellij { intellij {
version = clionVersion version = clionVersion
@ -176,13 +220,23 @@ project(":debugger") {
} }
} }
project(":lsp-common") {
apply {
plugin("java-library")
}
dependencies {
api("org.eclipse.lsp4j:org.eclipse.lsp4j:0.22.0")
}
}
project(":lsp") { project(":lsp") {
apply { apply {
plugin("java-library") plugin("java-library")
} }
dependencies { dependencies {
api("org.eclipse.lsp4j:org.eclipse.lsp4j:0.22.0") implementation(project(":common"))
implementation("com.vladsch.flexmark:flexmark:0.64.8") implementation(project(":backports"))
api(project(":lsp-common"))
api("org.apache.commons:commons-lang3:3.14.0") api("org.apache.commons:commons-lang3:3.14.0")
} }
} }

View file

@ -19,3 +19,4 @@ org.gradle.caching = true
# Enable Gradle Kotlin DSL Lazy Property Assignment -> https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:assignment # Enable Gradle Kotlin DSL Lazy Property Assignment -> https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:assignment
systemProp.org.gradle.unsafe.kotlin.assignment = true systemProp.org.gradle.unsafe.kotlin.assignment = true
kotlin.stdlib.default.dependency = false

View file

@ -0,0 +1,133 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.falsepattern.zigbrains.backports.com.intellij.lang.documentation
import com.intellij.lang.Language
import com.intellij.lang.documentation.DocumentationMarkup.*
import com.intellij.lang.documentation.DocumentationSettings.InlineCodeHighlightingMode
import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.editor.richcopy.HtmlSyntaxInfoUtil
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.NlsSafe
import com.intellij.psi.PsiElement
import com.falsepattern.zigbrains.backports.com.intellij.ui.components.JBHtmlPaneStyleConfiguration
import com.falsepattern.zigbrains.backports.com.intellij.ui.components.JBHtmlPaneStyleConfiguration.*
import com.intellij.lang.documentation.DocumentationSettings
import com.intellij.util.concurrency.annotations.RequiresReadLock
import com.intellij.xml.util.XmlStringUtil
import org.jetbrains.annotations.ApiStatus.Internal
/**
* This class facilitates generation of highlighted text and code for Quick Documentation.
* It honors [DocumentationSettings], when highlighting code and links.
*/
object QuickDocHighlightingHelper {
const val CODE_BLOCK_PREFIX = "<pre><code>"
const val CODE_BLOCK_SUFFIX = "</code></pre>"
const val INLINE_CODE_PREFIX = "<code>"
const val INLINE_CODE_SUFFIX = "</code>"
/**
* The returned code block HTML (prefixed with [CODE_BLOCK_PREFIX] and suffixed with [CODE_BLOCK_SUFFIX])
* has syntax highlighted, if there is language provided and
* [DocumentationSettings.isHighlightingOfCodeBlocksEnabled] is `true`. The code block will
* be rendered with a special background if [DocumentationSettings.isCodeBackgroundEnabled] is `true`.
*
* Any special HTML characters, like `<` or `>` are escaped.
*/
@JvmStatic
@RequiresReadLock
fun getStyledCodeBlock(project: Project, language: Language?, code: @NlsSafe CharSequence): @NlsSafe String =
StringBuilder().apply { appendStyledCodeBlock(project, language, code) }.toString()
/**
* Appends code block HTML (prefixed with [CODE_BLOCK_PREFIX] and suffixed with [CODE_BLOCK_SUFFIX]),
* which has syntax highlighted, if there is language provided and
* [DocumentationSettings.isHighlightingOfCodeBlocksEnabled] is `true`. The code block will
* be rendered with a special background if [DocumentationSettings.isCodeBackgroundEnabled] is `true`.
*
* Any special HTML characters, like `<` or `>` are escaped.
*/
@JvmStatic
@RequiresReadLock
fun StringBuilder.appendStyledCodeBlock(project: Project, language: Language?, code: @NlsSafe CharSequence): @NlsSafe StringBuilder =
append(CODE_BLOCK_PREFIX)
.appendHighlightedCode(project, language, DocumentationSettings.isHighlightingOfCodeBlocksEnabled(), code, true)
.append(CODE_BLOCK_SUFFIX)
/**
* The returned inline code HTML (prefixed with [INLINE_CODE_PREFIX] and suffixed with [INLINE_CODE_SUFFIX])
* has syntax highlighted, if there is language provided and
* [DocumentationSettings.getInlineCodeHighlightingMode] is [DocumentationSettings.InlineCodeHighlightingMode.SEMANTIC_HIGHLIGHTING].
* The code block will be rendered with a special background if [DocumentationSettings.isCodeBackgroundEnabled] is `true` and
* [DocumentationSettings.getInlineCodeHighlightingMode] is not [DocumentationSettings.InlineCodeHighlightingMode.NO_HIGHLIGHTING].
*
* Any special HTML characters, like `<` or `>` are escaped.
*/
@JvmStatic
@RequiresReadLock
fun getStyledInlineCode(project: Project, language: Language?, @NlsSafe code: String): @NlsSafe String =
StringBuilder().apply { appendStyledInlineCode(project, language, code) }.toString()
/**
* Appends inline code HTML (prefixed with [INLINE_CODE_PREFIX] and suffixed with [INLINE_CODE_SUFFIX]),
* which has syntax highlighted, if there is language provided and
* [DocumentationSettings.getInlineCodeHighlightingMode] is [DocumentationSettings.InlineCodeHighlightingMode.SEMANTIC_HIGHLIGHTING].
* The code block will be rendered with a special background if [DocumentationSettings.isCodeBackgroundEnabled] is `true` and
* [DocumentationSettings.getInlineCodeHighlightingMode] is not [DocumentationSettings.InlineCodeHighlightingMode.NO_HIGHLIGHTING].
*
* Any special HTML characters, like `<` or `>` are escaped.
*/
@JvmStatic
@RequiresReadLock
fun StringBuilder.appendStyledInlineCode(project: Project, language: Language?, @NlsSafe code: String): StringBuilder =
append(INLINE_CODE_PREFIX)
.appendHighlightedCode(
project, language, DocumentationSettings.getInlineCodeHighlightingMode() == InlineCodeHighlightingMode.SEMANTIC_HIGHLIGHTING, code,
true)
.append(INLINE_CODE_SUFFIX)
/**
* Tries to guess a registered IDE language based. Useful e.g. for Markdown support
* to figure out a language to render a code block.
*/
@JvmStatic
fun guessLanguage(language: String?): Language? =
if (language == null)
null
else
Language
.findInstancesByMimeType(language)
.asSequence()
.plus(Language.findInstancesByMimeType("text/$language"))
.plus(
Language.getRegisteredLanguages()
.asSequence()
.filter { languageMatches(language, it) }
)
.firstOrNull()
private fun StringBuilder.appendHighlightedCode(project: Project, language: Language?, doHighlighting: Boolean,
code: CharSequence, isForRenderedDoc: Boolean): StringBuilder {
val processedCode = code.toString().trim('\n', '\r').replace(' ', ' ').trimEnd()
if (language != null && doHighlighting) {
HtmlSyntaxInfoUtil.appendHighlightedByLexerAndEncodedAsHtmlCodeSnippet(
this, project, language, processedCode,
DocumentationSettings.getHighlightingSaturation(isForRenderedDoc))
}
else {
append(XmlStringUtil.escapeString(processedCode.trimIndent()))
}
return this
}
private fun languageMatches(langType: String, language: Language): Boolean =
langType.equals(language.id, ignoreCase = true)
|| FileTypeManager.getInstance().getFileTypeByExtension(langType) === language.associatedFileType
}

View file

@ -0,0 +1,271 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc
import com.intellij.lang.Language
import com.intellij.lang.documentation.DocumentationSettings
import com.falsepattern.zigbrains.backports.com.intellij.lang.documentation.QuickDocHighlightingHelper
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl.DocFlavourDescriptor
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl.DocTagRenderer
import com.intellij.openapi.diagnostic.ControlFlowException
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiFile
import com.intellij.ui.ColorUtil
import com.intellij.util.concurrency.annotations.RequiresReadLock
import com.intellij.util.containers.CollectionFactory
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.ui.UIUtil
import org.intellij.markdown.IElementType
import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser
import org.jetbrains.annotations.Contract
import org.jetbrains.annotations.Nls
import java.util.regex.Pattern
/**
* [DocMarkdownToHtmlConverter] handles conversion of Markdown text to HTML, which is intended
* to be displayed in Quick Doc popup, or inline in an editor.
*/
object DocMarkdownToHtmlConverter {
private val LOG = Logger.getInstance(DocMarkdownToHtmlConverter::class.java)
private val TAG_START_OR_CLOSE_PATTERN: Pattern = Pattern.compile("(<)/?(\\w+)[> ]")
internal val TAG_PATTERN: Pattern = Pattern.compile("^</?([a-z][a-z-_0-9]*)[^>]*>$", Pattern.CASE_INSENSITIVE)
private val FENCE_PATTERN = "\\s+```.*".toRegex()
private const val FENCED_CODE_BLOCK = "```"
private val HTML_DOC_SUBSTITUTIONS: Map<String, String> = mapOf(
"<em>" to "<i>",
"</em>" to "</i>",
"<strong>" to "<b>",
"</strong>" to "</b>",
": //" to "://", // Fix URL
"<p></p>" to "",
"</p>" to "",
"<br />" to "",
)
internal val ACCEPTABLE_BLOCK_TAGS: MutableSet<CharSequence> = CollectionFactory.createCharSequenceSet(false)
.apply {
addAll(listOf( // Text content
"blockquote", "dd", "dl", "dt",
"hr", "li", "ol", "ul", "pre", "p", // Table,
"caption", "col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr"
))
}
internal val ACCEPTABLE_TAGS: Set<CharSequence> = CollectionFactory.createCharSequenceSet(false)
.apply {
addAll(ACCEPTABLE_BLOCK_TAGS)
addAll(listOf( // Content sectioning
"h1", "h2", "h3", "h4", "h5", "h6",
// Inline text semantic
"a", "b", "br", "code", "em", "i", "s", "span", "strong", "u", "wbr", "kbd", "samp",
// Image and multimedia
"img",
// Svg and math
"svg",
// Obsolete
"tt",
// special IJ tags
"shortcut", "icon"
))
}
/**
* Converts provided Markdown text to HTML. The results are intended to be used for Quick Documentation.
* If [defaultLanguage] is provided, it will be used for syntax coloring of inline code and code blocks, if language specifier is missing.
* Block and inline code syntax coloring is being done by [QuickDocHighlightingHelper], which honors [DocumentationSettings].
* Conversion must be run within a Read Action as it might require to create intermediate [PsiFile] to highlight block of code,
* or an inline code.
*/
@Contract(pure = true)
@JvmStatic
@RequiresReadLock
@JvmOverloads
fun convert(project: Project, @Nls markdownText: String, defaultLanguage: Language? = null): @Nls String {
val lines = markdownText.lines()
val minCommonIndent =
lines
.filter(String::isNotBlank)
.minOfOrNull { line -> line.indexOfFirst { !it.isWhitespace() }.let { if (it == -1) line.length else it } }
?: 0
val processedLines = ArrayList<String>(lines.size)
var isInCode = false
var isInTable = false
var tableFormats: List<String>? = null
for (i in lines.indices) {
var processedLine = lines[i].let { if (it.length <= minCommonIndent) "" else it.substring(minCommonIndent) }
processedLine = processedLine.trimEnd()
val count = StringUtil.getOccurrenceCount(processedLine, FENCED_CODE_BLOCK)
if (count > 0) {
isInCode = if (count % 2 == 0) isInCode else !isInCode
if (processedLine.matches(FENCE_PATTERN)) {
processedLine = processedLine.trim { it <= ' ' }
}
}
else if (!isInCode) {
// TODO merge custom table generation code with Markdown parser
val tableDelimiterIndex = processedLine.indexOf('|')
if (tableDelimiterIndex != -1) {
if (!isInTable) {
if (i + 1 < lines.size) {
tableFormats = parseTableFormats(splitTableCols(lines[i + 1]))
}
}
// create table only if we've successfully read the formats line
if (!ContainerUtil.isEmpty(tableFormats)) {
val parts = splitTableCols(processedLine)
if (isTableHeaderSeparator(parts)) continue
processedLine = getProcessedRow(project, defaultLanguage, isInTable, parts, tableFormats)
if (!isInTable) processedLine = "<table style=\"border: 0px;\" cellspacing=\"0\">$processedLine"
isInTable = true
}
}
else {
if (isInTable) processedLine += "</table>"
isInTable = false
tableFormats = null
}
}
processedLines.add(processedLine)
}
var normalizedMarkdown = StringUtil.join(processedLines, "\n")
if (isInTable) normalizedMarkdown += "</table>" //NON-NLS
return performConversion(project, defaultLanguage, normalizedMarkdown)?.trimEnd()
?: adjustHtml(replaceProhibitedTags(convertNewLinePlaceholdersToTags(markdownText), ContainerUtil.emptyList()))
}
private fun convertNewLinePlaceholdersToTags(generatedDoc: String): String {
return StringUtil.replace(generatedDoc, "\n", "\n<p>")
}
private fun parseTableFormats(cols: List<String>): List<String>? {
val formats = ArrayList<String>()
for (col in cols) {
if (!isHeaderSeparator(col)) return null
formats.add(parseFormat(col.trim { it <= ' ' }))
}
return formats
}
private fun isTableHeaderSeparator(parts: List<String>): Boolean =
parts.all { isHeaderSeparator(it) }
private fun isHeaderSeparator(s: String): Boolean =
s.trim { it <= ' ' }.removePrefix(":").removeSuffix(":").chars().allMatch { it == '-'.code }
private fun splitTableCols(processedLine: String): List<String> {
val parts = ArrayList(StringUtil.split(processedLine, "|"))
if (parts.isEmpty()) return parts
if (parts[0].isNullOrBlank())
parts.removeAt(0)
if (!parts.isEmpty() && parts[parts.size - 1].isNullOrBlank())
parts.removeAt(parts.size - 1)
return parts
}
private fun getProcessedRow(project: Project,
defaultLanguage: Language?,
isInTable: Boolean,
parts: List<String>,
tableFormats: List<String>?): String {
val openingTagStart = if (isInTable)
"<td style=\"$border\" "
else
"<th style=\"$border\" "
val closingTag = if (isInTable) "</td>" else "</th>"
val resultBuilder = StringBuilder("<tr style=\"$border\">$openingTagStart")
resultBuilder.append("align=\"").append(getAlign(0, tableFormats)).append("\">")
for (i in parts.indices) {
if (i > 0) {
resultBuilder.append(closingTag).append(openingTagStart).append("align=\"").append(getAlign(i, tableFormats)).append("\">")
}
resultBuilder.append(performConversion(project, defaultLanguage, parts[i].trim { it <= ' ' }))
}
resultBuilder.append(closingTag).append("</tr>")
return resultBuilder.toString()
}
private fun getAlign(index: Int, formats: List<String>?): String {
return if (formats == null || index >= formats.size) "left" else formats[index]
}
private fun parseFormat(format: String): String {
if (format.length <= 1) return "left"
val c0 = format[0]
val cE = format[format.length - 1]
return if (c0 == ':' && cE == ':') "center" else if (cE == ':') "right" else "left"
}
private val embeddedHtmlType = IElementType("ROOT")
private fun performConversion(project: Project, defaultLanguage: Language?, text: @Nls String): @NlsSafe String? {
try {
val flavour = DocFlavourDescriptor(project, defaultLanguage)
val parsedTree = MarkdownParser(flavour).parse(embeddedHtmlType, text, true)
return HtmlGenerator(text, parsedTree, flavour, false)
.generateHtml(DocTagRenderer(text))
}
catch (e: Exception) {
if (e is ControlFlowException) throw e
LOG.warn(e.message, e)
return null
}
}
private fun replaceProhibitedTags(line: String, skipRanges: List<TextRange>): @NlsSafe String {
val matcher = TAG_START_OR_CLOSE_PATTERN.matcher(line)
val builder = StringBuilder(line)
var diff = 0
l@ while (matcher.find()) {
val tagName = matcher.group(2)
if (ACCEPTABLE_TAGS.contains(tagName)) continue
val startOfTag = matcher.start(2)
for (range in skipRanges) {
if (range.contains(startOfTag)) {
continue@l
}
}
val start = matcher.start(1) + diff
if (StringUtil.toLowerCase(tagName) == "div") {
val isOpenTag = !matcher.group(0).contains("/")
val end = start + (if (isOpenTag) 5 else 6)
val replacement = if (isOpenTag) "<span>" else "</span>"
builder.replace(start, end, replacement)
diff += 1
}
else {
builder.replace(start, start + 1, "&lt;")
diff += 3
}
}
return builder.toString()
}
@Contract(pure = true)
private fun adjustHtml(html: String): String {
var str = html
for ((key, value) in HTML_DOC_SUBSTITUTIONS) {
if (str.indexOf(key) > 0) {
str = str.replace(key, value)
}
}
return str.trim { it <= ' ' }
}
private val border: String
get() = "margin: 0; border: 1px solid; border-color: #" + ColorUtil
.toHex(UIUtil.getTooltipSeparatorColor()) + "; border-spacing: 0; border-collapse: collapse;vertical-align: baseline;"
}

View file

@ -0,0 +1,83 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl
import com.intellij.lang.Language
import com.intellij.openapi.project.Project
import org.intellij.markdown.IElementType
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.flavours.commonmark.CommonMarkMarkerProcessor
import org.intellij.markdown.flavours.gfm.GFMConstraints
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
import org.intellij.markdown.html.GeneratingProvider
import org.intellij.markdown.html.ImageGeneratingProvider
import org.intellij.markdown.html.ReferenceLinksGeneratingProvider
import org.intellij.markdown.html.makeXssSafe
import org.intellij.markdown.parser.LinkMap
import org.intellij.markdown.parser.MarkerProcessor
import org.intellij.markdown.parser.MarkerProcessorFactory
import org.intellij.markdown.parser.ProductionHolder
import org.intellij.markdown.parser.constraints.CommonMarkdownConstraints
import org.intellij.markdown.parser.markerblocks.MarkerBlockProvider
import org.intellij.markdown.parser.markerblocks.providers.HtmlBlockProvider
import java.net.URI
private val baseHtmlGeneratingProvidersMap =
GFMFlavourDescriptor().createHtmlGeneratingProviders(LinkMap(emptyMap()), null) + hashMapOf(
MarkdownTokenTypes.HTML_TAG to DocSanitizingTagGeneratingProvider(),
MarkdownElementTypes.PARAGRAPH to DocParagraphGeneratingProvider(),
)
internal class DocFlavourDescriptor(private val project: Project, private val defaultLanguage: Language?) : GFMFlavourDescriptor() {
override val markerProcessorFactory: MarkerProcessorFactory
get() = object : MarkerProcessorFactory {
override fun createMarkerProcessor(productionHolder: ProductionHolder): MarkerProcessor<*> =
DocumentationMarkerProcessor(productionHolder, GFMConstraints.BASE)
}
override fun createHtmlGeneratingProviders(linkMap: LinkMap, baseURI: URI?): Map<IElementType, GeneratingProvider> =
MergedMap(hashMapOf(MarkdownElementTypes.FULL_REFERENCE_LINK to
ReferenceLinksGeneratingProvider(linkMap, baseURI, absolutizeAnchorLinks).makeXssSafe(useSafeLinks),
MarkdownElementTypes.SHORT_REFERENCE_LINK to
ReferenceLinksGeneratingProvider(linkMap, baseURI, absolutizeAnchorLinks).makeXssSafe(useSafeLinks),
MarkdownElementTypes.IMAGE to ImageGeneratingProvider(linkMap, baseURI).makeXssSafe(useSafeLinks),
MarkdownElementTypes.CODE_BLOCK to DocCodeBlockGeneratingProvider(project, defaultLanguage),
MarkdownElementTypes.CODE_FENCE to DocCodeBlockGeneratingProvider(project, defaultLanguage),
MarkdownElementTypes.CODE_SPAN to DocCodeSpanGeneratingProvider(project, defaultLanguage)),
baseHtmlGeneratingProvidersMap)
private class DocumentationMarkerProcessor(productionHolder: ProductionHolder,
constraintsBase: CommonMarkdownConstraints) : CommonMarkMarkerProcessor(productionHolder,
constraintsBase) {
override fun getMarkerBlockProviders(): List<MarkerBlockProvider<StateInfo>> =
super.getMarkerBlockProviders().filter { it !is HtmlBlockProvider } + DocHtmlBlockProvider
}
private class MergedMap(val map1: Map<IElementType, GeneratingProvider>,
val map2: Map<IElementType, GeneratingProvider>) : Map<IElementType, GeneratingProvider> {
override fun isEmpty(): Boolean =
map1.isEmpty() && map2.isEmpty()
override fun get(key: IElementType): GeneratingProvider? =
map1[key] ?: map2[key]
override fun containsValue(value: GeneratingProvider): Boolean =
map1.containsValue(value) || map2.containsValue(value)
override fun containsKey(key: IElementType): Boolean =
map1.containsKey(key) || map2.containsKey(key)
override val entries: Set<Map.Entry<IElementType, GeneratingProvider>>
get() = throw UnsupportedOperationException()
override val keys: Set<IElementType>
get() = throw UnsupportedOperationException()
override val size: Int
get() = throw UnsupportedOperationException()
override val values: Collection<GeneratingProvider>
get() = throw UnsupportedOperationException()
}
}

View file

@ -0,0 +1,104 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter
import com.intellij.lang.Language
import com.falsepattern.zigbrains.backports.com.intellij.lang.documentation.QuickDocHighlightingHelper
import com.falsepattern.zigbrains.backports.com.intellij.lang.documentation.QuickDocHighlightingHelper.guessLanguage
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.containers.CollectionFactory
import com.intellij.util.containers.ContainerUtil
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.LeafASTNode
import org.intellij.markdown.ast.accept
import org.intellij.markdown.ast.getTextInNode
import org.intellij.markdown.html.GeneratingProvider
import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.html.TrimmingInlineHolderProvider
private val TAG_REPLACE_MAP = CollectionFactory.createCharSequenceMap<String>(false).also {
it["div"] = "span"
it["em"] = "i"
it["strong"] = "b"
}
internal class DocSanitizingTagGeneratingProvider : GeneratingProvider {
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
val nodeText = node.getTextInNode(text)
if (nodeText.contentEquals("</p>", true)) return
val matcher = DocMarkdownToHtmlConverter.TAG_PATTERN.matcher(nodeText)
if (matcher.matches()) {
val tagName = matcher.group(1)
val replaceWith = TAG_REPLACE_MAP[tagName]
if (replaceWith != null) {
visitor.consumeHtml(nodeText.subSequence(0, matcher.start(1)))
visitor.consumeHtml(replaceWith)
visitor.consumeHtml(nodeText.subSequence(matcher.end(1), nodeText.length))
return
}
if (DocMarkdownToHtmlConverter.ACCEPTABLE_TAGS.contains(tagName)) {
visitor.consumeHtml(nodeText)
return
}
}
visitor.consumeHtml(StringUtil.escapeXmlEntities(nodeText.toString()))
}
}
internal class DocCodeBlockGeneratingProvider(private val project: Project, private val defaultLanguage: Language?) : GeneratingProvider {
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
val contents = StringBuilder()
var language: String? = null
node.children.forEach { child ->
when (child.type) {
MarkdownTokenTypes.CODE_FENCE_CONTENT, MarkdownTokenTypes.CODE_LINE, MarkdownTokenTypes.EOL ->
contents.append(child.getTextInNode(text))
MarkdownTokenTypes.FENCE_LANG ->
language = HtmlGenerator.leafText(text, child).toString().trim().takeWhile { !it.isWhitespace() }
}
}
visitor.consumeHtml(QuickDocHighlightingHelper.getStyledCodeBlock(
project, guessLanguage(language) ?: defaultLanguage, contents.toString()))
}
}
internal class DocCodeSpanGeneratingProvider(private val project: Project, private val defaultLanguage: Language?) : GeneratingProvider {
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
val nodes = node.children.subList(1, node.children.size - 1)
val output = nodes
.filter { it.type != MarkdownTokenTypes.BLOCK_QUOTE }
.joinToString(separator = "") { it.getTextInNode(text) }.trim()
visitor.consumeHtml(QuickDocHighlightingHelper.getStyledInlineCode(project, defaultLanguage, output))
}
}
internal class DocParagraphGeneratingProvider : TrimmingInlineHolderProvider() {
override fun openTag(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
visitor.consumeTagOpen(node, "p")
}
override fun closeTag(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
visitor.consumeTagClose("p")
}
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
val childrenToRender = childrenToRender(node)
if (childrenToRender.isEmpty()) return
openTag(visitor, text, node)
for (child in childrenToRender(node)) {
if (child is LeafASTNode) {
visitor.visitLeaf(child)
} else {
child.accept(visitor)
}
}
closeTag(visitor, text, node)
}
}

View file

@ -0,0 +1,69 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter
import org.intellij.markdown.lexer.Compat.assert
import org.intellij.markdown.parser.LookaheadText
import org.intellij.markdown.parser.MarkerProcessor
import org.intellij.markdown.parser.ProductionHolder
import org.intellij.markdown.parser.constraints.MarkdownConstraints
import org.intellij.markdown.parser.markerblocks.MarkerBlock
import org.intellij.markdown.parser.markerblocks.MarkerBlockProvider
import org.intellij.markdown.parser.markerblocks.impl.HtmlBlockMarkerBlock
internal object DocHtmlBlockProvider : MarkerBlockProvider<MarkerProcessor.StateInfo> {
override fun createMarkerBlocks(pos: LookaheadText.Position,
productionHolder: ProductionHolder,
stateInfo: MarkerProcessor.StateInfo): List<MarkerBlock> {
val matchingGroup = matches(pos, stateInfo.currentConstraints)
if (matchingGroup in 0..3) {
return listOf(HtmlBlockMarkerBlock(stateInfo.currentConstraints, productionHolder, OPEN_CLOSE_REGEXES[matchingGroup].second, pos))
}
return emptyList()
}
override fun interruptsParagraph(pos: LookaheadText.Position, constraints: MarkdownConstraints): Boolean {
return matches(pos, constraints) in 0..4
}
private fun matches(pos: LookaheadText.Position, constraints: MarkdownConstraints): Int {
if (!MarkerBlockProvider.isStartOfLineWithConstraints(pos, constraints)) {
return -1
}
val text = pos.currentLineFromPosition
val offset = MarkerBlockProvider.passSmallIndent(text)
if (offset >= text.length || text[offset] != '<') {
return -1
}
val matchResult = FIND_START_REGEX.find(text.substring(offset))
?: return -1
assert(matchResult.groups.size == OPEN_CLOSE_REGEXES.size + 2) { "There are some excess capturing groups probably!" }
for (i in OPEN_CLOSE_REGEXES.indices) {
if (matchResult.groups[i + 2] != null) {
return i
}
}
assert(false) { "Match found but all groups are empty!" }
return -1
}
/** see {@link http://spec.commonmark.org/0.21/#html-blocks}
*
* nulls mean "Next line should be blank"
* */
private val OPEN_CLOSE_REGEXES: List<Pair<Regex, Regex?>> = listOf(
Pair(Regex("<(?:script|pre|style)(?: |>|$)", RegexOption.IGNORE_CASE),
Regex("</(?:script|style|pre)>", RegexOption.IGNORE_CASE)),
Pair(Regex("<!--"), Regex("-->")),
Pair(Regex("<\\?"), Regex("\\?>")),
Pair(Regex("<![A-Z]"), Regex(">")),
Pair(Regex("<!\\[CDATA\\["), Regex("]]>")),
Pair(Regex("</?(?:${DocMarkdownToHtmlConverter.ACCEPTABLE_BLOCK_TAGS.joinToString("|")})(?: |/?>|$)", RegexOption.IGNORE_CASE), null)
)
private val FIND_START_REGEX = Regex(
"^(${OPEN_CLOSE_REGEXES.joinToString(separator = "|", transform = { "(${it.first.pattern})" })})"
)
}

View file

@ -0,0 +1,50 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.getTextInNode
import org.intellij.markdown.html.DUMMY_ATTRIBUTES_CUSTOMIZER
import org.intellij.markdown.html.HtmlGenerator
internal class DocTagRenderer(private val wholeText: String)
: HtmlGenerator.DefaultTagRenderer(DUMMY_ATTRIBUTES_CUSTOMIZER, false) {
override fun openTag(node: ASTNode, tagName: CharSequence,
vararg attributes: CharSequence?,
autoClose: Boolean): CharSequence {
if (tagName.contentEquals("p", true)) {
val first = node.children.firstOrNull()
if (first != null && first.type === MarkdownTokenTypes.HTML_TAG) {
val text = first.getTextInNode(wholeText)
val matcher = DocMarkdownToHtmlConverter.TAG_PATTERN.matcher(text)
if (matcher.matches()) {
val nestedTag = matcher.group(1)
if (DocMarkdownToHtmlConverter.ACCEPTABLE_BLOCK_TAGS.contains(nestedTag)) {
return ""
}
}
}
}
if (tagName.contentEquals("code", true) && node.type === MarkdownTokenTypes.CODE_FENCE_CONTENT) {
return ""
}
return super.openTag(node, convertTag(tagName), *attributes, autoClose = autoClose)
}
override fun closeTag(tagName: CharSequence): CharSequence {
if (tagName.contentEquals("p", true)) return ""
return super.closeTag(convertTag(tagName))
}
private fun convertTag(tagName: CharSequence): CharSequence {
if (tagName.contentEquals("strong", true)) {
return "b"
}
else if (tagName.contentEquals("em", true)) {
return "i"
}
return tagName
}
}

View file

@ -0,0 +1,71 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.falsepattern.zigbrains.backports.com.intellij.ui.components
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.openapi.editor.colors.TextAttributesKey
import java.util.*
data class JBHtmlPaneStyleConfiguration(
val colorScheme: EditorColorsScheme = EditorColorsManager.getInstance().globalScheme,
val editorInlineContext: Boolean = false,
val inlineCodeParentSelectors: List<String> = listOf(""),
val largeCodeFontSizeSelectors: List<String> = emptyList(),
val enableInlineCodeBackground: Boolean = true,
val enableCodeBlocksBackground: Boolean = true,
val useFontLigaturesInCode: Boolean = false,
/** Unscaled */
val spaceBeforeParagraph: Int = defaultSpaceBeforeParagraph,
/** Unscaled */
val spaceAfterParagraph: Int = defaultSpaceAfterParagraph,
val controlStyleOverrides: ControlStyleOverrides? = null,
) {
override fun equals(other: Any?): Boolean =
other is JBHtmlPaneStyleConfiguration
&& colorSchemesEqual(colorScheme, other.colorScheme)
&& inlineCodeParentSelectors == other.inlineCodeParentSelectors
&& largeCodeFontSizeSelectors == other.largeCodeFontSizeSelectors
&& enableInlineCodeBackground == other.enableInlineCodeBackground
&& enableCodeBlocksBackground == other.enableCodeBlocksBackground
&& useFontLigaturesInCode == other.useFontLigaturesInCode
&& spaceBeforeParagraph == other.spaceBeforeParagraph
&& spaceAfterParagraph == other.spaceAfterParagraph
private fun colorSchemesEqual(colorScheme: EditorColorsScheme, colorScheme2: EditorColorsScheme): Boolean =
// Update here when more colors are used from the colorScheme
colorScheme.defaultBackground.rgb == colorScheme2.defaultBackground.rgb
&& colorScheme.defaultForeground.rgb == colorScheme2.defaultForeground.rgb
&& ControlKind.entries.all {
colorScheme.getAttributes(it.colorSchemeKey) ==
colorScheme2.getAttributes(it.colorSchemeKey)
}
override fun hashCode(): Int =
Objects.hash(colorScheme.defaultBackground.rgb and 0xffffff,
colorScheme.defaultForeground.rgb and 0xffffff,
inlineCodeParentSelectors, largeCodeFontSizeSelectors,
enableInlineCodeBackground, enableCodeBlocksBackground,
useFontLigaturesInCode, spaceBeforeParagraph, spaceAfterParagraph)
data class ControlStyleOverrides(
val controlKindSuffix: String,
val overrides: Map<ControlKind, Collection<ControlProperty>>
)
enum class ControlKind(val id: String, val colorSchemeKey: TextAttributesKey) {
}
enum class ControlProperty(val id: String) {
}
companion object {
@JvmStatic
val defaultSpaceBeforeParagraph: Int get() = 4
@JvmStatic
val defaultSpaceAfterParagraph: Int get() = 4
}
}

View file

@ -13,18 +13,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.lsp.utils; package com.falsepattern.zigbrains.common.util;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.project.NoAccessDuringPsiEvents; import com.intellij.openapi.project.NoAccessDuringPsiEvents;
import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Condition; import lombok.val;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
public class ApplicationUtils { public class ApplicationUtil {
private final static ExecutorService EXECUTOR_SERVICE; private final static ExecutorService EXECUTOR_SERVICE;
@ -50,16 +49,7 @@ public class ApplicationUtils {
} }
static public <T> T computableReadAction(Computable<T> computable) { static public <T> T computableReadAction(Computable<T> computable) {
if (ApplicationManager.getApplication().isDispatchThread() || return ApplicationManager.getApplication().runReadAction(computable);
ApplicationManagerEx.getApplicationEx().holdsReadLock()) {
return ApplicationManager.getApplication().runReadAction(computable);
} else {
var result = new Object() {
T value = null;
};
ApplicationManager.getApplication().invokeAndWait(() -> result.value = ApplicationManager.getApplication().runReadAction(computable));
return result.value;
}
} }
static public void writeAction(Runnable runnable) { static public void writeAction(Runnable runnable) {
@ -70,15 +60,15 @@ public class ApplicationUtils {
return ApplicationManager.getApplication().runWriteAction(computable); return ApplicationManager.getApplication().runWriteAction(computable);
} }
static public void invokeAfterPsiEvents(Runnable runnable) { static public void invokeAfterPsiEvents(Runnable runnable, boolean readLock, boolean writeLock) {
Runnable wrapper = () -> { Runnable wrapper = () -> {
if (NoAccessDuringPsiEvents.isInsideEventProcessing()) { if (NoAccessDuringPsiEvents.isInsideEventProcessing()) {
invokeAfterPsiEvents(runnable); invokeAfterPsiEvents(runnable, readLock, writeLock);
} else { } else {
runnable.run(); runnable.run();
} }
}; };
val app = ApplicationManager.getApplication();
ApplicationManager.getApplication().invokeLater(wrapper, (Condition<Void>) value -> false); app.invokeLater(writeLock ? wrapper : () -> app.executeOnPooledThread(readLock ? () -> app.runReadAction(wrapper) : wrapper));
} }
} }

View file

@ -0,0 +1,70 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.common.util;
import lombok.val;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class CollectionUtil {
public static <T> List<T> concat(List<T> a, List<T> b) {
val res = new ArrayList<>(a);
res.addAll(b);
return res;
}
public static <T> List<T> concat(T[] a, List<T> b) {
val res = new ArrayList<>(List.of(a));
res.addAll(b);
return res;
}
@SafeVarargs
public static <T> List<T> concat(List<T> a, T... b) {
val res = new ArrayList<>(a);
res.addAll(List.of(b));
return res;
}
@SuppressWarnings("unchecked")
public static <T> T[] concat(T[]... arrays) {
if (null != arrays && 0 != arrays.length) {
int resultLength = (Integer)java.util.Arrays.stream(arrays).filter(Objects::nonNull).map((e) -> {
return e.length;
}).reduce(0, Integer::sum);
T[] resultArray = (T[]) Array.newInstance(arrays[0].getClass().getComponentType(), resultLength);
int i = 0;
int n = arrays.length;
for(int curr = 0; i < n; ++i) {
T[] array = arrays[i];
if (null != array) {
int length = array.length;
System.arraycopy(array, 0, resultArray, curr, length);
curr += length;
}
}
return resultArray;
} else {
return null;
}
}
}

View file

@ -0,0 +1,134 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.common.util;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
public class FileUtil {
private static final Logger LOG = Logger.getInstance(FileUtil.class);
public final static String SPACE_ENCODED = "%20";
private final static OS os = (System.getProperty("os.name").toLowerCase().contains("win")) ? OS.WINDOWS : OS.UNIX;
private final static String COLON_ENCODED = "%3A";
private final static String URI_FILE_BEGIN = "file:";
private final static String URI_VALID_FILE_BEGIN = "file:///";
private final static char URI_PATH_SEP = '/';
/**
* Fixes common problems in uri, mainly related to Windows
*
* @param uri The uri to sanitize
* @return The sanitized uri
*/
public static String sanitizeURI(String uri) {
if (uri != null) {
StringBuilder reconstructed = new StringBuilder();
String uriCp = uri.replaceAll(" ", SPACE_ENCODED); //Don't trust servers
if (!uri.startsWith(URI_FILE_BEGIN)) {
LOG.warn("Malformed uri : " + uri);
return uri; //Probably not an uri
} else {
uriCp = uriCp.substring(URI_FILE_BEGIN.length());
while (uriCp.startsWith(Character.toString(URI_PATH_SEP))) {
uriCp = uriCp.substring(1);
}
reconstructed.append(URI_VALID_FILE_BEGIN);
if (os == OS.UNIX) {
return reconstructed.append(uriCp).toString();
} else {
reconstructed.append(uriCp.substring(0, uriCp.indexOf(URI_PATH_SEP)));
char driveLetter = reconstructed.charAt(URI_VALID_FILE_BEGIN.length());
if (Character.isLowerCase(driveLetter)) {
reconstructed.setCharAt(URI_VALID_FILE_BEGIN.length(), Character.toUpperCase(driveLetter));
}
if (reconstructed.toString().endsWith(COLON_ENCODED)) {
reconstructed.delete(reconstructed.length() - 3, reconstructed.length());
}
if (!reconstructed.toString().endsWith(":")) {
reconstructed.append(":");
}
return reconstructed.append(uriCp.substring(uriCp.indexOf(URI_PATH_SEP))).toString();
}
}
} else {
return null;
}
}
/**
* Returns the URI string corresponding to a VirtualFileSystem file
*
* @param file The file
* @return the URI
*/
public static String URIFromVirtualFile(VirtualFile file) {
return file == null? null : pathToUri(file.getPath());
}
/**
* Transforms an URI string into a VFS file
*
* @param uri The uri
* @return The virtual file
*/
public static VirtualFile virtualFileFromURI(URI uri) {
return LocalFileSystem.getInstance().findFileByIoFile(new File(uri));
}
/**
* Transforms an URI string into a VFS file
*
* @param uri The uri
* @return The virtual file
*/
public static VirtualFile virtualFileFromURI(String uri) {
try {
return virtualFileFromURI(new URI(sanitizeURI(uri)));
} catch (URISyntaxException e) {
LOG.warn(e);
return null;
}
}
/**
* Transforms a path into an URI string
*
* @param path The path
* @return The uri
*/
public static String pathToUri(@Nullable String path) {
return path != null ? sanitizeURI(new File(path).toURI().toString()) : null;
}
public static String pathToUri(@Nullable Path path) {
return path != null ? sanitizeURI(path.toUri().toString()) : null;
}
/**
* Object representing the OS type (Windows or Unix)
*/
public enum OS {
WINDOWS, UNIX
}
}

View file

@ -16,6 +16,10 @@
package com.falsepattern.zigbrains.common.util; package com.falsepattern.zigbrains.common.util;
import lombok.val;
import java.util.Arrays;
public class StringUtil { public class StringUtil {
public static String blankToNull(String value) { public static String blankToNull(String value) {
return value == null || value.isBlank() ? null : value; return value == null || value.isBlank() ? null : value;
@ -24,4 +28,56 @@ public class StringUtil {
public static String orEmpty(String value) { public static String orEmpty(String value) {
return value == null ? "" : value; return value == null ? "" : value;
} }
private static final char[] VT100_CHARS = new char[256];
static {
Arrays.fill(VT100_CHARS, ' ');
VT100_CHARS[0x6A] = '┘';
VT100_CHARS[0x6B] = '┐';
VT100_CHARS[0x6C] = '┌';
VT100_CHARS[0x6D] = '└';
VT100_CHARS[0x6E] = '┼';
VT100_CHARS[0x71] = '─';
VT100_CHARS[0x74] = '├';
VT100_CHARS[0x75] = '┤';
VT100_CHARS[0x76] = '┴';
VT100_CHARS[0x77] = '┬';
VT100_CHARS[0x78] = '│';
}
private static final String VT100_BEGIN_SEQ = "\u001B(0";
private static final String VT100_END_SEQ = "\u001B(B";
private static final int VT100_BEGIN_SEQ_LENGTH = VT100_BEGIN_SEQ.length();
private static final int VT100_END_SEQ_LENGTH = VT100_END_SEQ.length();
public static String translateVT100Escapes(String text) {
int offset = 0;
val result = new StringBuilder();
val textLength = text.length();
while (offset < textLength) {
val startIndex = text.indexOf(VT100_BEGIN_SEQ, offset);
if (startIndex < 0) {
result.append(text.substring(offset, textLength).replace(VT100_END_SEQ, ""));
break;
}
result.append(text, offset, startIndex);
val blockOffset = startIndex + VT100_BEGIN_SEQ_LENGTH;
var endIndex = text.indexOf(VT100_END_SEQ, blockOffset);
if (endIndex < 0) {
endIndex = textLength;
}
for (int i = blockOffset; i < endIndex; i++) {
val c = text.charAt(i);
if (c >= 256) {
result.append(c);
} else {
result.append(VT100_CHARS[c]);
}
}
offset = endIndex + VT100_END_SEQ_LENGTH;
}
return result.toString();
}
} }

View file

@ -40,6 +40,15 @@ public class TextFieldUtil {
onTextChanged); onTextChanged);
} }
public static TextFieldWithBrowseButton pathToFileTextField(Disposable disposable,
@NlsContexts.DialogTitle String dialogTitle,
Runnable onTextChanged) {
return pathTextField(FileChooserDescriptorFactory.createSingleFileDescriptor(),
disposable,
dialogTitle,
onTextChanged);
}
public static TextFieldWithBrowseButton pathTextField(FileChooserDescriptor fileChooserDescriptor, public static TextFieldWithBrowseButton pathTextField(FileChooserDescriptor fileChooserDescriptor,
Disposable disposable, Disposable disposable,
@NlsContexts.DialogTitle String dialogTitle, @NlsContexts.DialogTitle String dialogTitle,

View file

@ -1,17 +0,0 @@
<!--
~ Copyright 2023-2024 FalsePattern
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<idea-plugin/>

1
modules/debugger/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
!src/**/build/

View file

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.cpp; package com.falsepattern.zigbrains.cpp;
import com.falsepattern.zigbrains.zig.debugbridge.DebuggerDriverProvider; import com.falsepattern.zigbrains.debugbridge.DebuggerDriverProvider;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.jetbrains.cidr.cpp.execution.debugger.backend.CLionGDBDriverConfiguration; import com.jetbrains.cidr.cpp.execution.debugger.backend.CLionGDBDriverConfiguration;

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugbridge; package com.falsepattern.zigbrains.debugbridge;
import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;

View file

@ -14,13 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger; package com.falsepattern.zigbrains.debugger;
import com.falsepattern.zigbrains.zig.debugbridge.DebuggerDriverProvider; import com.falsepattern.zigbrains.debugbridge.DebuggerDriverProvider;
import com.falsepattern.zigbrains.debugger.win.WinDebuggerDriverConfiguration;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType; import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications; import com.intellij.notification.Notifications;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.util.system.OS;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration; import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration; import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration;
import lombok.val; import lombok.val;
@ -28,6 +30,9 @@ import org.jetbrains.annotations.Nullable;
public class Utils { public class Utils {
public static @Nullable DebuggerDriverConfiguration getDebuggerConfiguration(Project project) { public static @Nullable DebuggerDriverConfiguration getDebuggerConfiguration(Project project) {
if (OS.CURRENT == OS.Windows) {
return new WinDebuggerDriverConfiguration();
}
val providedDebugger = DebuggerDriverProvider.findDebuggerConfigurations(project) val providedDebugger = DebuggerDriverProvider.findDebuggerConfigurations(project)
.filter(x -> x instanceof DebuggerDriverConfiguration) .filter(x -> x instanceof DebuggerDriverConfiguration)
.map(x -> (DebuggerDriverConfiguration)x) .map(x -> (DebuggerDriverConfiguration)x)

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger; package com.falsepattern.zigbrains.debugger;
import com.jetbrains.cidr.execution.debugger.CidrDebuggerEditorsExtensionBase; import com.jetbrains.cidr.execution.debugger.CidrDebuggerEditorsExtensionBase;

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger; package com.falsepattern.zigbrains.debugger;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver; import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver;
import lombok.AccessLevel; import lombok.AccessLevel;

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger; package com.falsepattern.zigbrains.debugger;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase; import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfile;

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger; package com.falsepattern.zigbrains.debugger;
import com.falsepattern.zigbrains.zig.ZigFileType; import com.falsepattern.zigbrains.zig.ZigFileType;
import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileType;

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger; package com.falsepattern.zigbrains.debugger;
import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionException;
import com.intellij.execution.filters.Filter; import com.intellij.execution.filters.Filter;

View file

@ -0,0 +1,47 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger;
import com.intellij.openapi.project.Project;
import com.intellij.xdebugger.XSourcePosition;
import com.jetbrains.cidr.execution.debugger.backend.LLValue;
import com.jetbrains.cidr.execution.debugger.evaluation.LocalVariablesFilterHandler;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class ZigVariablesFilterHandler implements LocalVariablesFilterHandler {
@NotNull
@Override
public CompletableFuture<List<LLValue>> filterVars(@NotNull Project project, @NotNull XSourcePosition xSourcePosition, @NotNull List<? extends LLValue> list) {
return CompletableFuture.supplyAsync(() -> {
val vf = xSourcePosition.getFile();
if ("zig".equals(vf.getExtension())) {
return new ArrayList<>(list);
}
return List.of();
});
}
@Override
public boolean canFilterAtPos(@NotNull Project proj, @NotNull XSourcePosition pos) {
return "zig".equals(pos.getFile().getExtension());
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.dap;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
class BlockingPipedInputStream extends PipedInputStream {
boolean closed;
public BlockingPipedInputStream(PipedOutputStream pout, int pipeSize) throws IOException {
super(pout, pipeSize);
}
public synchronized int read() throws IOException {
if (this.closed) {
throw new IOException("stream closed");
} else {
while(super.in < 0) {
this.notifyAll();
try {
this.wait(750L);
} catch (InterruptedException var2) {
throw new InterruptedIOException();
}
}
int ret = this.buffer[super.out++] & 255;
if (super.out >= this.buffer.length) {
super.out = 0;
}
if (super.in == super.out) {
super.in = -1;
}
return ret;
}
}
public void close() throws IOException {
this.closed = true;
super.close();
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.dap;
import com.falsepattern.zigbrains.zig.ZigLanguage;
import com.intellij.execution.ExecutionException;
import com.intellij.lang.Language;
import com.intellij.openapi.util.Expirable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.UserDataHolderEx;
import com.jetbrains.cidr.ArchitectureType;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerCommandException;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import com.jetbrains.cidr.execution.debugger.backend.EvaluationContext;
import com.jetbrains.cidr.execution.debugger.backend.LLFrame;
import com.jetbrains.cidr.execution.debugger.backend.LLThread;
import com.jetbrains.cidr.execution.debugger.backend.LLValue;
import com.jetbrains.cidr.execution.debugger.backend.LLValueData;
import org.eclipse.lsp4j.debug.InitializeRequestArguments;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class DAPDebuggerDriverConfiguration extends DebuggerDriverConfiguration {
@Override
public abstract @NotNull String getDriverName();
@Override
public abstract @NotNull DebuggerDriver createDriver(@NotNull DebuggerDriver.Handler handler,
@NotNull ArchitectureType architectureType) throws ExecutionException;
public abstract void customizeInitializeArguments(InitializeRequestArguments initArgs);
@Override
public @NotNull Language getConsoleLanguage() {
return ZigLanguage.INSTANCE;
}
@Override
public EvaluationContext createEvaluationContext(@NotNull DebuggerDriver debuggerDriver, @Nullable Expirable expirable, @NotNull LLThread llThread, @NotNull LLFrame llFrame, @NotNull UserDataHolderEx userDataHolderEx) {
return new EvaluationContext(debuggerDriver,expirable,llThread,llFrame,userDataHolderEx) {
@Override
public @NotNull String convertToRValue(@NotNull LLValueData llValueData, @NotNull Pair<LLValue, String> pair) throws DebuggerCommandException, ExecutionException {
return cast(pair.getSecond(), pair.getFirst().getType());
}
} ;
}
}

View file

@ -0,0 +1,267 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.dap;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.debugger.ZigDebuggerLanguage;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFileUtil;
import com.jetbrains.cidr.execution.debugger.backend.FileLocation;
import com.jetbrains.cidr.execution.debugger.backend.LLBreakpoint;
import com.jetbrains.cidr.execution.debugger.backend.LLBreakpointLocation;
import com.jetbrains.cidr.execution.debugger.backend.LLFrame;
import com.jetbrains.cidr.execution.debugger.backend.LLInstruction;
import com.jetbrains.cidr.execution.debugger.backend.LLMemoryHunk;
import com.jetbrains.cidr.execution.debugger.backend.LLModule;
import com.jetbrains.cidr.execution.debugger.backend.LLSymbolOffset;
import com.jetbrains.cidr.execution.debugger.backend.LLThread;
import com.jetbrains.cidr.execution.debugger.memory.Address;
import com.jetbrains.cidr.execution.debugger.memory.AddressRange;
import lombok.val;
import org.eclipse.lsp4j.debug.Breakpoint;
import org.eclipse.lsp4j.debug.DisassembledInstruction;
import org.eclipse.lsp4j.debug.Module;
import org.eclipse.lsp4j.debug.ReadMemoryResponse;
import org.eclipse.lsp4j.debug.Source;
import org.eclipse.lsp4j.debug.StackFrame;
import org.eclipse.lsp4j.debug.Thread;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Pattern;
public class Util {
public static LLThread threadJBFromDAP(Thread DAPThread) {
return new LLThread(DAPThread.getId(), null, null, DAPThread.getName(), null);
}
public static Thread threadDAPFromJB(LLThread JBThread) {
val DAPThread = new Thread();
DAPThread.setId((int) JBThread.getId());
return DAPThread;
}
public static LLBreakpoint breakpointJBFromDAP(Breakpoint DAPBreakpoint) {
val source = DAPBreakpoint.getSource();
var sourcePath = source == null ? "": Objects.requireNonNullElseGet(source.getPath(), () -> Objects.requireNonNullElse(source.getOrigin(), "unknown"));
sourcePath = toJBPath(sourcePath);
return new LLBreakpoint(DAPBreakpoint.getId(), sourcePath, Objects.requireNonNullElse(DAPBreakpoint.getLine(), 0) - 1, null);
}
public static @Nullable LLBreakpointLocation getLocation(Breakpoint DAPBreakpoint) {
val ref = DAPBreakpoint.getInstructionReference();
if (ref == null)
return null;
val addr = Long.parseLong(ref.substring(2), 16);
FileLocation fl = null;
val src = DAPBreakpoint.getSource();
if (src != null) {
fl = new FileLocation(src.getPath(), DAPBreakpoint.getLine());
}
return new LLBreakpointLocation(DAPBreakpoint.getId() + "", Address.fromUnsignedLong(addr), fl);
}
public static Breakpoint breakpointDAPFromJB(LLBreakpoint JBBreakpoint) {
val DAPBreakpoint = new Breakpoint();
DAPBreakpoint.setId(JBBreakpoint.getId());
DAPBreakpoint.setLine(JBBreakpoint.getOrigLine() + 1);
val source = new Source();
source.setPath(JBBreakpoint.getOrigFile());
DAPBreakpoint.setSource(source);
DAPBreakpoint.setMessage(JBBreakpoint.getCondition());
return DAPBreakpoint;
}
public static LLModule moduleJBFromDAP(Module DAPModule) {
return new LLModule(toJBPath(DAPModule.getPath()));
}
public static Module moduleDAPFromJB(LLModule JBModule) {
val DAPModule = new Module();
DAPModule.setPath(toJBPath(JBModule.getPath()));
DAPModule.setName(JBModule.getName());
return DAPModule;
}
public static LLFrame frameJBFromDAP(StackFrame DAPFrame, @Nullable DAPDriver.MappedBreakpoint helperBreakpoint, Map<Integer, DAPDriver.MappedModule> modules) {
val ptr = parseAddress(DAPFrame.getInstructionPointerReference());
val name = DAPFrame.getName();
boolean inline = name.startsWith("[Inline Frame] ");
val function = name.substring(name.indexOf('!') + 1, name.indexOf('('));
val moduleID = DAPFrame.getModuleId();
String moduleName = null;
if (moduleID != null) {
if (moduleID.isRight()) {
moduleName = moduleID.getRight();
} else {
val module = modules.get(moduleID.getLeft());
moduleName = module.java().getName();
}
}
var line = DAPFrame.getLine();
String sourcePath;
{
val src = DAPFrame.getSource();
sourcePath = src == null ? null : toJBPath(src.getPath());
}
if (helperBreakpoint != null) {
if (line == 0) {
line = helperBreakpoint.dap().getLine();
}
if (sourcePath == null) {
val src = helperBreakpoint.dap().getSource();
if (src != null) {
sourcePath = toJBPath(src.getPath());
}
}
}
return new LLFrame(DAPFrame.getId(),
function,
sourcePath,
null,
line - 1,
ptr,
ZigDebuggerLanguage.INSTANCE,
false,
inline,
moduleName);
}
public static Source toSource(String path) {
val src = new Source();
val absolute = Path.of(path).toAbsolutePath();
src.setName(absolute.getFileName().toString());
src.setPath(toWinPath(absolute.toString()));
return src;
}
public static String toWinPath(String path) {
if (path == null)
return null;
return path.replace('/', '\\');
}
public static String toJBPath(String path) {
if (path == null)
return null;
return path.replace('\\', '/');
}
public static Long parseAddressNullable(String address) {
if (address == null)
return null;
return parseAddress(address);
}
public static long parseAddress(String address) {
if (address == null)
return 0L;
if (!address.startsWith("0x"))
return Long.parseUnsignedLong(address);
return Long.parseUnsignedLong(address.substring(2), 16);
}
public static String stringifyAddress(long address) {
return "0x" + Long.toHexString(address);
}
private static final Pattern HEX_FIX_REGEX = Pattern.compile("([0-9A-F]+)(?<!\\W)h");
public static LLInstruction instructionJBFromDAP(DisassembledInstruction DAPInstruction, Source loc, Integer startLine, Integer endLine, boolean uniq, LLSymbolOffset symbol) {
val address = Address.parseHexString(DAPInstruction.getAddress());
val byteStrings = DAPInstruction.getInstructionBytes().split(" ");
val bytes = new ArrayList<Byte>(byteStrings.length);
for (val byteString: byteStrings) {
bytes.add((byte) Integer.parseInt(byteString, 16));
}
val result = new ArrayList<LLInstruction>();
String comment = null;
blk:
if (loc != null && startLine != null && endLine != null && uniq) {
val pathStr = Util.toJBPath(loc.getPath());
Path path;
try {
path = Path.of(pathStr);
} catch (InvalidPathException ignored) {
break blk;
}
val text = ApplicationUtil.computableReadAction(() -> {
val file = VfsUtil.findFile(path, true);
if (file == null)
return null;
val doc = VirtualFileUtil.findDocument(file);
if (doc == null)
return null;
return doc.getImmutableCharSequence().toString().split("(\r\n|\r|\n)");
});
if (text == null)
break blk;
startLine -= 1;
endLine -= 1;
if (text.length <= endLine)
break blk;
comment = text[endLine];
}
var nicerDisassembly = new StringBuilder();
var disassembly = DAPInstruction.getInstruction();
val matcher = HEX_FIX_REGEX.matcher(disassembly);
int prevEnd = 0;
while (matcher.find()) {
nicerDisassembly.append(disassembly, prevEnd, matcher.start());
val hex = matcher.group(1).toLowerCase();
nicerDisassembly.append("0x").append(hex);
prevEnd = matcher.end();
}
if (prevEnd < disassembly.length())
nicerDisassembly.append(disassembly, prevEnd, disassembly.length());
return LLInstruction.create(address,
bytes,
nicerDisassembly.toString(),
comment,
symbol);
}
public static LLMemoryHunk memoryJBFromDAP(ReadMemoryResponse DAPMemory) {
val address = Util.parseAddress(DAPMemory.getAddress());
val bytes = Base64.getDecoder().decode(DAPMemory.getData());
val range = new AddressRange(Address.fromUnsignedLong(address), Address.fromUnsignedLong(address + bytes.length - 1));
return new LLMemoryHunk(range, bytes);
}
public static <T> T get(CompletableFuture<T> future) throws ExecutionException {
try {
return future.get(4, TimeUnit.SECONDS);
} catch (InterruptedException | TimeoutException e) {
throw new ExecutionException(e);
} catch (java.util.concurrent.ExecutionException e) {
throw new ExecutionException(e.getCause());
}
}
public static @NotNull String emptyIfNull(@Nullable String str) {
return str == null ? "" : str;
}
}

View file

@ -0,0 +1,518 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.dap;
import com.intellij.execution.ExecutionException;
import lombok.RequiredArgsConstructor;
import org.eclipse.lsp4j.debug.BreakpointLocationsArguments;
import org.eclipse.lsp4j.debug.BreakpointLocationsResponse;
import org.eclipse.lsp4j.debug.CancelArguments;
import org.eclipse.lsp4j.debug.Capabilities;
import org.eclipse.lsp4j.debug.CompletionsArguments;
import org.eclipse.lsp4j.debug.CompletionsResponse;
import org.eclipse.lsp4j.debug.ConfigurationDoneArguments;
import org.eclipse.lsp4j.debug.ContinueArguments;
import org.eclipse.lsp4j.debug.ContinueResponse;
import org.eclipse.lsp4j.debug.DataBreakpointInfoArguments;
import org.eclipse.lsp4j.debug.DataBreakpointInfoResponse;
import org.eclipse.lsp4j.debug.DisassembleArguments;
import org.eclipse.lsp4j.debug.DisassembleResponse;
import org.eclipse.lsp4j.debug.DisconnectArguments;
import org.eclipse.lsp4j.debug.EvaluateArguments;
import org.eclipse.lsp4j.debug.EvaluateResponse;
import org.eclipse.lsp4j.debug.ExceptionInfoArguments;
import org.eclipse.lsp4j.debug.ExceptionInfoResponse;
import org.eclipse.lsp4j.debug.GotoArguments;
import org.eclipse.lsp4j.debug.GotoTargetsArguments;
import org.eclipse.lsp4j.debug.GotoTargetsResponse;
import org.eclipse.lsp4j.debug.InitializeRequestArguments;
import org.eclipse.lsp4j.debug.LoadedSourcesArguments;
import org.eclipse.lsp4j.debug.LoadedSourcesResponse;
import org.eclipse.lsp4j.debug.ModulesArguments;
import org.eclipse.lsp4j.debug.ModulesResponse;
import org.eclipse.lsp4j.debug.NextArguments;
import org.eclipse.lsp4j.debug.PauseArguments;
import org.eclipse.lsp4j.debug.ReadMemoryArguments;
import org.eclipse.lsp4j.debug.ReadMemoryResponse;
import org.eclipse.lsp4j.debug.RestartArguments;
import org.eclipse.lsp4j.debug.RestartFrameArguments;
import org.eclipse.lsp4j.debug.ReverseContinueArguments;
import org.eclipse.lsp4j.debug.ScopesArguments;
import org.eclipse.lsp4j.debug.ScopesResponse;
import org.eclipse.lsp4j.debug.SetBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetDataBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetDataBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetExceptionBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetExceptionBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetExpressionArguments;
import org.eclipse.lsp4j.debug.SetExpressionResponse;
import org.eclipse.lsp4j.debug.SetFunctionBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetFunctionBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetInstructionBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetInstructionBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetVariableArguments;
import org.eclipse.lsp4j.debug.SetVariableResponse;
import org.eclipse.lsp4j.debug.SourceArguments;
import org.eclipse.lsp4j.debug.SourceResponse;
import org.eclipse.lsp4j.debug.StackTraceArguments;
import org.eclipse.lsp4j.debug.StackTraceResponse;
import org.eclipse.lsp4j.debug.StepBackArguments;
import org.eclipse.lsp4j.debug.StepInArguments;
import org.eclipse.lsp4j.debug.StepInTargetsArguments;
import org.eclipse.lsp4j.debug.StepInTargetsResponse;
import org.eclipse.lsp4j.debug.StepOutArguments;
import org.eclipse.lsp4j.debug.TerminateArguments;
import org.eclipse.lsp4j.debug.TerminateThreadsArguments;
import org.eclipse.lsp4j.debug.ThreadsResponse;
import org.eclipse.lsp4j.debug.VariablesArguments;
import org.eclipse.lsp4j.debug.VariablesResponse;
import org.eclipse.lsp4j.debug.WriteMemoryArguments;
import org.eclipse.lsp4j.debug.WriteMemoryResponse;
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static com.falsepattern.zigbrains.debugger.dap.Util.get;
@SuppressWarnings("unused")
@RequiredArgsConstructor
public class WrappedDebugServer<T extends IDebugProtocolServer> implements IDebugProtocolServer {
protected final T server;
public void cancelNow(CancelArguments args) throws ExecutionException {
get(cancel(args));
}
public Capabilities initializeNow(InitializeRequestArguments args) throws ExecutionException {
return get(initialize(args));
}
public void configurationDoneNow(ConfigurationDoneArguments args) throws ExecutionException {
get(configurationDone(args));
}
public void launchNow(Map<String, Object> args) throws ExecutionException {
get(launch(args));
}
public void attachNow(Map<String, Object> args) throws ExecutionException {
get(attach(args));
}
public void restartNow(RestartArguments args) throws ExecutionException {
get(restart(args));
}
public void disconnectNow(DisconnectArguments args) throws ExecutionException {
get(disconnect(args));
}
public void terminateNow(TerminateArguments args) throws ExecutionException {
get(terminate(args));
}
public BreakpointLocationsResponse breakpointLocationsNow(BreakpointLocationsArguments args) throws ExecutionException {
return get(breakpointLocations(args));
}
public SetBreakpointsResponse setBreakpointsNow(SetBreakpointsArguments args) throws ExecutionException {
return get(setBreakpoints(args));
}
public SetFunctionBreakpointsResponse setFunctionBreakpointsNow(SetFunctionBreakpointsArguments args) throws ExecutionException {
return get(setFunctionBreakpoints(args));
}
public SetExceptionBreakpointsResponse setExceptionBreakpointsNow(SetExceptionBreakpointsArguments args) throws ExecutionException {
return get(setExceptionBreakpoints(args));
}
public DataBreakpointInfoResponse dataBreakpointInfoNow(DataBreakpointInfoArguments args) throws ExecutionException {
return get(dataBreakpointInfo(args));
}
public SetDataBreakpointsResponse setDataBreakpointsNow(SetDataBreakpointsArguments args) throws ExecutionException {
return get(setDataBreakpoints(args));
}
public SetInstructionBreakpointsResponse setInstructionBreakpointsNow(SetInstructionBreakpointsArguments args) throws ExecutionException {
return get(setInstructionBreakpoints(args));
}
public ContinueResponse continueNow(ContinueArguments args) throws ExecutionException {
return get(continue_(args));
}
public void nextNow(NextArguments args) throws ExecutionException {
get(next(args));
}
public void stepInNow(StepInArguments args) throws ExecutionException {
get(stepIn(args));
}
public void stepOutNow(StepOutArguments args) throws ExecutionException {
get(stepOut(args));
}
public void stepBackNow(StepBackArguments args) throws ExecutionException {
get(stepBack(args));
}
public void reverseContinueNow(ReverseContinueArguments args) throws ExecutionException {
get(reverseContinue(args));
}
public void restartFrameNow(RestartFrameArguments args) throws ExecutionException {
get(restartFrame(args));
}
public void gotoNow(GotoArguments args) throws ExecutionException {
get(goto_(args));
}
public void pauseNow(PauseArguments args) throws ExecutionException {
get(pause(args));
}
public StackTraceResponse stackTraceNow(StackTraceArguments args) throws ExecutionException {
return get(stackTrace(args));
}
public ScopesResponse scopesNow(ScopesArguments args) throws ExecutionException {
return get(scopes(args));
}
public VariablesResponse variablesNow(VariablesArguments args) throws ExecutionException {
return get(variables(args));
}
public SetVariableResponse setVariableNow(SetVariableArguments args) throws ExecutionException {
return get(setVariable(args));
}
public SourceResponse sourceNow(SourceArguments args) throws ExecutionException {
return get(source(args));
}
public ThreadsResponse threadsNow() throws ExecutionException {
return get(threads());
}
public void terminateThreadsNow(TerminateThreadsArguments args) throws ExecutionException {
get(terminateThreads(args));
}
public ModulesResponse modulesNow(ModulesArguments args) throws ExecutionException {
return get(modules(args));
}
public LoadedSourcesResponse loadedSourcesNow(LoadedSourcesArguments args) throws ExecutionException {
return get(loadedSources(args));
}
public EvaluateResponse evaluateNow(EvaluateArguments args) throws ExecutionException {
return get(evaluate(args));
}
public SetExpressionResponse setExpressionNow(SetExpressionArguments args) throws ExecutionException {
return get(setExpression(args));
}
public StepInTargetsResponse stepInTargetsNow(StepInTargetsArguments args) throws ExecutionException {
return get(stepInTargets(args));
}
public GotoTargetsResponse gotoTargetsNow(GotoTargetsArguments args) throws ExecutionException {
return get(gotoTargets(args));
}
public CompletionsResponse completionsNow(CompletionsArguments args) throws ExecutionException {
return get(completions(args));
}
public ExceptionInfoResponse exceptionInfoNow(ExceptionInfoArguments args) throws ExecutionException {
return get(exceptionInfo(args));
}
public ReadMemoryResponse readMemoryNow(ReadMemoryArguments args) throws ExecutionException {
return get(readMemory(args));
}
public WriteMemoryResponse writeMemoryNow(WriteMemoryArguments args) throws ExecutionException {
return get(writeMemory(args));
}
public DisassembleResponse disassembleNow(DisassembleArguments args) throws ExecutionException {
return get(disassemble(args));
}
@Override
@JsonRequest
public CompletableFuture<Void> cancel(CancelArguments args) {
return server.cancel(args);
}
@Override
@JsonRequest
public CompletableFuture<Capabilities> initialize(InitializeRequestArguments args) {
return server.initialize(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> configurationDone(ConfigurationDoneArguments args) {
return server.configurationDone(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> launch(Map<String, Object> args) {
return server.launch(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> attach(Map<String, Object> args) {
return server.attach(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> restart(RestartArguments args) {
return server.restart(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> disconnect(DisconnectArguments args) {
return server.disconnect(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> terminate(TerminateArguments args) {
return server.terminate(args);
}
@Override
@JsonRequest
public CompletableFuture<BreakpointLocationsResponse> breakpointLocations(BreakpointLocationsArguments args) {
return server.breakpointLocations(args);
}
@Override
@JsonRequest
public CompletableFuture<SetBreakpointsResponse> setBreakpoints(SetBreakpointsArguments args) {
return server.setBreakpoints(args);
}
@Override
@JsonRequest
public CompletableFuture<SetFunctionBreakpointsResponse> setFunctionBreakpoints(SetFunctionBreakpointsArguments args) {
return server.setFunctionBreakpoints(args);
}
@Override
@JsonRequest
public CompletableFuture<SetExceptionBreakpointsResponse> setExceptionBreakpoints(SetExceptionBreakpointsArguments args) {
return server.setExceptionBreakpoints(args);
}
@Override
@JsonRequest
public CompletableFuture<DataBreakpointInfoResponse> dataBreakpointInfo(DataBreakpointInfoArguments args) {
return server.dataBreakpointInfo(args);
}
@Override
@JsonRequest
public CompletableFuture<SetDataBreakpointsResponse> setDataBreakpoints(SetDataBreakpointsArguments args) {
return server.setDataBreakpoints(args);
}
@Override
@JsonRequest
public CompletableFuture<SetInstructionBreakpointsResponse> setInstructionBreakpoints(SetInstructionBreakpointsArguments args) {
return server.setInstructionBreakpoints(args);
}
@Override
@JsonRequest("continue")
public CompletableFuture<ContinueResponse> continue_(ContinueArguments args) {
return server.continue_(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> next(NextArguments args) {
return server.next(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> stepIn(StepInArguments args) {
return server.stepIn(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> stepOut(StepOutArguments args) {
return server.stepOut(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> stepBack(StepBackArguments args) {
return server.stepBack(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> reverseContinue(ReverseContinueArguments args) {
return server.reverseContinue(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> restartFrame(RestartFrameArguments args) {
return server.restartFrame(args);
}
@Override
@JsonRequest("goto")
public CompletableFuture<Void> goto_(GotoArguments args) {
return server.goto_(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> pause(PauseArguments args) {
return server.pause(args);
}
@Override
@JsonRequest
public CompletableFuture<StackTraceResponse> stackTrace(StackTraceArguments args) {
return server.stackTrace(args);
}
@Override
@JsonRequest
public CompletableFuture<ScopesResponse> scopes(ScopesArguments args) {
return server.scopes(args);
}
@Override
@JsonRequest
public CompletableFuture<VariablesResponse> variables(VariablesArguments args) {
return server.variables(args);
}
@Override
@JsonRequest
public CompletableFuture<SetVariableResponse> setVariable(SetVariableArguments args) {
return server.setVariable(args);
}
@Override
@JsonRequest
public CompletableFuture<SourceResponse> source(SourceArguments args) {
return server.source(args);
}
@Override
@JsonRequest
public CompletableFuture<ThreadsResponse> threads() {
return server.threads();
}
@Override
@JsonRequest
public CompletableFuture<Void> terminateThreads(TerminateThreadsArguments args) {
return server.terminateThreads(args);
}
@Override
@JsonRequest
public CompletableFuture<ModulesResponse> modules(ModulesArguments args) {
return server.modules(args);
}
@Override
@JsonRequest
public CompletableFuture<LoadedSourcesResponse> loadedSources(LoadedSourcesArguments args) {
return server.loadedSources(args);
}
@Override
@JsonRequest
public CompletableFuture<EvaluateResponse> evaluate(EvaluateArguments args) {
return server.evaluate(args);
}
@Override
@JsonRequest
public CompletableFuture<SetExpressionResponse> setExpression(SetExpressionArguments args) {
return server.setExpression(args);
}
@Override
@JsonRequest
public CompletableFuture<StepInTargetsResponse> stepInTargets(StepInTargetsArguments args) {
return server.stepInTargets(args);
}
@Override
@JsonRequest
public CompletableFuture<GotoTargetsResponse> gotoTargets(GotoTargetsArguments args) {
return server.gotoTargets(args);
}
@Override
@JsonRequest
public CompletableFuture<CompletionsResponse> completions(CompletionsArguments args) {
return server.completions(args);
}
@Override
@JsonRequest
public CompletableFuture<ExceptionInfoResponse> exceptionInfo(ExceptionInfoArguments args) {
return server.exceptionInfo(args);
}
@Override
@JsonRequest
public CompletableFuture<ReadMemoryResponse> readMemory(ReadMemoryArguments args) {
return server.readMemory(args);
}
@Override
@JsonRequest
public CompletableFuture<WriteMemoryResponse> writeMemory(WriteMemoryArguments args) {
return server.writeMemory(args);
}
@Override
@JsonRequest
public CompletableFuture<DisassembleResponse> disassemble(DisassembleArguments args) {
return server.disassemble(args);
}
}

View file

@ -0,0 +1,89 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.runner.base;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.jetbrains.cidr.execution.Installer;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@RequiredArgsConstructor
public abstract class ZigDebugEmitBinaryInstaller<ProfileState extends ProfileStateBase<?>> implements Installer {
protected final String kind;
protected final ProfileState profileState;
protected final AbstractZigToolchain toolchain;
private File executableFile;
@Override
public @NotNull GeneralCommandLine install() throws ExecutionException {
val commandLine = profileState.getCommandLine(toolchain, true);
final Path tmpDir;
try {
tmpDir = Files.createTempDirectory("zigbrains_debug").toAbsolutePath();
} catch (IOException e) {
throw new ExecutionException("Failed to create temporary directory for " + kind + " binary", e);
}
val exe = tmpDir.resolve("executable").toFile();
commandLine.addParameters("-femit-bin=" + exe.getAbsolutePath());
val outputOpt = CLIUtil.execute(commandLine, Integer.MAX_VALUE);
if (outputOpt.isEmpty()) {
throw new ExecutionException("Failed to execute \"zig " + commandLine.getParametersList().getParametersString() + "\"!");
}
val output = outputOpt.get();
if (output.getExitCode() != 0) {
throw new ExecutionException("Zig compilation failed with exit code " + output.getExitCode() + "\nError output:\n" + output.getStdout() + "\n" + output.getStderr());
}
//Find our binary
try (val stream = Files.list(tmpDir)){
executableFile = stream.filter(file -> !file.getFileName().toString().endsWith(".o"))
.map(Path::toFile)
.filter(File::canExecute)
.findFirst()
.orElseThrow(() -> new IOException("No executable file present in temporary directory \"" +
tmpDir + "\""));
} catch (Exception e) {
throw new ExecutionException("Failed to find compiled binary", e);
}
//Construct new command line
val cfg = profileState.configuration();
val cli = new GeneralCommandLine().withExePath(executableFile.getAbsolutePath());
cfg.getWorkingDirectory().getPath().ifPresent(x -> cli.setWorkDirectory(x.toFile()));
cli.addParameters(getExeArgs());
cli.withCharset(StandardCharsets.UTF_8);
cli.withRedirectErrorStream(true);
return cli;
}
public abstract String[] getExeArgs();
@Override
public @NotNull File getExecutableFile() {
return executableFile;
}
}

View file

@ -14,10 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.base; package com.falsepattern.zigbrains.debugger.runner.base;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.intellij.util.system.CpuArch;
import com.jetbrains.cidr.ArchitectureType;
import com.jetbrains.cidr.execution.RunParameters; import com.jetbrains.cidr.execution.RunParameters;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration; import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -38,6 +40,6 @@ public abstract class ZigDebugParametersBase<ProfileState extends ProfileStateBa
@Override @Override
public @Nullable String getArchitectureId() { public @Nullable String getArchitectureId() {
return null; return ArchitectureType.forVmCpuArch(CpuArch.CURRENT).getId();
} }
} }

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.base; package com.falsepattern.zigbrains.debugger.runner.base;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.runconfig.ZigProgramRunnerBase; import com.falsepattern.zigbrains.project.runconfig.ZigProgramRunnerBase;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.Utils; import com.falsepattern.zigbrains.debugger.Utils;
import com.falsepattern.zigbrains.zig.debugger.ZigLocalDebugProcess; import com.falsepattern.zigbrains.debugger.ZigLocalDebugProcess;
import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.executors.DefaultDebugExecutor;

View file

@ -0,0 +1,53 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.runner.binary;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.project.execution.binary.ProfileStateBinary;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public class ZigDebugParametersBinary extends ZigDebugParametersBase<ProfileStateBinary> {
public ZigDebugParametersBinary(DebuggerDriverConfiguration driverConfiguration, AbstractZigToolchain toolchain, ProfileStateBinary profileStateBinary) {
super(driverConfiguration, toolchain, profileStateBinary);
}
@Override
public @NotNull Installer getInstaller() {
return new Installer() {
private File executableFile;
@Override
public @NotNull GeneralCommandLine install() throws ExecutionException {
val cli = profileState.getCommandLine(toolchain, true);
executableFile = profileState.configuration().getExePath().getPathOrThrow().toFile();
return cli;
}
@Override
public @NotNull File getExecutableFile() {
return executableFile;
}
};
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.runner.binary;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.binary.ProfileStateBinary;
import com.falsepattern.zigbrains.project.execution.binary.ZigExecConfigBinary;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ZigDebugRunnerBinary extends ZigDebugRunnerBase<ProfileStateBinary> {
@Override
public @NotNull @NonNls String getRunnerId() {
return "ZigDebugRunnerBinary";
}
@Override
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
return this.executorId.equals(executorId) &&
(profile instanceof ZigExecConfigBinary);
}
@Override
protected @Nullable ZigDebugParametersBase<ProfileStateBinary> getDebugParameters(ProfileStateBinary profileStateBinary, ExecutionEnvironment environment, DebuggerDriverConfiguration debuggerDriver, AbstractZigToolchain toolchain$) {
if (!(toolchain$ instanceof LocalZigToolchain toolchain)) {
Notifications.Bus.notify(new Notification("ZigBrains.Debugger.Error", "The debugger only supports local zig toolchains!", NotificationType.ERROR));
return null;
}
return new ZigDebugParametersBinary(debuggerDriver, toolchain, profileStateBinary);
}
@Override
protected @Nullable ProfileStateBinary castProfileState(ProfileStateBase<?> state) {
return state instanceof ProfileStateBinary state$ ? state$ : null;
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.runner.build;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.project.execution.build.ProfileStateBuild;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class ZigDebugParametersBuild extends ZigDebugParametersBase<ProfileStateBuild> {
public ZigDebugParametersBuild(DebuggerDriverConfiguration driverConfiguration, AbstractZigToolchain toolchain, ProfileStateBuild profileStateBuild) {
super(driverConfiguration, toolchain, profileStateBuild);
}
@Override
public @NotNull Installer getInstaller() {
return new Installer() {
private File executableFile;
@Override
public @NotNull GeneralCommandLine install() throws ExecutionException {
val exePath = profileState.configuration().getExePath().getPath();
if (exePath.isEmpty()) {
throw new ExecutionException("Please specify the output exe path to debug \"zig build\" tasks!");
}
Path exe = exePath.get();
val commandLine = profileState.getCommandLine(toolchain, true);
val outputOpt = CLIUtil.execute(commandLine, Integer.MAX_VALUE);
if (outputOpt.isEmpty()) {
throw new ExecutionException("Failed to execute \"zig " + commandLine.getParametersList().getParametersString() + "\"!");
}
val output = outputOpt.get();
if (output.getExitCode() != 0) {
throw new ExecutionException("Zig compilation failed with exit code " + output.getExitCode() + "\nError output:\n" + output.getStdout() + "\n" + output.getStderr());
}
if (!Files.exists(exe) || !Files.isExecutable(exe)) {
throw new ExecutionException("File " + exe + " does not exist or is not executable!");
}
executableFile = exe.toFile();
//Construct new command line
val cfg = profileState.configuration();
val cli = new GeneralCommandLine().withExePath(executableFile.getAbsolutePath());
cfg.getWorkingDirectory().getPath().ifPresent(x -> cli.setWorkDirectory(x.toFile()));
cli.withCharset(StandardCharsets.UTF_8);
cli.withRedirectErrorStream(true);
return cli;
}
@Override
public @NotNull File getExecutableFile() {
return executableFile;
}
};
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.runner.build;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.build.ProfileStateBuild;
import com.falsepattern.zigbrains.project.execution.build.ZigExecConfigBuild;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ZigDebugRunnerBuild extends ZigDebugRunnerBase<ProfileStateBuild> {
@Override
public @NotNull @NonNls String getRunnerId() {
return "ZigDebugRunnerBuild";
}
@Override
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
return this.executorId.equals(executorId) &&
(profile instanceof ZigExecConfigBuild);
}
@Override
protected @Nullable ZigDebugParametersBase<ProfileStateBuild> getDebugParameters(ProfileStateBuild profileStateBuild, ExecutionEnvironment environment, DebuggerDriverConfiguration debuggerDriver, AbstractZigToolchain toolchain$) {
if (!(toolchain$ instanceof LocalZigToolchain toolchain)) {
Notifications.Bus.notify(new Notification("ZigBrains.Debugger.Error", "The debugger only supports local zig toolchains!", NotificationType.ERROR));
return null;
}
return new ZigDebugParametersBuild(debuggerDriver, toolchain, profileStateBuild);
}
@Override
protected @Nullable ProfileStateBuild castProfileState(ProfileStateBase<?> state) {
return state instanceof ProfileStateBuild state$ ? state$ : null;
}
}

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.run; package com.falsepattern.zigbrains.debugger.runner.run;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun; import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugParametersBase;
import com.jetbrains.cidr.execution.Installer; import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.TrivialInstaller;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration; import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -31,6 +31,11 @@ public class ZigDebugParametersRun extends ZigDebugParametersBase<ProfileStateRu
@Override @Override
public @NotNull Installer getInstaller() { public @NotNull Installer getInstaller() {
return new TrivialInstaller(profileState.getCommandLine(toolchain)); return new ZigDebugEmitBinaryInstaller<>("run", profileState, toolchain) {
@Override
public String[] getExeArgs() {
return profileState.configuration().getExeArgs().args;
}
};
} }
} }

View file

@ -14,14 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.run; package com.falsepattern.zigbrains.debugger.runner.run;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun; import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun;
import com.falsepattern.zigbrains.project.execution.run.ZigExecConfigRun; import com.falsepattern.zigbrains.project.execution.run.ZigExecConfigRun;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain; import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugRunnerBase;
import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;

View file

@ -0,0 +1,41 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.runner.test;
import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import org.jetbrains.annotations.NotNull;
public class ZigDebugParametersTest extends ZigDebugParametersBase<ProfileStateTest> {
public ZigDebugParametersTest(DebuggerDriverConfiguration driverConfiguration, AbstractZigToolchain toolchain, ProfileStateTest profileState) {
super(driverConfiguration, toolchain, profileState);
}
@Override
public @NotNull Installer getInstaller() {
return new ZigDebugEmitBinaryInstaller<>("test", profileState, toolchain) {
@Override
public String[] getExeArgs() {
return new String[0];
}
};
}
}

View file

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.test; package com.falsepattern.zigbrains.debugger.runner.test;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest; import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest;
import com.falsepattern.zigbrains.project.execution.test.ZigExecConfigTest; import com.falsepattern.zigbrains.project.execution.test.ZigExecConfigTest;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain; import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugParametersBase; import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugRunnerBase; import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase;
import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;

View file

@ -0,0 +1,166 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.win;
import com.falsepattern.zigbrains.debugger.dap.DAPDriver;
import com.falsepattern.zigbrains.debugger.dap.WrappedDebugServer;
import com.intellij.execution.ExecutionException;
import com.intellij.util.system.CpuArch;
import com.jetbrains.cidr.ArchitectureType;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.eclipse.lsp4j.debug.Capabilities;
import org.eclipse.lsp4j.debug.OutputEventArguments;
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
import org.eclipse.lsp4j.jsonrpc.debug.messages.DebugResponseMessage;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.jsonrpc.util.ToStringBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.concurrent.CompletableFuture;
import java.util.zip.Inflater;
public class WinDAPDriver extends DAPDriver<
IDebugProtocolServer, WrappedDebugServer<IDebugProtocolServer>,
WinDAPDriver.WinDAPDebuggerClient
> {
private final CompletableFuture<HandshakeResponse> handshakeFuture = new CompletableFuture<>();
public WinDAPDriver(@NotNull Handler handler, WinDebuggerDriverConfiguration config) throws ExecutionException {
super(handler, config);
DAPDriver$postConstructor();
}
@Override
public void DAPDriver$postConstructor$invoke() {
}
@Override
protected MessageConsumer wrapMessageConsumer(MessageConsumer messageConsumer) {
return new MessageConsumer() {
private boolean verifyHandshake = true;
@Override
public void consume(Message message) throws MessageIssueException, JsonRpcException {
if (verifyHandshake && message instanceof DebugResponseMessage res && res.getMethod().equals("handshake")) {
verifyHandshake = false;
res.setResponseId(1);
}
messageConsumer.consume(message);
}
};
}
@Override
protected Class<IDebugProtocolServer> getServerInterface() {
return IDebugProtocolServer.class;
}
@Override
protected WrappedDebugServer<IDebugProtocolServer> wrapDebugServer(IDebugProtocolServer remoteProxy) {
return new WrappedDebugServer<>(remoteProxy);
}
@Override
protected WinDAPDebuggerClient createDebuggerClient() {
return this.new WinDAPDebuggerClient();
}
@Override
protected CompletableFuture<?> wrapInitialize(CompletableFuture<Capabilities> capabilitiesCompletableFuture) {
return capabilitiesCompletableFuture.thenCombine(handshakeFuture, (res, hs) -> res);
}
@SuppressWarnings("unused")
protected class WinDAPDebuggerClient extends DAPDebuggerClient {
@Override
public void output(OutputEventArguments args) {
if ("telemetry".equals(args.getCategory())) {
return;
}
super.output(args);
}
@JsonRequest
public CompletableFuture<HandshakeResponse> handshake(HandshakeRequest handshake) {
return CompletableFuture.supplyAsync(() -> {
try {
val hasher = MessageDigest.getInstance("SHA-256");
hasher.update(handshake.getValue().getBytes(StandardCharsets.UTF_8));
var inflater = new Inflater(true);
val coconut = DAPDebuggerClient.class.getResourceAsStream("/coconut.jpg").readAllBytes();
inflater.setInput(coconut, coconut.length - 80, 77);
inflater.finished();
var b = new byte[1];
while (inflater.inflate(b) > 0) {
hasher.update(b);
}
return new HandshakeResponse(new String(coconut, coconut.length - 3, 3) + Base64.getEncoder().encodeToString(hasher.digest()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}).thenApply(handshakeResponse -> {
handshakeFuture.complete(handshakeResponse);
return handshakeResponse;
});
}
}
@Override
public @Nullable String getArchitecture() throws ExecutionException {
return ArchitectureType.forVmCpuArch(CpuArch.CURRENT).getId();
}
@Data
@NoArgsConstructor
public static class HandshakeRequest {
@NonNull
private String value;
@Override
public String toString() {
val b = new ToStringBuilder(this);
b.add("value", this.value);
return b.toString();
}
}
@Data
@NoArgsConstructor
@RequiredArgsConstructor
public static class HandshakeResponse {
@NonNull
private String signature;
@Override
public String toString() {
val b = new ToStringBuilder(this);
b.add("signature", this.signature);
return b.toString();
}
}
}

View file

@ -0,0 +1,139 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.win;
import com.falsepattern.zigbrains.debugger.dap.DAPDebuggerDriverConfiguration;
import com.falsepattern.zigbrains.debugger.win.config.WinDebuggerConfigService;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.jetbrains.cidr.ArchitectureType;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver;
import lombok.val;
import org.apache.commons.io.file.PathUtils;
import org.eclipse.lsp4j.debug.InitializeRequestArguments;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Map;
public class WinDebuggerDriverConfiguration extends DAPDebuggerDriverConfiguration {
private static void extractVSDebugger(Path pathToPluginFile, Path pathToExtracted) throws IOException {
if (pathToPluginFile == null) {
throw new IllegalArgumentException("Please set the debugger inside Build, Execution, Deployment | Debugger | Zig (Windows)");
}
if (!Files.isRegularFile(pathToPluginFile) || !pathToPluginFile.getFileName().toString().endsWith(".vsix")) {
throw new IllegalArgumentException("Invalid debugger file path! Please check Build, Execution, Deployment | Debugger | Zig (Windows) again! The file MUST be a .vsix file!");
}
URI uri;
try {
uri = new URI("jar:" + pathToPluginFile.toAbsolutePath().toUri().toASCIIString());
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Could not parse plugin file path: " + pathToPluginFile);
}
try (val fs = FileSystems.newFileSystem(uri, Map.of())) {
val basePath = fs.getPath("/extension/debugAdapters/vsdbg/bin");
try (val walk = Files.walk(basePath)) {
walk.forEach(path -> {
if (!Files.isRegularFile(path))
return;
val relPath = Path.of(basePath.relativize(path).toString());
val resPath = pathToExtracted.resolve(relPath);
try {
Files.createDirectories(resPath.getParent());
Files.copy(path, resPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
}
@Override
public @NotNull GeneralCommandLine createDriverCommandLine(@NotNull DebuggerDriver debuggerDriver, @NotNull ArchitectureType architectureType) {
val pathToPluginFileStr = WinDebuggerConfigService.getInstance().cppToolsPath;
if (pathToPluginFileStr == null || pathToPluginFileStr.isBlank()) {
throw new IllegalArgumentException("Please set the debugger inside Build, Execution, Deployment | Debugger | Zig (Windows)");
}
Path pathToPluginFile;
try {
pathToPluginFile = Path.of(pathToPluginFileStr);
} catch (InvalidPathException e) {
throw new IllegalArgumentException("Invalid debugger path " + pathToPluginFileStr + "! Make sure it points to the .vsix file!");
}
Path pathToExtracted;
try {
val tmpDir = Path.of(System.getProperty("java.io.tmpdir"));
int i = 0;
do {
pathToExtracted = tmpDir.resolve("zb-windbg-" + i++);
} while (Files.exists(pathToExtracted.resolve(".lock")) || Files.isRegularFile(pathToExtracted));
if (Files.exists(pathToExtracted)) {
PathUtils.deleteDirectory(pathToExtracted);
}
extractVSDebugger(pathToPluginFile, pathToExtracted);
} catch (IOException e) {
throw new RuntimeException(e);
}
Path finalPathToExtracted = pathToExtracted;
val lockFile = finalPathToExtracted.resolve(".lock");
try {
Files.createFile(lockFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
Cleaner.create().register(debuggerDriver, () -> {
try {
Files.delete(lockFile);
} catch (IOException e) {
e.printStackTrace();
}
});
val cli = new GeneralCommandLine();
cli.setExePath(finalPathToExtracted.resolve("vsdbg.exe").toString());
cli.setCharset(StandardCharsets.UTF_8);
cli.addParameters("--interpreter=vscode", "--extConfigDir=%USERPROFILE%\\.cppvsdbg\\extensions");
cli.setWorkDirectory(finalPathToExtracted.toString());
return cli;
}
@Override
public @NotNull String getDriverName() {
return "WinDAPDriver";
}
@Override
public @NotNull DebuggerDriver createDriver(DebuggerDriver.@NotNull Handler handler, @NotNull ArchitectureType architectureType)
throws ExecutionException {
return new WinDAPDriver(handler, this);
}
@Override
public void customizeInitializeArguments(InitializeRequestArguments initArgs) {
initArgs.setPathFormat("path");
initArgs.setAdapterID("cppvsdbg");
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.win.config;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
@Service(Service.Level.APP)
@State(name = "CPPToolsSettings",
storages = @Storage("zigbrains.xml"))
public final class WinDebuggerConfigService implements PersistentStateComponent<WinDebuggerConfigService> {
public String cppToolsPath = "";
public static WinDebuggerConfigService getInstance() {
return ApplicationManager.getApplication().getService(WinDebuggerConfigService.class);
}
@Override
public @NotNull WinDebuggerConfigService getState() {
return this;
}
@Override
public void loadState(@NotNull WinDebuggerConfigService state) {
XmlSerializerUtil.copyBean(state, this);
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.win.config;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.ui.JBColor;
import com.intellij.util.system.OS;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import javax.swing.JComponent;
import java.util.Objects;
import static com.intellij.ui.dsl.builder.BuilderKt.panel;
public class WinDebuggerConfigurable implements Configurable {
private WinDebuggerSettingsPanel settingsPanel;
@Override
public @NlsContexts.ConfigurableName String getDisplayName() {
return "Zig Debugger (Windows)";
}
@Override
public @Nullable JComponent createComponent() {
settingsPanel = new WinDebuggerSettingsPanel();
return panel((p) -> {
if (OS.CURRENT != OS.Windows) {
p.row("This menu has no effect on linux/macos/non-windows systems, use the C++ toolchains (see plugin description).", (r) -> null);
p.row("For completeness' sake, here is what you would need to configure on Windows:", (r) -> null);
p.separator(JBColor.foreground());
}
settingsPanel.attachPanelTo(p);
return null;
});
}
@Override
public boolean isModified() {
if (settingsPanel == null)
return false;
var cppSettings = WinDebuggerConfigService.getInstance();
var settingsData = settingsPanel.getData();
return !Objects.equals(settingsData.cppToolsPath(), cppSettings.cppToolsPath);
}
@Override
public void apply() throws ConfigurationException {
if (settingsPanel == null)
return;
var cppSettings = WinDebuggerConfigService.getInstance();
var settingsData = settingsPanel.getData();
cppSettings.cppToolsPath = settingsData.cppToolsPath();
}
@Override
public void reset() {
if (settingsPanel == null)
return;
val cppSettings = WinDebuggerConfigService.getInstance();
settingsPanel.setData(new WinDebuggerSettingsPanel.SettingsData(cppSettings.cppToolsPath));
}
@Override
public void disposeUIResources() {
if (settingsPanel == null)
return;
Disposer.dispose(settingsPanel);
settingsPanel = null;
}
}

View file

@ -0,0 +1,99 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.win.config;
import com.falsepattern.zigbrains.common.util.StringUtil;
import com.falsepattern.zigbrains.common.util.TextFieldUtil;
import com.falsepattern.zigbrains.project.openapi.MyDisposable;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.AlignY;
import com.intellij.ui.dsl.builder.Panel;
import lombok.Getter;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import javax.swing.JLabel;
import java.util.Optional;
public class WinDebuggerSettingsPanel implements MyDisposable {
@Getter
private boolean disposed = false;
public record SettingsData(@Nullable String cppToolsPath) {}
@SuppressWarnings("DialogTitleCapitalization")
private final TextFieldWithBrowseButton pathToArchive = TextFieldUtil.pathToFileTextField(this,
"Path to \"cpptools-win**.vsix\"",
() -> {});
public SettingsData getData() {
return new SettingsData(StringUtil.blankToNull(pathToArchive.getText()));
}
public void setData(SettingsData value) {
pathToArchive.setText(Optional.ofNullable(value.cppToolsPath()).orElse(""));
}
private static HyperlinkLabel link(String url) {
val href = new HyperlinkLabel(url);
href.setHyperlinkTarget(url);
return href;
}
public void attachPanelTo(Panel panel) {
panel.panel(p -> {
p.row("Debugging Zig on Windows requires you to manually install the MSVC toolchain.",
(r) -> null);
p.row("To install the MSVC debugger, follow setup 3 under Prerequisites on the following page:",
(r) -> null);
return null;
});
panel.panel(p -> {
p.row((JLabel) null, (r) -> {
r.cell(link("https://code.visualstudio.com/docs/cpp/config-msvc"));
return null;
});
return null;
});
panel.panel(p -> {
p.row("After you've installed MSVC, you also need download the vscode plugin with the debugger adapter.", (r) -> null);
p.row("Latest known working version: 1.19.6. Newer versions may or may not work.", (r) -> null);
return null;
});
panel.panel(p -> {
p.row("You can download the latest version here:", (r) -> {
r.cell(link("https://github.com/microsoft/vscode-cpptools/releases"));
return null;
});
p.row("Put the path to the downloaded file here:", (r) -> {
r.cell(pathToArchive).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
return null;
});
}
@Override
public void dispose() {
disposed = true;
Disposer.dispose(pathToArchive);
}
}

View file

@ -1,100 +0,0 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugger.test;
import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugParametersBase;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessListener;
import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import java.util.stream.Collectors;
public class ZigDebugParametersTest extends ZigDebugParametersBase<ProfileStateTest> {
public ZigDebugParametersTest(DebuggerDriverConfiguration driverConfiguration, AbstractZigToolchain toolchain, ProfileStateTest profileState) {
super(driverConfiguration, toolchain, profileState);
}
@Override
public @NotNull Installer getInstaller() {
return new ZigTestInstaller();
}
private class ZigTestInstaller implements Installer {
private File executableFile;
@Override
public @NotNull GeneralCommandLine install() throws ExecutionException {
val commandLine = profileState.getCommandLine(toolchain);
final Path tmpDir;
try {
tmpDir = Files.createTempDirectory("zigbrains_debug").toAbsolutePath();
} catch (IOException e) {
throw new ExecutionException("Failed to create temporary directory for test binary", e);
}
val exe = tmpDir.resolve("executable").toFile();
commandLine.addParameters("--test-no-exec", "-femit-bin=" + exe.getAbsolutePath());
val outputOpt = CLIUtil.execute(commandLine, Integer.MAX_VALUE);
if (outputOpt.isEmpty()) {
throw new ExecutionException("Failed to start \"zig test\"!");
}
val output = outputOpt.get();
if (output.getExitCode() != 0) {
throw new ExecutionException("Zig test compilation failed with exit code " + output.getExitCode() + "\nError output:\n" + output.getStdout() + "\n" + output.getStderr());
}
//Find our binary
try (val stream = Files.list(tmpDir)){
executableFile = stream.filter(file -> !file.getFileName().toString().endsWith(".o"))
.map(Path::toFile)
.filter(File::canExecute)
.findFirst()
.orElseThrow(() -> new IOException("No executable file present in temporary directory \"" +
tmpDir + "\""));
} catch (Exception e) {
throw new ExecutionException("Failed to find compiled test binary", e);
}
//Construct new command line
val cfg = profileState.configuration();
val cli = new GeneralCommandLine().withExePath(executableFile.getAbsolutePath());
if (cfg.workingDirectory != null) {
cli.withWorkDirectory(cfg.workingDirectory.toString());
}
cli.withCharset(StandardCharsets.UTF_8);
cli.withRedirectErrorStream(true);
return cli;
}
@Override
public @NotNull File getExecutableFile() {
return executableFile;
}
}
}

View file

@ -14,10 +14,10 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<idea-plugin package="com.falsepattern.zigbrains.zig.cpp"> <idea-plugin package="com.falsepattern.zigbrains.cpp">
<depends>com.intellij.modules.clion</depends> <depends>com.intellij.modules.clion</depends>
<extensions defaultExtensionNs="com.falsepattern.zigbrains"> <extensions defaultExtensionNs="com.falsepattern.zigbrains">
<debuggerDriverProvider implementation="com.falsepattern.zigbrains.zig.cpp.CPPDebuggerDriverProvider"/> <debuggerDriverProvider implementation="com.falsepattern.zigbrains.cpp.CPPDebuggerDriverProvider"/>
</extensions> </extensions>
</idea-plugin> </idea-plugin>

View file

@ -14,29 +14,37 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<idea-plugin package="com.falsepattern.zigbrains.zig.debugger"> <idea-plugin package="com.falsepattern.zigbrains.debugger">
<depends>com.intellij.modules.cidr.debugger</depends> <depends>com.intellij.modules.cidr.debugger</depends>
<resource-bundle>zigbrains.zig.debugger.Bundle</resource-bundle> <resource-bundle>zigbrains.debugger.Bundle</resource-bundle>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<programRunner implementation="com.falsepattern.zigbrains.zig.debugger.run.ZigDebugRunnerRun" <programRunner implementation="com.falsepattern.zigbrains.debugger.runner.run.ZigDebugRunnerRun"
id="ZigDebugRunnerRun"/> id="ZigDebugRunnerRun"/>
<programRunner implementation="com.falsepattern.zigbrains.zig.debugger.test.ZigDebugRunnerTest" <programRunner implementation="com.falsepattern.zigbrains.debugger.runner.test.ZigDebugRunnerTest"
id="ZigDebugRunnerTest"/> id="ZigDebugRunnerTest"/>
<programRunner implementation="com.falsepattern.zigbrains.debugger.runner.build.ZigDebugRunnerBuild"
id="ZigDebugRunnerBuild"/>
<programRunner implementation="com.falsepattern.zigbrains.debugger.runner.binary.ZigDebugRunnerBinary"
id="ZigDebugRunnerBinary"/>
<notificationGroup displayType="BALLOON" <notificationGroup displayType="BALLOON"
bundle="zigbrains.zig.debugger.Bundle" bundle="zigbrains.debugger.Bundle"
key="notif-debug-error" key="notif-debug-error"
id="ZigBrains.Debugger.Error"/> id="ZigBrains.Debugger.Error"/>
<notificationGroup displayType="BALLOON" <notificationGroup displayType="BALLOON"
bundle="zigbrains.zig.debugger.Bundle" bundle="zigbrains.debugger.Bundle"
key="notif-debug-warn" key="notif-debug-warn"
id="ZigBrains.Debugger.Warn"/> id="ZigBrains.Debugger.Warn"/>
<applicationConfigurable parentId="project.propDebugger"
instance="com.falsepattern.zigbrains.debugger.win.config.WinDebuggerConfigurable"
displayName="Zig (Windows)"/>
</extensions> </extensions>
<extensions defaultExtensionNs="cidr.debugger"> <extensions defaultExtensionNs="cidr.debugger">
<languageSupport language="Zig" implementationClass="com.falsepattern.zigbrains.zig.debugger.ZigDebuggerLanguageSupport"/> <languageSupport language="Zig" implementationClass="com.falsepattern.zigbrains.debugger.ZigDebuggerLanguageSupport"/>
<editorsExtension language="Zig" implementationClass="com.falsepattern.zigbrains.zig.debugger.ZigDebuggerEditorsExtension"/> <editorsExtension language="Zig" implementationClass="com.falsepattern.zigbrains.debugger.ZigDebuggerEditorsExtension"/>
<lineBreakpointFileTypesProvider implementation="com.falsepattern.zigbrains.zig.debugger.ZigLineBreakpointFileTypesProvider"/> <lineBreakpointFileTypesProvider implementation="com.falsepattern.zigbrains.debugger.ZigLineBreakpointFileTypesProvider"/>
<localVariablesFilterHandler implementation="com.falsepattern.zigbrains.debugger.ZigVariablesFilterHandler"/>
</extensions> </extensions>
</idea-plugin> </idea-plugin>

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.lsp.client.connection; package com.falsepattern.zigbrains.lspcommon.connection;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -40,7 +40,7 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
@Nullable @Nullable
private ProcessBuilder builder; private ProcessBuilder builder;
@Nullable @Nullable
private Process process = null; protected Process process = null;
private List<String> commands; private List<String> commands;
private String workingDir; private String workingDir;
@ -55,7 +55,7 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
} }
public void start() throws IOException { public void start() throws IOException {
if ((workingDir == null || commands == null || commands.isEmpty() || commands.contains(null)) && builder == null) { if ((workingDir == null || commands == null || commands.isEmpty()) && builder == null) {
throw new IOException("Unable to start language server: " + this.toString()); throw new IOException("Unable to start language server: " + this.toString());
} }
ProcessBuilder builder = createProcessBuilder(); ProcessBuilder builder = createProcessBuilder();
@ -86,6 +86,12 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
return process != null ? process.getInputStream() : null; return process != null ? process.getInputStream() : null;
} }
@Nullable
@Override
public InputStream getErrorStream() {
return process != null ? process.getErrorStream() : null;
}
@Nullable @Nullable
@Override @Override
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
@ -98,6 +104,12 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
} }
} }
public void onExit(Runnable runnable) {
if (process != null) {
process.onExit().thenRun(runnable);
}
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof ProcessStreamConnectionProvider) { if (obj instanceof ProcessStreamConnectionProvider) {

View file

@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.lsp.client.connection; package com.falsepattern.zigbrains.lspcommon.connection;
import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -25,6 +26,10 @@ public interface StreamConnectionProvider {
InputStream getInputStream(); InputStream getInputStream();
default @Nullable InputStream getErrorStream() {
return null;
}
OutputStream getOutputStream(); OutputStream getOutputStream();
void stop(); void stop();

View file

@ -15,6 +15,7 @@
*/ */
package com.falsepattern.zigbrains.lsp; package com.falsepattern.zigbrains.lsp;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus; import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition.LanguageServerDefinition; import com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition.LanguageServerDefinition;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper; import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
@ -27,6 +28,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import lombok.val;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@ -43,7 +45,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate; import java.util.function.Predicate;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.pool; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.pool;
public class IntellijLanguageClient { public class IntellijLanguageClient {
@ -330,9 +332,14 @@ public class IntellijLanguageClient {
public static void removeWrapper(LanguageServerWrapper wrapper) { public static void removeWrapper(LanguageServerWrapper wrapper) {
if (wrapper.getProject() != null) { if (wrapper.getProject() != null) {
String[] extensions = wrapper.getServerDefinition().ext.split(LanguageServerDefinition.SPLIT_CHAR); String[] extensions = wrapper.getServerDefinition().ext.split(LanguageServerDefinition.SPLIT_CHAR);
val rootPath = wrapper.getProjectRootPath();
if (rootPath == null) {
LOG.error("Project root path is null");
return;
}
val absolutePath = FileUtil.pathToUri(rootPath);
for (String ext : extensions) { for (String ext : extensions) {
MutablePair<String, String> extProjectPair = new MutablePair<>(ext, FileUtils.pathToUri( MutablePair<String, String> extProjectPair = new MutablePair<>(ext, absolutePath);
new File(wrapper.getProjectRootPath()).getAbsolutePath()));
extToLanguageWrapper.remove(extProjectPair); extToLanguageWrapper.remove(extProjectPair);
extToServerDefinition.remove(extProjectPair); extToServerDefinition.remove(extProjectPair);
} }

View file

@ -19,13 +19,10 @@ package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient; import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager; import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase; import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.requests.ReformatHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction; import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;

View file

@ -20,13 +20,9 @@ import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager; import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase; import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.intellij.codeInsight.hint.actions.ShowImplementationsAction; import com.intellij.codeInsight.hint.actions.ShowImplementationsAction;
import com.intellij.codeInsight.navigation.CtrlMouseAction;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
@ -58,6 +54,8 @@ public class LSPGotoDefinitionAction extends ShowImplementationsAction {
super.actionPerformed(e); super.actionPerformed(e);
return; return;
} }
manager.gotoDefinition(psiElement); if (!manager.gotoDefinition(psiElement)) {
super.actionPerformed(e);
}
} }
} }

View file

@ -15,14 +15,12 @@
*/ */
package com.falsepattern.zigbrains.lsp.actions; package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient; import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.requests.ReformatHandler; import com.falsepattern.zigbrains.lsp.requests.ReformatHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.intellij.codeInsight.actions.ReformatCodeAction; import com.intellij.codeInsight.actions.ReformatCodeAction;
import com.intellij.lang.LanguageFormatting;
import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManager;
@ -51,7 +49,7 @@ public class LSPReformatAction extends ReformatCodeAction implements DumbAware {
super.actionPerformed(e); super.actionPerformed(e);
return; return;
} }
ApplicationUtils.writeAction(() -> FileDocumentManager.getInstance().saveDocument(editor.getDocument())); ApplicationUtil.writeAction(() -> FileDocumentManager.getInstance().saveDocument(editor.getDocument()));
// if editor hasSelection, only reformat selection, not reformat the whole file // if editor hasSelection, only reformat selection, not reformat the whole file
if (editor.getSelectionModel().hasSelection()) { if (editor.getSelectionModel().hasSelection()) {
ReformatHandler.reformatSelection(editor); ReformatHandler.reformatSelection(editor);

View file

@ -22,7 +22,6 @@ import com.intellij.codeInsight.actions.LayoutCodeDialog;
import com.intellij.codeInsight.actions.LayoutCodeOptions; import com.intellij.codeInsight.actions.LayoutCodeOptions;
import com.intellij.codeInsight.actions.ShowReformatFileDialog; import com.intellij.codeInsight.actions.ShowReformatFileDialog;
import com.intellij.codeInsight.actions.TextRangeType; import com.intellij.codeInsight.actions.TextRangeType;
import com.intellij.lang.LanguageFormatting;
import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;

View file

@ -15,10 +15,10 @@
*/ */
package com.falsepattern.zigbrains.lsp.client; package com.falsepattern.zigbrains.lsp.client;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase; import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.requests.WorkspaceEditHandler; import com.falsepattern.zigbrains.lsp.requests.WorkspaceEditHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;
import com.intellij.notification.NotificationAction; import com.intellij.notification.NotificationAction;
import com.intellij.notification.NotificationGroup; import com.intellij.notification.NotificationGroup;
@ -123,7 +123,7 @@ public class DefaultLanguageClient implements LanguageClient {
@Override @Override
public void publishDiagnostics(PublishDiagnosticsParams publishDiagnosticsParams) { public void publishDiagnostics(PublishDiagnosticsParams publishDiagnosticsParams) {
String uri = FileUtils.sanitizeURI(publishDiagnosticsParams.getUri()); String uri = FileUtil.sanitizeURI(publishDiagnosticsParams.getUri());
List<Diagnostic> diagnostics = publishDiagnosticsParams.getDiagnostics(); List<Diagnostic> diagnostics = publishDiagnosticsParams.getDiagnostics();
EditorEventManagerBase.diagnostics(uri, diagnostics); EditorEventManagerBase.diagnostics(uri, diagnostics);
} }
@ -134,7 +134,7 @@ public class DefaultLanguageClient implements LanguageClient {
String message = messageParams.getMessage(); String message = messageParams.getMessage();
if (isModal) { if (isModal) {
ApplicationUtils.invokeLater(() -> { ApplicationUtil.invokeLater(() -> {
MessageType msgType = messageParams.getType(); MessageType msgType = messageParams.getType();
switch (msgType) { switch (msgType) {
case Error: case Error:

View file

@ -15,7 +15,7 @@
*/ */
package com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition; package com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition;
import com.falsepattern.zigbrains.lsp.client.connection.StreamConnectionProvider; import com.falsepattern.zigbrains.lspcommon.connection.StreamConnectionProvider;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@ -24,7 +24,6 @@ import org.eclipse.lsp4j.InitializeParams;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URI;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -32,7 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
/** /**
* A trait representing a ServerDefinition * A trait representing a ServerDefinition
*/ */
public class LanguageServerDefinition { public abstract class LanguageServerDefinition {
private static final Logger LOG = Logger.getInstance(LanguageServerDefinition.class); private static final Logger LOG = Logger.getInstance(LanguageServerDefinition.class);
@ -75,18 +74,6 @@ public class LanguageServerDefinition {
} }
} }
/**
* Returns the initialization options for the given uri.
*
* @param uri file URI
* @return initialization options
* @deprecated use {@link #customizeInitializeParams(InitializeParams)} instead
*/
@Deprecated
public Object getInitializationOptions(URI uri) {
return null;
}
/** /**
* Use this method to modify the {@link InitializeParams} that was initialized by this library. The values * Use this method to modify the {@link InitializeParams} that was initialized by this library. The values
* assigned to the passed {@link InitializeParams} after this method ends will be the ones sent to the LSP server. * assigned to the passed {@link InitializeParams} after this method ends will be the ones sent to the LSP server.
@ -107,9 +94,7 @@ public class LanguageServerDefinition {
* @param workingDir The root directory * @param workingDir The root directory
* @return The stream connection provider * @return The stream connection provider
*/ */
public StreamConnectionProvider createConnectionProvider(String workingDir) { public abstract StreamConnectionProvider createConnectionProvider(String workingDir);
throw new UnsupportedOperationException();
}
public ServerListener getServerListener() { public ServerListener getServerListener() {
return ServerListener.DEFAULT; return ServerListener.DEFAULT;

View file

@ -15,8 +15,8 @@
*/ */
package com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition; package com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition;
import com.falsepattern.zigbrains.lsp.client.connection.ProcessStreamConnectionProvider; import com.falsepattern.zigbrains.lspcommon.connection.ProcessStreamConnectionProvider;
import com.falsepattern.zigbrains.lsp.client.connection.StreamConnectionProvider; import com.falsepattern.zigbrains.lspcommon.connection.StreamConnectionProvider;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;

View file

@ -15,8 +15,8 @@
*/ */
package com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition; package com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition;
import com.falsepattern.zigbrains.lsp.client.connection.ProcessStreamConnectionProvider; import com.falsepattern.zigbrains.lspcommon.connection.ProcessStreamConnectionProvider;
import com.falsepattern.zigbrains.lsp.client.connection.StreamConnectionProvider; import com.falsepattern.zigbrains.lspcommon.connection.StreamConnectionProvider;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;

View file

@ -15,6 +15,8 @@
*/ */
package com.falsepattern.zigbrains.lsp.client.languageserver.wrapper; package com.falsepattern.zigbrains.lsp.client.languageserver.wrapper;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient; import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.client.DefaultLanguageClient; import com.falsepattern.zigbrains.lsp.client.DefaultLanguageClient;
import com.falsepattern.zigbrains.lsp.client.ServerWrapperBaseClientContext; import com.falsepattern.zigbrains.lsp.client.ServerWrapperBaseClientContext;
@ -23,7 +25,6 @@ import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.DefaultRequestManager; import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.DefaultRequestManager;
import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager; import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager;
import com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition.LanguageServerDefinition; import com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition.LanguageServerDefinition;
import com.falsepattern.zigbrains.lsp.editor.DocumentEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager; import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase; import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.extensions.LSPExtensionManager; import com.falsepattern.zigbrains.lsp.extensions.LSPExtensionManager;
@ -33,7 +34,6 @@ import com.falsepattern.zigbrains.lsp.requests.Timeout;
import com.falsepattern.zigbrains.lsp.requests.Timeouts; import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.lsp.statusbar.LSPServerStatusWidget; import com.falsepattern.zigbrains.lsp.statusbar.LSPServerStatusWidget;
import com.falsepattern.zigbrains.lsp.statusbar.LSPServerStatusWidgetFactory; import com.falsepattern.zigbrains.lsp.statusbar.LSPServerStatusWidgetFactory;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils; import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.lsp.utils.LSPException; import com.falsepattern.zigbrains.lsp.utils.LSPException;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
@ -43,6 +43,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
@ -51,6 +52,7 @@ import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.remoteServer.util.CloudNotifier; import com.intellij.remoteServer.util.CloudNotifier;
import com.intellij.util.PlatformIcons; import com.intellij.util.PlatformIcons;
import lombok.val;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.lsp4j.ClientCapabilities; import org.eclipse.lsp4j.ClientCapabilities;
@ -104,6 +106,7 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -131,12 +134,12 @@ import static com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus.
* The implementation of a LanguageServerWrapper (specific to a serverDefinition and a project) * The implementation of a LanguageServerWrapper (specific to a serverDefinition and a project)
*/ */
public class LanguageServerWrapper { public class LanguageServerWrapper {
public final LanguageServerDefinition serverDefinition;
public LanguageServerDefinition serverDefinition;
private final LSPExtensionManager extManager; private final LSPExtensionManager extManager;
private final Project project; private final Project project;
private final HashSet<Editor> toConnect = new HashSet<>(); private final HashSet<Editor> toConnect = new HashSet<>();
private final String projectRootPath; @Nullable
private final Path projectRootPath;
private final HashSet<String> urisUnderLspControl = new HashSet<>(); private final HashSet<String> urisUnderLspControl = new HashSet<>();
private final HashSet<Editor> connectedEditors = new HashSet<>(); private final HashSet<Editor> connectedEditors = new HashSet<>();
private final Map<String, Set<EditorEventManager>> uriToEditorManagers = new HashMap<>(); private final Map<String, Set<EditorEventManager>> uriToEditorManagers = new HashMap<>();
@ -167,7 +170,12 @@ public class LanguageServerWrapper {
this.project = project; this.project = project;
// We need to keep the project rootPath in addition to the project instance, since we cannot get the project // We need to keep the project rootPath in addition to the project instance, since we cannot get the project
// base path if the project is disposed. // base path if the project is disposed.
this.projectRootPath = project.getBasePath(); val projectDir = ProjectUtil.guessProjectDir(project);
if (projectDir != null) {
this.projectRootPath = projectDir.toNioPath();
} else {
this.projectRootPath = null;
}
this.extManager = extManager; this.extManager = extManager;
projectToLanguageServerWrapper.put(project, this); projectToLanguageServerWrapper.put(project, this);
} }
@ -182,7 +190,7 @@ public class LanguageServerWrapper {
} }
public static LanguageServerWrapper forVirtualFile(VirtualFile file, Project project) { public static LanguageServerWrapper forVirtualFile(VirtualFile file, Project project) {
return uriToLanguageServerWrapper.get(new ImmutablePair<>(FileUtils.VFSToURI(file), FileUtils.projectToUri(project))); return uriToLanguageServerWrapper.get(new ImmutablePair<>(FileUtil.URIFromVirtualFile(file), FileUtils.projectToUri(project)));
} }
/** /**
@ -201,7 +209,7 @@ public class LanguageServerWrapper {
return serverDefinition; return serverDefinition;
} }
public String getProjectRootPath() { public Path getProjectRootPath() {
return projectRootPath; return projectRootPath;
} }
@ -237,7 +245,7 @@ public class LanguageServerWrapper {
String msg = String.format("%s \n is not initialized after %d seconds", String msg = String.format("%s \n is not initialized after %d seconds",
serverDefinition.toString(), Timeout.getTimeout(Timeouts.INIT) / 1000); serverDefinition.toString(), Timeout.getTimeout(Timeouts.INIT) / 1000);
LOG.warn(msg, e); LOG.warn(msg, e);
ApplicationUtils.invokeLater(() -> { ApplicationUtil.invokeLater(() -> {
if (!alreadyShownTimeout) { if (!alreadyShownTimeout) {
notifier.showMessage(msg, MessageType.WARNING); notifier.showMessage(msg, MessageType.WARNING);
alreadyShownTimeout = true; alreadyShownTimeout = true;
@ -280,7 +288,7 @@ public class LanguageServerWrapper {
return null; return null;
} }
VirtualFile currentOpenFile = selectedEditor.getFile(); VirtualFile currentOpenFile = selectedEditor.getFile();
VirtualFile requestedFile = FileUtils.virtualFileFromURI(uri); VirtualFile requestedFile = FileUtil.virtualFileFromURI(uri);
if (currentOpenFile == null || requestedFile == null) { if (currentOpenFile == null || requestedFile == null) {
return null; return null;
} }
@ -396,7 +404,7 @@ public class LanguageServerWrapper {
} }
// Triggers annotators since this is the first editor which starts the LS // Triggers annotators since this is the first editor which starts the LS
// and annotators are executed before LS is bootstrap to provide diagnostics. // and annotators are executed before LS is bootstrap to provide diagnostics.
ApplicationUtils.computableReadAction(() -> { ApplicationUtil.computableReadAction(() -> {
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (psiFile != null) { if (psiFile != null) {
DaemonCodeAnalyzer.getInstance(project).restart(psiFile); DaemonCodeAnalyzer.getInstance(project).restart(psiFile);
@ -449,7 +457,7 @@ public class LanguageServerWrapper {
launcherFuture.cancel(true); launcherFuture.cancel(true);
} }
if (serverDefinition != null) { if (serverDefinition != null) {
serverDefinition.stop(projectRootPath); serverDefinition.stop(projectRootPath != null ? projectRootPath.toString() : null);
} }
for (Editor ed : new HashSet<>(connectedEditors)) { for (Editor ed : new HashSet<>(connectedEditors)) {
disconnect(ed); disconnect(ed);
@ -496,7 +504,7 @@ public class LanguageServerWrapper {
if (status == STOPPED && !alreadyShownCrash && !alreadyShownTimeout) { if (status == STOPPED && !alreadyShownCrash && !alreadyShownTimeout) {
setStatus(STARTING); setStatus(STARTING);
try { try {
Pair<InputStream, OutputStream> streams = serverDefinition.start(projectRootPath); Pair<InputStream, OutputStream> streams = serverDefinition.start(projectRootPath != null ? projectRootPath.toString() : null);
InputStream inputStream = streams.getKey(); InputStream inputStream = streams.getKey();
OutputStream outputStream = streams.getValue(); OutputStream outputStream = streams.getValue();
InitializeParams initParams = getInitParams(); InitializeParams initParams = getInitParams();
@ -540,7 +548,7 @@ public class LanguageServerWrapper {
}); });
} catch (LSPException | IOException | URISyntaxException e) { } catch (LSPException | IOException | URISyntaxException e) {
LOG.warn(e); LOG.warn(e);
ApplicationUtils.invokeLater(() -> ApplicationUtil.invokeLater(() ->
notifier.showMessage(String.format("Can't start server due to %s", e.getMessage()), notifier.showMessage(String.format("Can't start server due to %s", e.getMessage()),
MessageType.WARNING)); MessageType.WARNING));
removeServerWrapper(); removeServerWrapper();
@ -550,7 +558,7 @@ public class LanguageServerWrapper {
private InitializeParams getInitParams() throws URISyntaxException { private InitializeParams getInitParams() throws URISyntaxException {
InitializeParams initParams = new InitializeParams(); InitializeParams initParams = new InitializeParams();
String projectRootUri = FileUtils.pathToUri(projectRootPath); String projectRootUri = FileUtil.pathToUri(projectRootPath);
WorkspaceFolder workspaceFolder = new WorkspaceFolder(projectRootUri, this.project.getName()); WorkspaceFolder workspaceFolder = new WorkspaceFolder(projectRootUri, this.project.getName());
initParams.setWorkspaceFolders(Collections.singletonList(workspaceFolder)); initParams.setWorkspaceFolders(Collections.singletonList(workspaceFolder));
@ -591,8 +599,6 @@ public class LanguageServerWrapper {
new ClientCapabilities(workspaceClientCapabilities, textDocumentClientCapabilities, null)); new ClientCapabilities(workspaceClientCapabilities, textDocumentClientCapabilities, null));
initParams.setClientInfo(new ClientInfo(ApplicationInfo.getInstance().getVersionName(), ApplicationInfo.getInstance().getFullVersion())); initParams.setClientInfo(new ClientInfo(ApplicationInfo.getInstance().getVersionName(), ApplicationInfo.getInstance().getFullVersion()));
// custom initialization options and initialize params provided by users
initParams.setInitializationOptions(serverDefinition.getInitializationOptions(URI.create(initParams.getWorkspaceFolders().get(0).getUri())));
serverDefinition.customizeInitializeParams(initParams); serverDefinition.customizeInitializeParams(initParams);
return initParams; return initParams;
} }
@ -639,7 +645,7 @@ public class LanguageServerWrapper {
if (crashCount <= 3) { if (crashCount <= 3) {
reconnect(); reconnect();
} else { } else {
ApplicationUtils.invokeLater(() -> { ApplicationUtil.invokeLater(() -> {
if (alreadyShownCrash) { if (alreadyShownCrash) {
reconnect(); reconnect();
} else { } else {
@ -679,7 +685,7 @@ public class LanguageServerWrapper {
List<String> connected = new ArrayList<>(); List<String> connected = new ArrayList<>();
urisUnderLspControl.forEach(s -> { urisUnderLspControl.forEach(s -> {
try { try {
connected.add(new URI(FileUtils.sanitizeURI(s)).toString()); connected.add(new URI(FileUtil.sanitizeURI(s)).toString());
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
LOG.warn(e); LOG.warn(e);
} }
@ -733,7 +739,7 @@ public class LanguageServerWrapper {
* @param projectUri The project root uri * @param projectUri The project root uri
*/ */
public void disconnect(String uri, String projectUri) { public void disconnect(String uri, String projectUri) {
uriToLanguageServerWrapper.remove(new ImmutablePair<>(FileUtils.sanitizeURI(uri), FileUtils.sanitizeURI(projectUri))); uriToLanguageServerWrapper.remove(new ImmutablePair<>(FileUtil.sanitizeURI(uri), FileUtil.sanitizeURI(projectUri)));
Set<EditorEventManager> managers = uriToEditorManagers.get(uri); Set<EditorEventManager> managers = uriToEditorManagers.get(uri);
if (managers == null) { if (managers == null) {
@ -752,7 +758,7 @@ public class LanguageServerWrapper {
} }
} }
urisUnderLspControl.remove(uri); urisUnderLspControl.remove(uri);
uriToLanguageServerWrapper.remove(new ImmutablePair<>(FileUtils.sanitizeURI(uri), FileUtils.sanitizeURI(projectUri))); uriToLanguageServerWrapper.remove(new ImmutablePair<>(FileUtil.sanitizeURI(uri), FileUtil.sanitizeURI(projectUri)));
} }
if (connectedEditors.isEmpty()) { if (connectedEditors.isEmpty()) {
stop(true); stop(true);
@ -787,7 +793,7 @@ public class LanguageServerWrapper {
* Reset language server wrapper state so it can be started again if it was failed earlier. * Reset language server wrapper state so it can be started again if it was failed earlier.
*/ */
public void restart() { public void restart() {
ApplicationUtils.pool(() -> { ApplicationUtil.pool(() -> {
if (isRestartable()) { if (isRestartable()) {
alreadyShownCrash = false; alreadyShownCrash = false;
alreadyShownTimeout = false; alreadyShownTimeout = false;

View file

@ -0,0 +1,92 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.contributors;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.backend.documentation.DocumentationLinkHandler;
import com.intellij.platform.backend.documentation.DocumentationTarget;
import com.intellij.platform.backend.documentation.LinkResolveResult;
import lombok.val;
import org.eclipse.lsp4j.Position;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.MalformedURLException;
import java.net.URL;
import static com.intellij.codeInsight.documentation.DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL;
public class LSPDocumentationLinkHandler implements DocumentationLinkHandler {
private static final String prefix = PSI_ELEMENT_PROTOCOL + "zigbrains://";
private final static Logger LOG = Logger.getInstance(LSPDocumentationLinkHandler.class);
@Override
public @Nullable LinkResolveResult resolveLink(@NotNull DocumentationTarget target, @NotNull String url) {
if (!url.startsWith(PSI_ELEMENT_PROTOCOL) || ! (target instanceof LSPDocumentationTargetProvider.LSPDocumentationTarget tgt)) {
return null;
}
url = url.replace(prefix, "file://");
val separator = url.indexOf("#L");
if (separator < 0)
return null;
val link = url.substring(0, separator);
final int line;
{
int theLine = 1;
try {
theLine = Integer.parseInt(url.substring(separator + 2));
} catch (NumberFormatException e) {
LOG.error("Could not parse file line: " + url.substring(separator + 2));
}
line = theLine - 1;
}
val app = ApplicationManager.getApplication();
app.executeOnPooledThread(() -> {
val project = tgt.file.getProject();
VirtualFile file;
try {
file = VfsUtil.findFileByURL(new URL(link));
} catch (MalformedURLException e1) {
LOG.warn("Syntax Exception occurred for uri: " + link);
return;
}
if (file == null)
return;
val descriptor = new OpenFileDescriptor(project, file);
app.invokeLater(() -> {
FileEditorManager.getInstance(project).openTextEditor(descriptor, true);
val editor = FileUtils.editorFromVirtualFile(file, project);
if (editor == null) {
return;
}
val logicalPos = DocumentUtils.getTabsAwarePosition(editor, new Position(line, 0));
if (logicalPos == null)
return;
editor.getCaretModel().moveToLogicalPosition(logicalPos);
editor.getScrollingModel().scrollTo(logicalPos, ScrollType.CENTER);
});
});
return null;
}
}

View file

@ -16,13 +16,14 @@
package com.falsepattern.zigbrains.lsp.contributors; package com.falsepattern.zigbrains.lsp.contributors;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase; import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.requests.HoverHandler; import com.falsepattern.zigbrains.lsp.requests.HoverHandler;
import com.falsepattern.zigbrains.lsp.requests.Timeout; import com.falsepattern.zigbrains.lsp.requests.Timeout;
import com.falsepattern.zigbrains.lsp.requests.Timeouts; import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils; import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils; import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter;
import com.intellij.model.Pointer; import com.intellij.model.Pointer;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
@ -33,6 +34,7 @@ import com.intellij.platform.backend.presentation.TargetPresentation;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.SmartPointerManager; import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiFileRange; import com.intellij.psi.SmartPsiFileRange;
import lombok.val;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.lsp4j.HoverParams; import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException; import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
@ -44,7 +46,8 @@ import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static com.intellij.codeInsight.documentation.DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL;
public class LSPDocumentationTargetProvider implements DocumentationTargetProvider { public class LSPDocumentationTargetProvider implements DocumentationTargetProvider {
@Override @Override
@ -57,7 +60,7 @@ public class LSPDocumentationTargetProvider implements DocumentationTargetProvid
public static class LSPDocumentationTarget implements DocumentationTarget { public static class LSPDocumentationTarget implements DocumentationTarget {
private final Pointer<LSPDocumentationTarget> pointer; private final Pointer<LSPDocumentationTarget> pointer;
private final PsiFile file; public final PsiFile file;
private final int offset; private final int offset;
public LSPDocumentationTarget(PsiFile file, int offset) { public LSPDocumentationTarget(PsiFile file, int offset) {
this.file = file; this.file = file;
@ -86,7 +89,7 @@ public class LSPDocumentationTargetProvider implements DocumentationTargetProvid
return null; return null;
} }
var caretPos = editor.offsetToLogicalPosition(offset); var caretPos = editor.offsetToLogicalPosition(offset);
var serverPos = ApplicationUtils.computableReadAction(() -> DocumentUtils.logicalToLSPPos(caretPos, editor)); var serverPos = ApplicationUtil.computableReadAction(() -> DocumentUtils.logicalToLSPPos(caretPos, editor));
return DocumentationResult.asyncDocumentation(() -> { return DocumentationResult.asyncDocumentation(() -> {
var identifier = manager.getIdentifier(); var identifier = manager.getIdentifier();
var request = wrapper.getRequestManager().hover(new HoverParams(identifier, serverPos)); var request = wrapper.getRequestManager().hover(new HoverParams(identifier, serverPos));
@ -102,13 +105,15 @@ public class LSPDocumentationTargetProvider implements DocumentationTargetProvid
return null; return null;
} }
String string = HoverHandler.getHoverString(hover); val markdown = HoverHandler.getHoverString(hover).replaceAll("file://", PSI_ELEMENT_PROTOCOL + "zigbrains://");
val string = ApplicationUtil.computableReadAction(() -> DocMarkdownToHtmlConverter
.convert(manager.getProject(), markdown));
if (StringUtils.isEmpty(string)) { if (StringUtils.isEmpty(string)) {
LOG.warn(String.format("Hover string returned is empty for file %s and pos (%d;%d)", LOG.warn(String.format("Hover string returned is empty for file %s and pos (%d;%d)",
identifier.getUri(), serverPos.getLine(), serverPos.getCharacter())); identifier.getUri(), serverPos.getLine(), serverPos.getCharacter()));
return null; return null;
} }
return DocumentationResult.documentation(string.lines().collect(Collectors.joining("<br>\n"))); return DocumentationResult.documentation(string);
} catch (TimeoutException e) { } catch (TimeoutException e) {
LOG.warn(e); LOG.warn(e);
wrapper.notifyFailure(Timeouts.HOVER); wrapper.notifyFailure(Timeouts.HOVER);

View file

@ -25,11 +25,10 @@ import com.intellij.codeInsight.hints.declarative.InlayTreeSink;
import com.intellij.codeInsight.hints.declarative.InlineInlayPosition; import com.intellij.codeInsight.hints.declarative.InlineInlayPosition;
import com.intellij.codeInsight.hints.declarative.OwnBypassCollector; import com.intellij.codeInsight.hints.declarative.OwnBypassCollector;
import com.intellij.codeInsight.hints.declarative.impl.DeclarativeInlayHintsPassFactory; import com.intellij.codeInsight.hints.declarative.impl.DeclarativeInlayHintsPassFactory;
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -92,12 +91,10 @@ public class LSPInlayHintProvider implements InlayHintsProvider {
tooltipText = switch (markup.getKind()) { tooltipText = switch (markup.getKind()) {
case "markdown" -> { case "markdown" -> {
var markedContent = markup.getValue(); var markedContent = markup.getValue();
if (markedContent.isEmpty()) { if (markedContent.isBlank()) {
yield ""; yield "";
} }
Parser parser = Parser.builder().build(); yield DocMarkdownToHtmlConverter.convert(manager.getProject(), markedContent);
HtmlRenderer renderer = HtmlRenderer.builder().build();
yield "<html>" + renderer.render(parser.parse(markedContent)) + "</html>";
} }
default -> markup.getValue(); default -> markup.getValue();
}; };

View file

@ -15,6 +15,7 @@
*/ */
package com.falsepattern.zigbrains.lsp.contributors.annotator; package com.falsepattern.zigbrains.lsp.contributors.annotator;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient; import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus; import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper; import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
@ -32,8 +33,10 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.util.SmartList; import com.intellij.util.SmartList;
import lombok.val;
import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.DiagnosticTag; import org.eclipse.lsp4j.DiagnosticTag;
@ -103,7 +106,7 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
VirtualFile virtualFile = file.getVirtualFile(); VirtualFile virtualFile = file.getVirtualFile();
if (FileUtils.isFileSupported(virtualFile) && IntellijLanguageClient.isExtensionSupported(virtualFile)) { if (FileUtils.isFileSupported(virtualFile) && IntellijLanguageClient.isExtensionSupported(virtualFile)) {
String uri = FileUtils.VFSToURI(virtualFile); String uri = FileUtil.URIFromVirtualFile(virtualFile);
// TODO annotations are applied to a file / document not to an editor. so store them by file and not by editor.. // TODO annotations are applied to a file / document not to an editor. so store them by file and not by editor..
EditorEventManager eventManager = EditorEventManagerBase.forUri(uri); EditorEventManager eventManager = EditorEventManagerBase.forUri(uri);
if (eventManager == null) { if (eventManager == null) {
@ -161,14 +164,17 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
} }
@Nullable @Nullable
protected AnnotationBuilder createAnnotation(Editor editor, AnnotationHolder holder, Diagnostic diagnostic) { protected TextRange getTextRange(Editor editor, Diagnostic diagnostic) {
final int start = DocumentUtils.LSPPosToOffset(editor, diagnostic.getRange().getStart()); final int start = DocumentUtils.LSPPosToOffset(editor, diagnostic.getRange().getStart());
final int end = DocumentUtils.LSPPosToOffset(editor, diagnostic.getRange().getEnd()); final int end = DocumentUtils.LSPPosToOffset(editor, diagnostic.getRange().getEnd());
if (start >= end) { if (start > end) {
return null; return null;
} }
final TextRange range = new TextRange(start, end); return new TextRange(start, end);
}
@NotNull
protected AnnotationBuilder createAnnotation(@NotNull TextRange range, AnnotationHolder holder, Diagnostic diagnostic) {
return holder.newAnnotation(lspToIntellijAnnotationsMap.get(diagnostic.getSeverity()), diagnostic.getMessage()) return holder.newAnnotation(lspToIntellijAnnotationsMap.get(diagnostic.getSeverity()), diagnostic.getMessage())
.range(range); .range(range);
} }
@ -176,18 +182,47 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
private void createAnnotations(AnnotationHolder holder, EditorEventManager eventManager) { private void createAnnotations(AnnotationHolder holder, EditorEventManager eventManager) {
final List<Diagnostic> diagnostics = eventManager.getDiagnostics(); final List<Diagnostic> diagnostics = eventManager.getDiagnostics();
final Editor editor = eventManager.editor; final Editor editor = eventManager.editor;
PsiFile file;
val document = editor.getDocument();
if (document != null) {
file = PsiDocumentManager.getInstance(eventManager.getProject()).getPsiFile(document);
} else {
file = null;
}
List<Annotation> annotations = new ArrayList<>(); List<Annotation> annotations = new ArrayList<>();
diagnostics.forEach(d -> { diagnostics.forEach(d -> {
var annotation = createAnnotation(editor, holder, d); var range = getTextRange(editor, d);
if (annotation != null) { if (range == null)
if (d.getTags() != null && d.getTags().contains(DiagnosticTag.Deprecated)) { return;
annotation = annotation.highlightType(ProblemHighlightType.LIKE_DEPRECATED); blk:
if (file != null && range.getLength() == 0) {
val psiElement = file.findElementAt(range.getStartOffset());
if (psiElement != null && !psiElement.getText().equals(".")) {
range = psiElement.getTextRange();
break blk;
}
val psiElementForward = file.findElementAt(range.getStartOffset() + 1);
if (psiElementForward != null) {
range = psiElementForward.getTextRange();
break blk;
}
val psiElementBack = file.findElementAt(range.getStartOffset() - 1);
if (psiElementBack != null) {
range = psiElementBack.getTextRange();
break blk;
}
if (psiElement != null) {
range = psiElement.getTextRange();
} }
annotation.create();
var theList = (SmartList<Annotation>) holder;
annotations.add(theList.get(theList.size() - 1));
} }
var annotation = createAnnotation(range, holder, d);
if (d.getTags() != null && d.getTags().contains(DiagnosticTag.Deprecated)) {
annotation = annotation.highlightType(ProblemHighlightType.LIKE_DEPRECATED);
}
annotation.create();
var theList = (SmartList<Annotation>) holder;
annotations.add(theList.get(theList.size() - 1));
}); });
eventManager.setAnnotations(annotations); eventManager.setAnnotations(annotations);

View file

@ -15,7 +15,7 @@
*/ */
package com.falsepattern.zigbrains.lsp.contributors.psi; package com.falsepattern.zigbrains.lsp.contributors.psi;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils; import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.utils.FileUtils; import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.lang.ASTNode; import com.intellij.lang.ASTNode;
import com.intellij.lang.Language; import com.intellij.lang.Language;
@ -700,7 +700,7 @@ public class LSPPsiElement extends PsiElementBase implements PsiNameIdentifierOw
if (editor == null) { if (editor == null) {
OpenFileDescriptor descriptor = new OpenFileDescriptor(getProject(), getContainingFile().getVirtualFile(), OpenFileDescriptor descriptor = new OpenFileDescriptor(getProject(), getContainingFile().getVirtualFile(),
getTextOffset()); getTextOffset());
ApplicationUtils.invokeLater(() -> ApplicationUtils ApplicationUtil.invokeLater(() -> ApplicationUtil
.writeAction(() -> FileEditorManager.getInstance(getProject()).openTextEditor(descriptor, false))); .writeAction(() -> FileEditorManager.getInstance(getProject()).openTextEditor(descriptor, false)));
} }
} }

View file

@ -15,6 +15,7 @@
*/ */
package com.falsepattern.zigbrains.lsp.contributors.symbol; package com.falsepattern.zigbrains.lsp.contributors.symbol;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient; import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus; import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager; import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager;
@ -70,7 +71,7 @@ public class WorkspaceSymbolProvider {
final SymbolInformation information = (result.getSymbolInformation() != null) ? final SymbolInformation information = (result.getSymbolInformation() != null) ?
result.getSymbolInformation() : from(result.getWorkspaceSymbol()); result.getSymbolInformation() : from(result.getWorkspaceSymbol());
final Location location = information.getLocation(); final Location location = information.getLocation();
final VirtualFile file = FileUtils.URIToVFS(location.getUri()); final VirtualFile file = FileUtil.virtualFileFromURI(location.getUri());
if (file != null) { if (file != null) {
final LSPIconProvider iconProviderFor = GUIUtils.getIconProviderFor(result.getDefinition()); final LSPIconProvider iconProviderFor = GUIUtils.getIconProviderFor(result.getDefinition());

View file

@ -15,23 +15,18 @@
*/ */
package com.falsepattern.zigbrains.lsp.editor; package com.falsepattern.zigbrains.lsp.editor;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper; import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils; import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import org.eclipse.lsp4j.DidChangeTextDocumentParams; import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams; import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams; import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent; import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem; import org.eclipse.lsp4j.TextDocumentItem;
@ -78,49 +73,50 @@ public class DocumentEventManager {
DidChangeTextDocumentParams changesParams = new DidChangeTextDocumentParams(new VersionedTextDocumentIdentifier(), DidChangeTextDocumentParams changesParams = new DidChangeTextDocumentParams(new VersionedTextDocumentIdentifier(),
Collections.singletonList(new TextDocumentContentChangeEvent())); Collections.singletonList(new TextDocumentContentChangeEvent()));
changesParams.getTextDocument().setUri(identifier.getUri()); changesParams.getTextDocument().setUri(identifier.getUri());
changesParams.getTextDocument().setVersion(++version); changesParams.getTextDocument().setVersion(++version);
if (syncKind == TextDocumentSyncKind.Incremental) { // TODO this incremental update logic is kinda broken, investigate later...
TextDocumentContentChangeEvent changeEvent = changesParams.getContentChanges().get(0); // if (syncKind == TextDocumentSyncKind.Incremental) {
CharSequence newText = event.getNewFragment(); // TextDocumentContentChangeEvent changeEvent = changesParams.getContentChanges().get(0);
int offset = event.getOffset(); // CharSequence newText = event.getNewFragment();
int newTextLength = event.getNewLength(); // int offset = event.getOffset();
// int newTextLength = event.getNewLength();
EditorEventManager editorEventManager = EditorEventManagerBase.forUri(FileUtils.documentToUri(document)); //
if (editorEventManager == null) { // EditorEventManager editorEventManager = EditorEventManagerBase.forUri(FileUtils.documentToUri(document));
LOG.warn("no editor associated with document"); // if (editorEventManager == null) {
return; // LOG.warn("no editor associated with document");
} // return;
Editor editor = editorEventManager.editor; // }
Position lspPosition = DocumentUtils.offsetToLSPPos(editor, offset); // Editor editor = editorEventManager.editor;
if (lspPosition == null) { // Position lspPosition = DocumentUtils.offsetToLSPPos(editor, offset);
return; // if (lspPosition == null) {
} // return;
int startLine = lspPosition.getLine(); // }
int startColumn = lspPosition.getCharacter(); // int startLine = lspPosition.getLine();
CharSequence oldText = event.getOldFragment(); // int startColumn = lspPosition.getCharacter();
// CharSequence oldText = event.getOldFragment();
//if text was deleted/replaced, calculate the end position of inserted/deleted text //
int endLine, endColumn; // //if text was deleted/replaced, calculate the end position of inserted/deleted text
if (oldText.length() > 0) { // int endLine, endColumn;
endLine = startLine + StringUtil.countNewLines(oldText); // if (oldText.length() > 0) {
String content = oldText.toString(); // endLine = startLine + StringUtil.countNewLines(oldText);
String[] oldLines = content.split("\n"); // String content = oldText.toString();
int oldTextLength = oldLines.length == 0 ? 0 : oldLines[oldLines.length - 1].length(); // String[] oldLines = content.split("\n");
endColumn = content.endsWith("\n") ? 0 : oldLines.length == 1 ? startColumn + oldTextLength : oldTextLength; // int oldTextLength = oldLines.length == 0 ? 0 : oldLines[oldLines.length - 1].length();
} else { //if insert or no text change, the end position is the same // endColumn = content.endsWith("\n") ? 0 : oldLines.length == 1 ? startColumn + oldTextLength : oldTextLength;
endLine = startLine; // } else { //if insert or no text change, the end position is the same
endColumn = startColumn; // endLine = startLine;
} // endColumn = startColumn;
Range range = new Range(new Position(startLine, startColumn), new Position(endLine, endColumn)); // }
changeEvent.setRange(range); // Range range = new Range(new Position(startLine, startColumn), new Position(endLine, endColumn));
changeEvent.setText(newText.toString()); // changeEvent.setRange(range);
} else if (syncKind == TextDocumentSyncKind.Full) { // changeEvent.setText(newText.toString());
// } else if (syncKind == TextDocumentSyncKind.Full) {
if (syncKind != TextDocumentSyncKind.None) {
changesParams.getContentChanges().get(0).setText(document.getText()); changesParams.getContentChanges().get(0).setText(document.getText());
} }
ApplicationUtils.pool(() -> wrapper.getRequestManager().didChange(changesParams)); // }
ApplicationUtil.pool(() -> wrapper.getRequestManager().didChange(changesParams));
} }
public void documentOpened() { public void documentOpened() {

View file

@ -15,6 +15,8 @@
*/ */
package com.falsepattern.zigbrains.lsp.editor; package com.falsepattern.zigbrains.lsp.editor;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.actions.LSPReferencesAction; import com.falsepattern.zigbrains.lsp.actions.LSPReferencesAction;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerOptions; import com.falsepattern.zigbrains.lsp.client.languageserver.ServerOptions;
import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager; import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager;
@ -25,11 +27,9 @@ import com.falsepattern.zigbrains.lsp.contributors.icon.LSPIconProvider;
import com.falsepattern.zigbrains.lsp.contributors.psi.LSPPsiElement; import com.falsepattern.zigbrains.lsp.contributors.psi.LSPPsiElement;
import com.falsepattern.zigbrains.lsp.contributors.rename.LSPRenameProcessor; import com.falsepattern.zigbrains.lsp.contributors.rename.LSPRenameProcessor;
import com.falsepattern.zigbrains.lsp.listeners.LSPCaretListenerImpl; import com.falsepattern.zigbrains.lsp.listeners.LSPCaretListenerImpl;
import com.falsepattern.zigbrains.lsp.requests.HoverHandler;
import com.falsepattern.zigbrains.lsp.requests.Timeout; import com.falsepattern.zigbrains.lsp.requests.Timeout;
import com.falsepattern.zigbrains.lsp.requests.Timeouts; import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.lsp.requests.WorkspaceEditHandler; import com.falsepattern.zigbrains.lsp.requests.WorkspaceEditHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils; import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils; import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.lsp.utils.GUIUtils; import com.falsepattern.zigbrains.lsp.utils.GUIUtils;
@ -54,15 +54,10 @@ import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.event.EditorMouseEvent; import com.intellij.openapi.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseListener;
import com.intellij.openapi.editor.event.EditorMouseMotionListener;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.fileEditor.OpenFileDescriptor;
@ -72,7 +67,6 @@ import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
@ -96,8 +90,6 @@ import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.DocumentRangeFormattingParams; import org.eclipse.lsp4j.DocumentRangeFormattingParams;
import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.FormattingOptions; import org.eclipse.lsp4j.FormattingOptions;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.InlayHint; import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintParams; import org.eclipse.lsp4j.InlayHintParams;
import org.eclipse.lsp4j.InsertReplaceEdit; import org.eclipse.lsp4j.InsertReplaceEdit;
@ -124,7 +116,6 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.Tuple; import org.eclipse.lsp4j.jsonrpc.messages.Tuple;
import org.eclipse.lsp4j.services.TextDocumentService; import org.eclipse.lsp4j.services.TextDocumentService;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.Icon; import javax.swing.Icon;
import java.awt.Point; import java.awt.Point;
@ -144,15 +135,14 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableReadAction; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.computableReadAction;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableWriteAction; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.computableWriteAction;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.invokeLater; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.invokeLater;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.pool; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.pool;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.writeAction; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.writeAction;
import static com.falsepattern.zigbrains.lsp.utils.DocumentUtils.toEither; import static com.falsepattern.zigbrains.lsp.utils.DocumentUtils.toEither;
import static com.falsepattern.zigbrains.lsp.utils.GUIUtils.createAndShowEditorHint; import static com.falsepattern.zigbrains.lsp.utils.GUIUtils.createAndShowEditorHint;
@ -171,7 +161,7 @@ import static com.falsepattern.zigbrains.lsp.utils.GUIUtils.createAndShowEditorH
public class EditorEventManager { public class EditorEventManager {
public final DocumentEventManager documentEventManager; public final DocumentEventManager documentEventManager;
protected Logger LOG = Logger.getInstance(EditorEventManager.class); protected static Logger LOG = Logger.getInstance(EditorEventManager.class);
public Editor editor; public Editor editor;
public LanguageServerWrapper wrapper; public LanguageServerWrapper wrapper;
@ -370,8 +360,8 @@ public class EditorEventManager {
res.forEach(l -> { res.forEach(l -> {
Position start = l.getRange().getStart(); Position start = l.getRange().getStart();
Position end = l.getRange().getEnd(); Position end = l.getRange().getEnd();
String uri = FileUtils.sanitizeURI(l.getUri()); String uri = FileUtil.sanitizeURI(l.getUri());
VirtualFile file = FileUtils.virtualFileFromURI(uri); VirtualFile file = FileUtil.virtualFileFromURI(uri);
if (fast) { if (fast) {
if (file == null) if (file == null)
return; return;
@ -1228,7 +1218,7 @@ public class EditorEventManager {
} }
public List<InlayHint> inlayHint() { public List<InlayHint> inlayHint() {
var range = ApplicationUtils.computableReadAction(() -> { var range = ApplicationUtil.computableReadAction(() -> {
var start = DocumentUtils.offsetToLSPPos(editor, 0); var start = DocumentUtils.offsetToLSPPos(editor, 0);
var end = DocumentUtils.offsetToLSPPos(editor, editor.getDocument().getTextLength()); var end = DocumentUtils.offsetToLSPPos(editor, editor.getDocument().getTextLength());
return new Range(start, end); return new Range(start, end);
@ -1295,14 +1285,16 @@ public class EditorEventManager {
} }
} }
// Tries to go to definition // Tries to go to definition
public void gotoDefinition(PsiElement element) { public boolean gotoDefinition(PsiElement element) {
if (editor.isDisposed()) { if (editor.isDisposed()) {
return; return false;
} }
val sourceOffset = element.getTextOffset(); val sourceOffset = element.getTextOffset();
val loc = requestDefinition(DocumentUtils.offsetToLSPPos(editor, sourceOffset)); val loc = requestDefinition(DocumentUtils.offsetToLSPPos(editor, sourceOffset));
if (loc == null)
return false;
gotoLocation(loc); return gotoLocation(project, loc);
} }
// Tries to go to declaration / show usages based on the element which is // Tries to go to declaration / show usages based on the element which is
@ -1322,7 +1314,7 @@ public class EditorEventManager {
return; return;
} }
String locUri = FileUtils.sanitizeURI(loc.getUri()); String locUri = FileUtil.sanitizeURI(loc.getUri());
if (identifier.getUri().equals(locUri) if (identifier.getUri().equals(locUri)
&& sourceOffset >= DocumentUtils.LSPPosToOffset(editor, loc.getRange().getStart()) && sourceOffset >= DocumentUtils.LSPPosToOffset(editor, loc.getRange().getStart())
@ -1333,11 +1325,11 @@ public class EditorEventManager {
referencesAction.forManagerAndOffset(this, sourceOffset); referencesAction.forManagerAndOffset(this, sourceOffset);
} }
} else { } else {
gotoLocation(loc); gotoLocation(project, loc);
} }
} }
public void gotoLocation(Location loc) { public static boolean gotoLocation(Project project, Location loc) {
VirtualFile file = null; VirtualFile file = null;
try { try {
file = VfsUtil.findFileByURL(new URL(loc.getUri())); file = VfsUtil.findFileByURL(new URL(loc.getUri()));
@ -1359,9 +1351,11 @@ public class EditorEventManager {
} }
} }
}); });
return true;
} else { } else {
LOG.warn("Empty file for " + loc.getUri()); LOG.warn("Empty file for " + loc.getUri());
} }
return false;
} }
public void requestAndShowCodeActions() { public void requestAndShowCodeActions() {

View file

@ -15,15 +15,12 @@
*/ */
package com.falsepattern.zigbrains.lsp.editor; package com.falsepattern.zigbrains.lsp.editor;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils; import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.utils.FileUtils; import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.lsp.utils.OSUtils;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import lombok.val; import lombok.val;
import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.Diagnostic;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -86,7 +83,7 @@ public class EditorEventManagerBase {
if (runOnRegistry.containsKey(manager.editor)) { if (runOnRegistry.containsKey(manager.editor)) {
var tasks = runOnRegistry.remove(manager.editor); var tasks = runOnRegistry.remove(manager.editor);
for (var task: tasks) { for (var task: tasks) {
ApplicationUtils.invokeLater(task); ApplicationUtil.invokeLater(task);
} }
} }
} }
@ -97,7 +94,7 @@ public class EditorEventManagerBase {
var manager = forEditor(editor); var manager = forEditor(editor);
if (manager != null) { if (manager != null) {
for (var task: runnables) { for (var task: runnables) {
ApplicationUtils.invokeLater(task); ApplicationUtil.invokeLater(task);
} }
} else { } else {
runOnRegistry.computeIfAbsent(editor, (ignored) -> new ArrayList<>()).addAll(List.of(runnables)); runOnRegistry.computeIfAbsent(editor, (ignored) -> new ArrayList<>()).addAll(List.of(runnables));

View file

@ -15,11 +15,12 @@
*/ */
package com.falsepattern.zigbrains.lsp.listeners; package com.falsepattern.zigbrains.lsp.listeners;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient; import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus; import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper; import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase; import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils; import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Document;
@ -27,7 +28,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileMoveEvent; import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams; import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.FileChangeType; import org.eclipse.lsp4j.FileChangeType;
import org.eclipse.lsp4j.FileEvent; import org.eclipse.lsp4j.FileEvent;
@ -51,7 +52,7 @@ class LSPFileEventManager {
* @param doc The document * @param doc The document
*/ */
static void willSave(Document doc) { static void willSave(Document doc) {
String uri = FileUtils.VFSToURI(FileDocumentManager.getInstance().getFile(doc)); String uri = FileUtil.URIFromVirtualFile(FileDocumentManager.getInstance().getFile(doc));
EditorEventManagerBase.willSave(uri); EditorEventManagerBase.willSave(uri);
} }
@ -72,16 +73,16 @@ class LSPFileEventManager {
if (!FileUtils.isFileSupported(file)) { if (!FileUtils.isFileSupported(file)) {
return; return;
} }
String uri = FileUtils.VFSToURI(file); String uri = FileUtil.URIFromVirtualFile(file);
if (uri == null) { if (uri == null) {
return; return;
} }
ApplicationUtils.invokeAfterPsiEvents(() -> { ApplicationUtil.invokeAfterPsiEvents(() -> {
EditorEventManagerBase.documentSaved(uri); EditorEventManagerBase.documentSaved(uri);
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri, FileUtils.findProjectsFor(file)
FileUtils.projectToUri(p), FileChangeType.Changed)); .forEach(p -> changedConfiguration(uri, FileUtils.projectToUri(p), FileChangeType.Changed));
}); }, false, false);
} }
/** /**
@ -89,19 +90,19 @@ class LSPFileEventManager {
* *
* @param event The file move event * @param event The file move event
*/ */
static void fileMoved(VirtualFileMoveEvent event) { static void fileMoved(VFileMoveEvent event) {
try { try {
VirtualFile file = event.getFile(); VirtualFile file = event.getFile();
if (!FileUtils.isFileSupported(file)) { if (!FileUtils.isFileSupported(file)) {
return; return;
} }
String newFileUri = FileUtils.VFSToURI(file); String newFileUri = FileUtil.URIFromVirtualFile(file);
String oldParentUri = FileUtils.VFSToURI(event.getOldParent()); String oldParentUri = FileUtil.URIFromVirtualFile(event.getOldParent());
if (newFileUri == null || oldParentUri == null) { if (newFileUri == null || oldParentUri == null) {
return; return;
} }
String oldFileUri = String.format("%s/%s", oldParentUri, event.getFileName()); String oldFileUri = String.format("%s/%s", oldParentUri, event.getFile().getName());
closeAndReopenAffectedFile(file, oldFileUri); closeAndReopenAffectedFile(file, oldFileUri);
} catch (Exception e) { } catch (Exception e) {
LOG.warn("LSP file move event failed due to :", e); LOG.warn("LSP file move event failed due to :", e);
@ -117,14 +118,14 @@ class LSPFileEventManager {
if (!FileUtils.isFileSupported(file)) { if (!FileUtils.isFileSupported(file)) {
return; return;
} }
String uri = FileUtils.VFSToURI(file); String uri = FileUtil.URIFromVirtualFile(file);
if (uri == null) { if (uri == null) {
return; return;
} }
ApplicationUtils.invokeAfterPsiEvents(() -> { ApplicationUtil.invokeAfterPsiEvents(() -> {
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri, FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri,
FileUtils.projectToUri(p), FileChangeType.Deleted)); FileUtils.projectToUri(p), FileChangeType.Deleted));
}); }, true, true);
} }
/** /**
@ -134,7 +135,7 @@ class LSPFileEventManager {
* @param newFileName the new file name * @param newFileName the new file name
*/ */
static void fileRenamed(String oldFileName, String newFileName) { static void fileRenamed(String oldFileName, String newFileName) {
ApplicationUtils.invokeAfterPsiEvents(() -> { ApplicationUtil.invokeAfterPsiEvents(() -> {
try { try {
// Getting the right file is not trivial here since we only have the file name. Since we have to iterate over // Getting the right file is not trivial here since we only have the file name. Since we have to iterate over
// all opened projects and filter based on the file name. // all opened projects and filter based on the file name.
@ -142,22 +143,24 @@ class LSPFileEventManager {
.flatMap(p -> searchFiles(newFileName, p).stream()) .flatMap(p -> searchFiles(newFileName, p).stream())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
for (VirtualFile file : files) { ApplicationUtil.invokeLater(() -> {
if (!FileUtils.isFileSupported(file)) { for (VirtualFile file : files) {
continue; if (!FileUtils.isFileSupported(file)) {
continue;
}
String newFileUri = FileUtil.URIFromVirtualFile(file);
String oldFileUri = newFileUri.replace(file.getName(), oldFileName);
closeAndReopenAffectedFile(file, oldFileUri);
} }
String newFileUri = FileUtils.VFSToURI(file); });
String oldFileUri = newFileUri.replace(file.getName(), oldFileName);
closeAndReopenAffectedFile(file, oldFileUri);
}
} catch (Exception e) { } catch (Exception e) {
LOG.warn("LSP file rename event failed due to : ", e); LOG.warn("LSP file rename event failed due to : ", e);
} }
}); }, true, false);
} }
private static void closeAndReopenAffectedFile(VirtualFile file, String oldFileUri) { private static void closeAndReopenAffectedFile(VirtualFile file, String oldFileUri) {
String newFileUri = FileUtils.VFSToURI(file); String newFileUri = FileUtil.URIFromVirtualFile(file);
// Notifies the language server. // Notifies the language server.
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(oldFileUri, FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(oldFileUri,
@ -172,7 +175,7 @@ class LSPFileEventManager {
if (!newFileUri.equals(oldFileUri)) { if (!newFileUri.equals(oldFileUri)) {
// Re-open file to so that the new editor will be connected to the language server. // Re-open file to so that the new editor will be connected to the language server.
FileEditorManager fileEditorManager = FileEditorManager.getInstance(p); FileEditorManager fileEditorManager = FileEditorManager.getInstance(p);
ApplicationUtils.invokeLater(() -> { ApplicationUtil.invokeLater(() -> {
fileEditorManager.closeFile(file); fileEditorManager.closeFile(file);
fileEditorManager.openFile(file, true); fileEditorManager.openFile(file, true);
}); });
@ -189,17 +192,17 @@ class LSPFileEventManager {
if (!FileUtils.isFileSupported(file)) { if (!FileUtils.isFileSupported(file)) {
return; return;
} }
String uri = FileUtils.VFSToURI(file); String uri = FileUtil.URIFromVirtualFile(file);
if (uri != null) { if (uri != null) {
ApplicationUtils.invokeAfterPsiEvents(() -> { ApplicationUtil.invokeAfterPsiEvents(() -> {
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri, FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri,
FileUtils.projectToUri(p), FileChangeType.Created)); FileUtils.projectToUri(p), FileChangeType.Created));
}); }, true, true);
} }
} }
private static void changedConfiguration(String uri, String projectUri, FileChangeType typ) { private static void changedConfiguration(String uri, String projectUri, FileChangeType typ) {
ApplicationUtils.pool(() -> { ApplicationUtil.pool(() -> {
DidChangeWatchedFilesParams params = getDidChangeWatchedFilesParams(uri, typ); DidChangeWatchedFilesParams params = getDidChangeWatchedFilesParams(uri, typ);
Set<LanguageServerWrapper> wrappers = IntellijLanguageClient.getAllServerWrappersFor(projectUri); Set<LanguageServerWrapper> wrappers = IntellijLanguageClient.getAllServerWrappersFor(projectUri);
if (wrappers == null) { if (wrappers == null) {

View file

@ -16,14 +16,42 @@
package com.falsepattern.zigbrains.lsp.listeners; package com.falsepattern.zigbrains.lsp.listeners;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileCopyEvent; import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.VirtualFileEvent; import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
import com.intellij.openapi.vfs.VirtualFileListener; import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent;
import com.intellij.openapi.vfs.VirtualFileMoveEvent; import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
import com.intellij.openapi.vfs.VirtualFilePropertyEvent; import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent;
import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent;
import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class VFSListener implements VirtualFileListener { import java.util.List;
public class VFSListener implements BulkFileListener {
@Override
public void before(@NotNull List<? extends @NotNull VFileEvent> events) {
}
@Override
public void after(@NotNull List<? extends @NotNull VFileEvent> events) {
for (val event: events) {
if (event instanceof VFilePropertyChangeEvent propEvent)
propertyChanged(propEvent);
else if (event instanceof VFileContentChangeEvent changeEvent)
contentsChanged(changeEvent);
else if (event instanceof VFileDeleteEvent deleteEvent)
fileDeleted(deleteEvent);
else if (event instanceof VFileMoveEvent moveEvent)
fileMoved(moveEvent);
else if (event instanceof VFileCopyEvent copyEvent)
fileCopied(copyEvent);
else if (event instanceof VFileCreateEvent createEvent)
fileCreated(createEvent);
}
}
/** /**
* Fired when a virtual file is renamed from within IDEA, or its writable status is changed. * Fired when a virtual file is renamed from within IDEA, or its writable status is changed.
@ -31,8 +59,7 @@ public class VFSListener implements VirtualFileListener {
* *
* @param event the event object containing information about the change. * @param event the event object containing information about the change.
*/ */
@Override public void propertyChanged(@NotNull VFilePropertyChangeEvent event) {
public void propertyChanged(@NotNull VirtualFilePropertyEvent event) {
if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) {
LSPFileEventManager.fileRenamed((String) event.getOldValue(), (String) event.getNewValue()); LSPFileEventManager.fileRenamed((String) event.getOldValue(), (String) event.getNewValue());
} }
@ -43,8 +70,7 @@ public class VFSListener implements VirtualFileListener {
* *
* @param event the event object containing information about the change. * @param event the event object containing information about the change.
*/ */
@Override public void contentsChanged(@NotNull VFileContentChangeEvent event) {
public void contentsChanged(@NotNull VirtualFileEvent event) {
LSPFileEventManager.fileChanged(event.getFile()); LSPFileEventManager.fileChanged(event.getFile());
} }
@ -53,8 +79,7 @@ public class VFSListener implements VirtualFileListener {
* *
* @param event the event object containing information about the change. * @param event the event object containing information about the change.
*/ */
@Override public void fileDeleted(@NotNull VFileDeleteEvent event) {
public void fileDeleted(@NotNull VirtualFileEvent event) {
LSPFileEventManager.fileDeleted(event.getFile()); LSPFileEventManager.fileDeleted(event.getFile());
} }
@ -63,8 +88,7 @@ public class VFSListener implements VirtualFileListener {
* *
* @param event the event object containing information about the change. * @param event the event object containing information about the change.
*/ */
@Override public void fileMoved(@NotNull VFileMoveEvent event) {
public void fileMoved(@NotNull VirtualFileMoveEvent event) {
LSPFileEventManager.fileMoved(event); LSPFileEventManager.fileMoved(event);
} }
@ -73,9 +97,8 @@ public class VFSListener implements VirtualFileListener {
* *
* @param event the event object containing information about the change. * @param event the event object containing information about the change.
*/ */
@Override public void fileCopied(@NotNull VFileCopyEvent event) {
public void fileCopied(@NotNull VirtualFileCopyEvent event) { LSPFileEventManager.fileCreated(event.findCreatedFile());
fileCreated(event);
} }
/** /**
@ -83,44 +106,7 @@ public class VFSListener implements VirtualFileListener {
* *
* @param event the event object containing information about the change. * @param event the event object containing information about the change.
*/ */
@Override public void fileCreated(@NotNull VFileCreateEvent event) {
public void fileCreated(@NotNull VirtualFileEvent event) {
LSPFileEventManager.fileCreated(event.getFile()); LSPFileEventManager.fileCreated(event.getFile());
} }
/**
* Fired before the change of a name or writable status of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforePropertyChange(@NotNull VirtualFilePropertyEvent event) {
}
/**
* Fired before the change of contents of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforeContentsChange(@NotNull VirtualFileEvent event) {
}
/**
* Fired before the deletion of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforeFileDeletion(@NotNull VirtualFileEvent event) {
}
/**
* Fired before the movement of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforeFileMovement(@NotNull VirtualFileMoveEvent event) {
}
} }

View file

@ -16,9 +16,6 @@
package com.falsepattern.zigbrains.lsp.requests; package com.falsepattern.zigbrains.lsp.requests;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.ui.UIUtil;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.MarkedString; import org.eclipse.lsp4j.MarkedString;
import org.eclipse.lsp4j.MarkupContent; import org.eclipse.lsp4j.MarkupContent;
@ -60,13 +57,9 @@ public class HoverHandler {
"```" + markedString.getLanguage() + " " + markedString.getValue() + "```" : "```" + markedString.getLanguage() + " " + markedString.getValue() + "```" :
""; "";
} }
Parser parser = Parser.builder().build(); result.add(string);
HtmlRenderer renderer = HtmlRenderer.builder().build();
if (!string.isEmpty()) {
result.add(renderer.render(parser.parse(string)));
}
} }
return "<html>" + String.join("\n\n", result) + "</html>"; return String.join("\n", result);
} else { } else {
return ""; return "";
} }
@ -75,9 +68,7 @@ public class HoverHandler {
if (markedContent.isEmpty()) { if (markedContent.isEmpty()) {
return ""; return "";
} }
Parser parser = Parser.builder().build(); return markedContent;
HtmlRenderer renderer = HtmlRenderer.builder().build();
return "<html>" + renderer.render(parser.parse(markedContent)) + "</html>";
} else { } else {
return ""; return "";
} }

View file

@ -15,10 +15,11 @@
*/ */
package com.falsepattern.zigbrains.lsp.requests; package com.falsepattern.zigbrains.lsp.requests;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.contributors.psi.LSPPsiElement; import com.falsepattern.zigbrains.lsp.contributors.psi.LSPPsiElement;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager; import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase; import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils; import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils; import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.CommandProcessor;
@ -57,8 +58,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.invokeLater; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.invokeLater;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.writeAction; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.writeAction;
import static com.falsepattern.zigbrains.lsp.utils.DocumentUtils.toEither; import static com.falsepattern.zigbrains.lsp.utils.DocumentUtils.toEither;
/** /**
@ -81,9 +82,9 @@ public class WorkspaceEditHandler {
TextEdit edit = new TextEdit(lspRange, newName); TextEdit edit = new TextEdit(lspRange, newName);
String uri = null; String uri = null;
try { try {
uri = FileUtils.sanitizeURI( uri = FileUtil.sanitizeURI(
new URL(ui.getVirtualFile().getUrl().replace(" ", FileUtils.SPACE_ENCODED)).toURI() new URL(ui.getVirtualFile().getUrl().replace(" ", FileUtil.SPACE_ENCODED)).toURI()
.toString()); .toString());
} catch (MalformedURLException | URISyntaxException e) { } catch (MalformedURLException | URISyntaxException e) {
LOG.warn(e); LOG.warn(e);
} }
@ -131,7 +132,7 @@ public class WorkspaceEditHandler {
TextDocumentEdit textEdit = tEdit.getLeft(); TextDocumentEdit textEdit = tEdit.getLeft();
VersionedTextDocumentIdentifier doc = textEdit.getTextDocument(); VersionedTextDocumentIdentifier doc = textEdit.getTextDocument();
int version = doc.getVersion() != null ? doc.getVersion() : Integer.MAX_VALUE; int version = doc.getVersion() != null ? doc.getVersion() : Integer.MAX_VALUE;
String uri = FileUtils.sanitizeURI(doc.getUri()); String uri = FileUtil.sanitizeURI(doc.getUri());
EditorEventManager manager = EditorEventManagerBase.forUri(uri); EditorEventManager manager = EditorEventManagerBase.forUri(uri);
if (manager != null) { if (manager != null) {
curProject[0] = manager.editor.getProject(); curProject[0] = manager.editor.getProject();
@ -151,7 +152,7 @@ public class WorkspaceEditHandler {
} else if (changes != null) { } else if (changes != null) {
changes.forEach((key, lChanges) -> { changes.forEach((key, lChanges) -> {
String uri = FileUtils.sanitizeURI(key); String uri = FileUtil.sanitizeURI(key);
EditorEventManager manager = EditorEventManagerBase.forUri(uri); EditorEventManager manager = EditorEventManagerBase.forUri(uri);
if (manager != null) { if (manager != null) {
@ -198,18 +199,18 @@ public class WorkspaceEditHandler {
Project[] projects = ProjectManager.getInstance().getOpenProjects(); Project[] projects = ProjectManager.getInstance().getOpenProjects();
//Infer the project from the uri //Infer the project from the uri
Project project = Stream.of(projects) Project project = Stream.of(projects)
.map(p -> new ImmutablePair<>(FileUtils.VFSToURI(ProjectUtil.guessProjectDir(p)), p)) .map(p -> new ImmutablePair<>(FileUtil.URIFromVirtualFile(ProjectUtil.guessProjectDir(p)), p))
.filter(p -> uri.startsWith(p.getLeft())).sorted(Collections.reverseOrder()) .filter(p -> uri.startsWith(p.getLeft())).sorted(Collections.reverseOrder())
.map(ImmutablePair::getRight).findFirst().orElse(projects[0]); .map(ImmutablePair::getRight).findFirst().orElse(projects[0]);
VirtualFile file = null; VirtualFile file = null;
try { try {
file = LocalFileSystem.getInstance().findFileByIoFile(new File(new URI(FileUtils.sanitizeURI(uri)))); file = LocalFileSystem.getInstance().findFileByIoFile(new File(new URI(FileUtil.sanitizeURI(uri))));
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
LOG.warn(e); LOG.warn(e);
} }
FileEditorManager fileEditorManager = FileEditorManager.getInstance(project); FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file); OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file);
Editor editor = ApplicationUtils Editor editor = ApplicationUtil
.computableWriteAction(() -> fileEditorManager.openTextEditor(descriptor, false)); .computableWriteAction(() -> fileEditorManager.openTextEditor(descriptor, false));
openedEditors.add(file); openedEditors.add(file);
curProject[0] = editor.getProject(); curProject[0] = editor.getProject();

View file

@ -31,7 +31,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableReadAction; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.computableReadAction;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;

View file

@ -15,6 +15,7 @@
*/ */
package com.falsepattern.zigbrains.lsp.utils; package com.falsepattern.zigbrains.lsp.utils;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient; import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.extensions.LSPExtensionManager; import com.falsepattern.zigbrains.lsp.extensions.LSPExtensionManager;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
@ -27,21 +28,19 @@ import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.LightVirtualFileBase; import com.intellij.testFramework.LightVirtualFileBase;
import lombok.val;
import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -50,19 +49,12 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableReadAction; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.computableReadAction;
/** /**
* Various file / uri related methods * Various file / uri related methods
*/ */
public class FileUtils { public class FileUtils {
private final static OS os = (System.getProperty("os.name").toLowerCase().contains("win")) ? OS.WINDOWS : OS.UNIX;
private final static String COLON_ENCODED = "%3A";
public final static String SPACE_ENCODED = "%20";
private final static String URI_FILE_BEGIN = "file:";
private final static String URI_VALID_FILE_BEGIN = "file:///";
private final static char URI_PATH_SEP = '/';
private static final Logger LOG = Logger.getInstance(FileUtils.class); private static final Logger LOG = Logger.getInstance(FileUtils.class);
public static List<Editor> getAllOpenedEditors(Project project) { public static List<Editor> getAllOpenedEditors(Project project) {
@ -83,7 +75,7 @@ public class FileUtils {
} }
public static List<Editor> getAllOpenedEditorsForUri(@NotNull Project project, String uri) { public static List<Editor> getAllOpenedEditorsForUri(@NotNull Project project, String uri) {
VirtualFile file = virtualFileFromURI(uri); VirtualFile file = FileUtil.virtualFileFromURI(uri);
if (file == null) if (file == null)
return Collections.emptyList(); return Collections.emptyList();
return getAllOpenedEditorsForVirtualFile(project, file); return getAllOpenedEditorsForVirtualFile(project, file);
@ -106,38 +98,13 @@ public class FileUtils {
}); });
} }
/**
* This can be used to instantly apply a language server definition without restarting the IDE.
*/
public static void reloadAllEditors() {
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
for (Project project : openProjects) {
reloadEditors(project);
}
}
/**
* This can be used to instantly apply a project-specific language server definition without restarting the
* project/IDE.
*
* @param project The project instance which need to be restarted
*/
public static void reloadEditors(@NotNull Project project) {
try {
List<Editor> allOpenedEditors = FileUtils.getAllOpenedEditors(project);
allOpenedEditors.forEach(IntellijLanguageClient::editorClosed);
allOpenedEditors.forEach(IntellijLanguageClient::editorOpened);
} catch (Exception e) {
LOG.warn(String.format("Refreshing project: %s is failed due to: ", project.getName()), e);
}
}
public static Editor editorFromPsiFile(PsiFile psiFile) { public static Editor editorFromPsiFile(PsiFile psiFile) {
return editorFromVirtualFile(psiFile.getVirtualFile(), psiFile.getProject()); return editorFromVirtualFile(psiFile.getVirtualFile(), psiFile.getProject());
} }
public static Editor editorFromUri(String uri, Project project) { public static Editor editorFromUri(String uri, Project project) {
return editorFromVirtualFile(virtualFileFromURI(uri), project); return editorFromVirtualFile(FileUtil.virtualFileFromURI(uri), project);
} }
@Nullable @Nullable
@ -149,15 +116,6 @@ public class FileUtils {
return null; return null;
} }
public static VirtualFile virtualFileFromURI(String uri) {
try {
return LocalFileSystem.getInstance().findFileByIoFile(new File(new URI(sanitizeURI(uri))));
} catch (URISyntaxException e) {
LOG.warn(e);
return null;
}
}
/** /**
* Returns a file type given an editor * Returns a file type given an editor
* *
@ -185,79 +143,14 @@ public class FileUtils {
* @return The URI * @return The URI
*/ */
public static String editorToURIString(Editor editor) { public static String editorToURIString(Editor editor) {
return sanitizeURI(VFSToURI(FileDocumentManager.getInstance().getFile(editor.getDocument()))); return FileUtil.sanitizeURI(
FileUtil.URIFromVirtualFile(FileDocumentManager.getInstance().getFile(editor.getDocument())));
} }
public static VirtualFile virtualFileFromEditor(Editor editor) { public static VirtualFile virtualFileFromEditor(Editor editor) {
return FileDocumentManager.getInstance().getFile(editor.getDocument()); return FileDocumentManager.getInstance().getFile(editor.getDocument());
} }
/**
* Returns the URI string corresponding to a VirtualFileSystem file
*
* @param file The file
* @return the URI
*/
public static String VFSToURI(VirtualFile file) {
return file == null? null : pathToUri(file.getPath());
}
/**
* Fixes common problems in uri, mainly related to Windows
*
* @param uri The uri to sanitize
* @return The sanitized uri
*/
public static String sanitizeURI(String uri) {
if (uri != null) {
StringBuilder reconstructed = new StringBuilder();
String uriCp = uri.replaceAll(" ", SPACE_ENCODED); //Don't trust servers
if (!uri.startsWith(URI_FILE_BEGIN)) {
LOG.warn("Malformed uri : " + uri);
return uri; //Probably not an uri
} else {
uriCp = uriCp.substring(URI_FILE_BEGIN.length());
while (uriCp.startsWith(Character.toString(URI_PATH_SEP))) {
uriCp = uriCp.substring(1);
}
reconstructed.append(URI_VALID_FILE_BEGIN);
if (os == OS.UNIX) {
return reconstructed.append(uriCp).toString();
} else {
reconstructed.append(uriCp.substring(0, uriCp.indexOf(URI_PATH_SEP)));
char driveLetter = reconstructed.charAt(URI_VALID_FILE_BEGIN.length());
if (Character.isLowerCase(driveLetter)) {
reconstructed.setCharAt(URI_VALID_FILE_BEGIN.length(), Character.toUpperCase(driveLetter));
}
if (reconstructed.toString().endsWith(COLON_ENCODED)) {
reconstructed.delete(reconstructed.length() - 3, reconstructed.length());
}
if (!reconstructed.toString().endsWith(":")) {
reconstructed.append(":");
}
return reconstructed.append(uriCp.substring(uriCp.indexOf(URI_PATH_SEP))).toString();
}
}
} else {
return null;
}
}
/**
* Transforms an URI string into a VFS file
*
* @param uri The uri
* @return The virtual file
*/
public static VirtualFile URIToVFS(String uri) {
try {
return LocalFileSystem.getInstance().findFileByIoFile(new File(new URI(sanitizeURI(uri))));
} catch (URISyntaxException e) {
LOG.warn(e);
return null;
}
}
/** /**
* Returns the project base dir uri given an editor * Returns the project base dir uri given an editor
* *
@ -265,42 +158,82 @@ public class FileUtils {
* @return The project whose the editor belongs * @return The project whose the editor belongs
*/ */
public static String editorToProjectFolderUri(Editor editor) { public static String editorToProjectFolderUri(Editor editor) {
return pathToUri(editorToProjectFolderPath(editor)); return FileUtil.pathToUri(editorToProjectFolderPath(editor));
} }
public static String editorToProjectFolderPath(Editor editor) { public static String editorToProjectFolderPath(Editor editor) {
if (editor != null && editor.getProject() != null && editor.getProject().getBasePath() != null) { if (editor == null)
return new File(editor.getProject().getBasePath()).getAbsolutePath(); return null;
}
return null;
}
/** val project = editor.getProject();
* Transforms a path into an URI string if (project == null)
* return null;
* @param path The path
* @return The uri val projectDir = ProjectUtil.guessProjectDir(editor.getProject());
*/ if (projectDir == null)
public static String pathToUri(@Nullable String path) { return null;
return path != null ? sanitizeURI(new File(path).toURI().toString()) : null;
return projectDir.toNioPath().toAbsolutePath().toString();
} }
public static String projectToUri(Project project) { public static String projectToUri(Project project) {
if (project != null && project.getBasePath() != null) { if (project == null)
return pathToUri(new File(project.getBasePath()).getAbsolutePath()); return null;
}
return null; val path = ProjectUtil.guessProjectDir(project);
if (path == null)
return null;
return FileUtil.pathToUri(path.toNioPath());
} }
public static String documentToUri(Document document) { public static String documentToUri(Document document) {
return sanitizeURI(VFSToURI(FileDocumentManager.getInstance().getFile(document))); return FileUtil.sanitizeURI(FileUtil.URIFromVirtualFile(FileDocumentManager.getInstance().getFile(document)));
} }
/** /**
* Object representing the OS type (Windows or Unix) * Find projects which contains the given file. This search runs among all open projects.
*/ */
public enum OS { @NotNull
WINDOWS, UNIX public static Set<Project> findProjectsFor(@NotNull VirtualFile file) {
return Arrays.stream(ProjectManager.getInstance().getOpenProjects())
.filter(p -> searchFiles(file.getName(), p).stream().anyMatch(f -> f.getPath().equals(file.getPath())))
.collect(Collectors.toSet());
}
public static Collection<VirtualFile> searchFiles(String fileName, Project p) {
try {
return computableReadAction(() -> FilenameIndex.getVirtualFilesByName(fileName, GlobalSearchScope.projectScope(p)));
} catch (Throwable t) {
// Todo - Find a proper way to handle when IDEA file indexing is in-progress.
return Collections.emptyList();
}
}
/**
* This can be used to instantly apply a language server definition without restarting the IDE.
*/
public static void reloadAllEditors() {
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
for (Project project : openProjects) {
reloadEditors(project);
}
}
/**
* This can be used to instantly apply a project-specific language server definition without restarting the
* project/IDE.
*
* @param project The project instance which need to be restarted
*/
public static void reloadEditors(@NotNull Project project) {
try {
List<Editor> allOpenedEditors = FileUtils.getAllOpenedEditors(project);
allOpenedEditors.forEach(IntellijLanguageClient::editorClosed);
allOpenedEditors.forEach(IntellijLanguageClient::editorOpened);
} catch (Exception e) {
LOG.warn(String.format("Refreshing project: %s is failed due to: ", project.getName()), e);
}
} }
/** /**
@ -322,25 +255,6 @@ public class FileUtils {
return IntellijLanguageClient.isExtensionSupported(file); return IntellijLanguageClient.isExtensionSupported(file);
} }
/**
* Find projects which contains the given file. This search runs among all open projects.
*/
@NotNull
public static Set<Project> findProjectsFor(@NotNull VirtualFile file) {
return Arrays.stream(ProjectManager.getInstance().getOpenProjects())
.filter(p -> searchFiles(file.getName(), p).stream().anyMatch(f -> f.getPath().equals(file.getPath())))
.collect(Collectors.toSet());
}
public static Collection<VirtualFile> searchFiles(String fileName, Project p) {
try {
return computableReadAction(() -> FilenameIndex.getVirtualFilesByName(fileName, GlobalSearchScope.projectScope(p)));
} catch (Throwable t) {
// Todo - Find a proper way to handle when IDEA file indexing is in-progress.
return Collections.emptyList();
}
}
/** /**
* Checks if the file in editor is supported by this LS client library. * Checks if the file in editor is supported by this LS client library.
*/ */

View file

@ -46,7 +46,7 @@ import java.net.URISyntaxException;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.writeAction; import static com.falsepattern.zigbrains.common.util.ApplicationUtil.writeAction;
public final class GUIUtils { public final class GUIUtils {
private static final LSPDefaultIconProvider DEFAULT_ICON_PROVIDER = new LSPDefaultIconProvider(); private static final LSPDefaultIconProvider DEFAULT_ICON_PROVIDER = new LSPDefaultIconProvider();

View file

@ -1,17 +0,0 @@
<!--
~ Copyright 2023-2024 FalsePattern
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<idea-plugin/>

View file

@ -0,0 +1,29 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.console;
import com.intellij.execution.filters.ConsoleFilterProvider;
import com.intellij.execution.filters.Filter;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
public class ZigConsoleFilterProvider implements ConsoleFilterProvider {
@Override
public Filter @NotNull [] getDefaultFilters(@NotNull Project project) {
return new Filter[]{new ZigSourceFileFilter(project)};
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.console;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.vfs.VirtualFile;
import kotlin.Pair;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Optional;
import java.util.regex.Pattern;
@RequiredArgsConstructor
public class ZigSourceFileFilter implements Filter {
private final Project project;
private final Pattern LEN_REGEX = Pattern.compile(":(\\d+):(\\d+)");
private Pair<Path, Integer> findLongestParsablePathFromOffset(String line, int end, Path projectPath) {
int longestStart = -1;
Path longest = null;
for (int i = end - 1; i >= 0; i--) {
try {
val pathStr = line.substring(i, end);
var path = Path.of(pathStr);
if (!(Files.exists(path) && Files.isRegularFile(path)) && projectPath != null) {
path = projectPath.resolve(pathStr);
if (!Files.exists(path) || !Files.isRegularFile(path))
continue;
}
longest = path;
longestStart = i;
} catch (InvalidPathException ignored){}
}
return new Pair<>(longest, longestStart);
}
@Nullable
@Override
public Result applyFilter(@NotNull String line, int entireLength) {
val lineStart = entireLength - line.length();
val projectPath = Optional.ofNullable(ProjectUtil.guessProjectDir(project)).map(VirtualFile::toNioPath).orElse(null);
val results = new ArrayList<ResultItem>();
val matcher = LEN_REGEX.matcher(line);
while (matcher.find()) {
val end = matcher.start();
val pair = findLongestParsablePathFromOffset(line, end, projectPath);
val path = pair.getFirst();
if (path == null)
return null;
val lineNumber = Math.max(Integer.parseInt(matcher.group(1)) - 1, 0);
val lineOffset = Math.max(Integer.parseInt(matcher.group(2)) - 1, 0);
val file = FileUtil.virtualFileFromURI(path.toUri());
results.add(new ResultItem(lineStart + pair.getSecond(), lineStart + matcher.end(), new OpenFileHyperlinkInfo(project, file, lineNumber, lineOffset)));
}
return new Result(results);
}
}

View file

@ -18,13 +18,13 @@ package com.falsepattern.zigbrains.project.execution;
import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessHandler; import com.intellij.execution.process.CapturingAnsiEscapesAwareProcessHandler;
import com.intellij.util.io.BaseOutputReader; import com.intellij.util.io.BaseOutputReader;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Optional; import java.util.Optional;
public class ZigCapturingProcessHandler extends CapturingProcessHandler { public class ZigCapturingProcessHandler extends CapturingAnsiEscapesAwareProcessHandler {
public static Optional<ZigCapturingProcessHandler> startProcess(GeneralCommandLine commandLine) { public static Optional<ZigCapturingProcessHandler> startProcess(GeneralCommandLine commandLine) {
try { try {
return Optional.of(new ZigCapturingProcessHandler(commandLine)); return Optional.of(new ZigCapturingProcessHandler(commandLine));

View file

@ -16,7 +16,6 @@
package com.falsepattern.zigbrains.project.execution.base; package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.zig.parser.ZigFile; import com.falsepattern.zigbrains.zig.parser.ZigFile;
import com.intellij.execution.actions.ConfigurationContext; import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.actions.LazyRunConfigurationProducer; import com.intellij.execution.actions.LazyRunConfigurationProducer;
@ -27,6 +26,8 @@ import com.intellij.psi.PsiElement;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends LazyRunConfigurationProducer<T> { public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends LazyRunConfigurationProducer<T> {
@NotNull @NotNull
@Override @Override
@ -47,7 +48,7 @@ public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends
return false; return false;
} }
var theFile = psiFile.getVirtualFile(); var theFile = psiFile.getVirtualFile();
var filePath = theFile.getPath(); var filePath = theFile.toNioPath();
return setupConfigurationFromContext(configuration, element, filePath, theFile); return setupConfigurationFromContext(configuration, element, filePath, theFile);
} }
@ -65,7 +66,7 @@ public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends
return false; return false;
} }
val vFile = file.getVirtualFile(); val vFile = file.getVirtualFile();
val filePath = vFile.getPath(); val filePath = vFile.toNioPath();
return isConfigurationFromContext(configuration, filePath, vFile, element); return isConfigurationFromContext(configuration, filePath, vFile, element);
} }
@ -95,6 +96,6 @@ public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends
} }
*/ */
protected abstract boolean setupConfigurationFromContext(@NotNull T configuration, PsiElement element, String filePath, VirtualFile theFile); protected abstract boolean setupConfigurationFromContext(@NotNull T configuration, PsiElement element, Path filePath, VirtualFile theFile);
protected abstract boolean isConfigurationFromContext(@NotNull T configuration, String filePath, VirtualFile vFile, PsiElement element); protected abstract boolean isConfigurationFromContext(@NotNull T configuration, Path filePath, VirtualFile vFile, PsiElement element);
} }

View file

@ -0,0 +1,24 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.execution.base;
public enum OptimizationLevel {
Debug,
ReleaseFast,
ReleaseSafe,
ReleaseSmall
}

View file

@ -16,10 +16,10 @@
package com.falsepattern.zigbrains.project.execution.base; package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.project.execution.ZigCapturingProcessHandler;
import com.falsepattern.zigbrains.project.runconfig.ZigProcessHandler; import com.falsepattern.zigbrains.project.runconfig.ZigProcessHandler;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.ProjectUtil; import com.falsepattern.zigbrains.project.util.ProjectUtil;
import com.intellij.build.BuildTextConsoleView;
import com.intellij.execution.DefaultExecutionResult; import com.intellij.execution.DefaultExecutionResult;
import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.CommandLineState; import com.intellij.execution.configurations.CommandLineState;
@ -31,6 +31,7 @@ import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections;
public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends CommandLineState { public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends CommandLineState {
protected final T configuration; protected final T configuration;
@ -42,18 +43,20 @@ public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends C
@Override @Override
protected @NotNull ProcessHandler startProcess() throws ExecutionException { protected @NotNull ProcessHandler startProcess() throws ExecutionException {
return new ZigCapturingProcessHandler(getCommandLine(ProjectUtil.getToolchain(getEnvironment().getProject()))); return new ZigProcessHandler(getCommandLine(ProjectUtil.getToolchain(getEnvironment().getProject()), false));
} }
public GeneralCommandLine getCommandLine(AbstractZigToolchain toolchain) { public GeneralCommandLine getCommandLine(AbstractZigToolchain toolchain, boolean debug) throws ExecutionException {
val workingDirectory = configuration.workingDirectory; val workingDirectory = configuration.getWorkingDirectory();
val zigExecutablePath = toolchain.pathToExecutable("zig"); val zigExecutablePath = toolchain.pathToExecutable("zig");
return new GeneralCommandLine().withExePath(zigExecutablePath.toString()) val cli = new GeneralCommandLine();
.withWorkDirectory(workingDirectory.toString()) cli.setExePath(zigExecutablePath.toString());
.withCharset(StandardCharsets.UTF_8) workingDirectory.getPath().ifPresent(x -> cli.setWorkDirectory(x.toFile()));
.withRedirectErrorStream(true) cli.setCharset(StandardCharsets.UTF_8);
.withParameters(configuration.buildCommandLineArgs()); cli.setRedirectErrorStream(true);
cli.addParameters(debug ? configuration.buildDebugCommandLineArgs() : configuration.buildCommandLineArgs());
return cli;
} }
public T configuration() { public T configuration() {
@ -63,7 +66,7 @@ public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends C
public DefaultExecutionResult executeCommandLine(GeneralCommandLine commandLine, ExecutionEnvironment environment) public DefaultExecutionResult executeCommandLine(GeneralCommandLine commandLine, ExecutionEnvironment environment)
throws ExecutionException { throws ExecutionException {
val handler = startProcess(commandLine); val handler = startProcess(commandLine);
val console = getConsoleBuilder().getConsole(); val console = new BuildTextConsoleView(environment.getProject(), false, Collections.emptyList());
console.attachToProcess(handler); console.attachToProcess(handler);
return new DefaultExecutionResult(console, handler); return new DefaultExecutionResult(console, handler);
} }

View file

@ -18,75 +18,462 @@ package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.project.ui.WorkingDirectoryComponent; import com.falsepattern.zigbrains.project.ui.WorkingDirectoryComponent;
import com.falsepattern.zigbrains.project.ui.ZigFilePathPanel; import com.falsepattern.zigbrains.project.ui.ZigFilePathPanel;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.falsepattern.zigbrains.project.util.ElementUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.ui.LabeledComponent; import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.dsl.builder.AlignX; import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.AlignY; import com.intellij.ui.dsl.builder.AlignY;
import com.intellij.ui.dsl.builder.Panel; import com.intellij.ui.dsl.builder.Panel;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.val;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.JComponent; import javax.swing.JComponent;
import java.io.Serializable;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Objects; import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static com.intellij.ui.dsl.builder.BuilderKt.panel; import static com.intellij.ui.dsl.builder.BuilderKt.panel;
public class ZigConfigEditor<T extends ZigExecConfigBase<T>> extends SettingsEditor<T> { public class ZigConfigEditor<T extends ZigExecConfigBase<T>> extends SettingsEditor<T> {
protected final LabeledComponent<TextFieldWithBrowseButton> workingDirectoryComponent = private final ZigExecConfigBase<T> state;
new WorkingDirectoryComponent(); private final List<ZigConfigurable.ZigConfigModule<?>> configModules = new ArrayList<>();
public ZigConfigEditor(ZigExecConfigBase<T> state) {
this.state = state;
}
@Override @Override
protected void applyEditorTo(@NotNull T s) throws ConfigurationException { protected void applyEditorTo(@NotNull T s) throws ConfigurationException {
s.workingDirectory = Paths.get(workingDirectoryComponent.getComponent().getText()); try {
outer:
for (val cfg : s.getConfigurables()) {
for (val module : configModules) {
if (module.tryApply(cfg))
continue outer;
}
System.err.println("EEE");
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
} }
@Override @Override
protected void resetEditorFrom(@NotNull T s) { protected void resetEditorFrom(@NotNull T s) {
workingDirectoryComponent.getComponent().setText(Objects.requireNonNullElse(s.workingDirectory, "").toString()); outer:
for (val cfg: s.getConfigurables()) {
for (val module: configModules) {
if (module.tryReset(cfg))
continue outer;
}
}
} }
@Override @Override
protected final @NotNull JComponent createEditor() { protected final @NotNull JComponent createEditor() {
configModules.clear();
configModules.addAll(state.getConfigurables().stream().map(ZigConfigurable::createEditor).toList());
return panel((p) -> { return panel((p) -> {
constructPanel(p); for (val module: configModules) {
module.construct(p);
}
return null; return null;
}); });
} }
protected void constructPanel(Panel p) { @Override
p.row(workingDirectoryComponent.getLabel(), (r) -> { protected void disposeEditor() {
r.cell(workingDirectoryComponent).resizableColumn().align(AlignX.FILL).align(AlignY.FILL); for (val module: configModules) {
return null; module.dispose();
}); }
configModules.clear();
} }
public static abstract class WithFilePath<T extends ZigExecConfigBase<T>> extends ZigConfigEditor<T> { public interface ZigConfigurable<T extends ZigConfigurable<T>> extends Serializable, Cloneable {
private final ZigFilePathPanel filePathPanel = new ZigFilePathPanel(); void readExternal(@NotNull Element element);
void writeExternal(@NotNull Element element);
ZigConfigModule<T> createEditor();
T clone();
@Override interface ZigConfigModule<T extends ZigConfigurable<T>> extends Disposable {
protected void applyEditorTo(@NotNull T s) throws ConfigurationException { @Nullable T tryMatch(ZigConfigurable<?> cfg);
super.applyEditorTo(s); void apply(T configurable) throws ConfigurationException;
setFilePath(s, filePathPanel.getText()); void reset(T configurable);
default boolean tryApply(ZigConfigurable<?> cfg) throws ConfigurationException {
val x = tryMatch(cfg);
if (x != null) {
apply(x);
return true;
}
return false;
}
default boolean tryReset(ZigConfigurable<?> cfg) {
val x = tryMatch(cfg);
if (x != null) {
reset(x);
return true;
}
return false;
}
void construct(Panel p);
}
}
public static abstract class PathConfigurable<T extends PathConfigurable<T>> implements ZigConfigurable<T> {
private @Nullable Path path = null;
public Optional<Path> getPath() {
return Optional.ofNullable(path);
}
public @NotNull Path getPathOrThrow() {
return getPath().orElseThrow(() -> new IllegalArgumentException("Empty file path!"));
}
public void setPath(@Nullable Path path) {
this.path = path;
} }
@Override @Override
protected void resetEditorFrom(@NotNull T s) { public void readExternal(@NotNull Element element) {
super.resetEditorFrom(s); try {
filePathPanel.setText(Objects.requireNonNullElse(getFilePath(s), "")); ElementUtil.readString(element, getSerializedName()).map(Paths::get).ifPresent(x -> path = x);
} catch (InvalidPathException ignored){}
} }
@Override @Override
protected void constructPanel(Panel p) { public void writeExternal(@NotNull Element element) {
super.constructPanel(p); ElementUtil.writeString(element, getSerializedName(), path == null ? null : path.toString());
p.row("Target file", (r) -> { }
r.cell(filePathPanel).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null; @Override
@SneakyThrows
public T clone() {
return (T) super.clone();
}
protected abstract String getSerializedName();
public abstract static class PathConfigModule<T extends PathConfigurable<T>> implements ZigConfigModule<T> {
@Override
public void apply(T s) throws ConfigurationException {
try {
s.setPath(Paths.get(getString()));
} catch (InvalidPathException e) {
throw new ConfigurationException(e.getMessage(), e, "Invalid Path");
}
}
@Override
public void reset(T s) {
setString(s.getPath().map(Path::toString).orElse(""));
}
protected abstract String getString();
protected abstract void setString(String str);
}
}
@Getter(AccessLevel.PROTECTED)
@RequiredArgsConstructor
public static class WorkDirectoryConfigurable extends PathConfigurable<WorkDirectoryConfigurable> {
private transient final String serializedName;
@Override
public WorkDirectoryConfigModule createEditor() {
return new WorkDirectoryConfigModule(serializedName);
}
@RequiredArgsConstructor
public static class WorkDirectoryConfigModule extends PathConfigModule<WorkDirectoryConfigurable> {
private final String serializedName;
@Override
public @Nullable WorkDirectoryConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof WorkDirectoryConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
protected String getString() {
return workingDirectoryComponent.getComponent().getText();
}
@Override
protected void setString(String str) {
workingDirectoryComponent.getComponent().setText(str);
}
private final WorkingDirectoryComponent workingDirectoryComponent = new WorkingDirectoryComponent(this);
@Override
public void construct(Panel p) {
p.row(workingDirectoryComponent.getLabel(), (r) -> {
r.cell(workingDirectoryComponent).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
}
@Override
public void dispose() {
workingDirectoryComponent.dispose();
}
}
}
@RequiredArgsConstructor
public static class FilePathConfigurable extends PathConfigurable<FilePathConfigurable> {
@Getter(AccessLevel.PROTECTED)
private transient final String serializedName;
private transient final String guiLabel;
@Override
public FilePathConfigModule createEditor() {
return new FilePathConfigModule(serializedName, guiLabel);
}
@RequiredArgsConstructor
public static class FilePathConfigModule extends PathConfigModule<FilePathConfigurable> {
private final String serializedName;
private final String label;
private final ZigFilePathPanel filePathPanel = new ZigFilePathPanel();
@Override
public @Nullable FilePathConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof FilePathConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
protected String getString() {
return filePathPanel.getText();
}
@Override
protected void setString(String str) {
filePathPanel.setText(str);
}
@Override
public void construct(Panel p) {
p.row(label, (r) -> {
r.cell(filePathPanel).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
}
@Override
public void dispose() {
}
}
}
@RequiredArgsConstructor
public static class ColoredConfigurable implements ZigConfigurable<ColoredConfigurable> {
private transient final String serializedName;
public boolean colored = true;
@Override
public void readExternal(@NotNull Element element) {
ElementUtil.readBoolean(element, serializedName).ifPresent(x -> colored = x);
}
@Override
public void writeExternal(@NotNull Element element) {
ElementUtil.writeBoolean(element, serializedName, colored);
}
@Override
public ColoredConfigModule createEditor() {
return new ColoredConfigModule(serializedName);
}
@Override
@SneakyThrows
public ColoredConfigurable clone() {
return (ColoredConfigurable) super.clone();
}
@RequiredArgsConstructor
public static class ColoredConfigModule implements ZigConfigModule<ColoredConfigurable> {
private final String serializedName;
private final JBCheckBox checkBox = new JBCheckBox();
@Override
public @Nullable ColoredConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof ColoredConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
public void apply(ColoredConfigurable s) throws ConfigurationException {
s.colored = checkBox.isSelected();
}
@Override
public void reset(ColoredConfigurable s) {
checkBox.setSelected(s.colored);
}
@Override
public void construct(Panel p) {
p.row("Colored terminal", (r) -> {
r.cell(checkBox);
return null;
});
}
@Override
public void dispose() {
}
}
}
@RequiredArgsConstructor
public static class OptimizationConfigurable implements ZigConfigurable<OptimizationConfigurable> {
private transient final String serializedName;
public OptimizationLevel level = OptimizationLevel.Debug;
public boolean forced = false;
@Override
public void readExternal(@NotNull Element element) {
ElementUtil.readChild(element, serializedName).ifPresent(child -> {
ElementUtil.readEnum(child, "level", OptimizationLevel.class).ifPresent(x -> level = x);
ElementUtil.readBoolean(child,"forced").ifPresent(x -> forced = x);
}); });
} }
protected abstract String getFilePath(T config); @Override
protected abstract void setFilePath(T config, String path); public void writeExternal(@NotNull Element element) {
val child = ElementUtil.writeChild(element, serializedName);
ElementUtil.writeEnum(child, "level", level);
ElementUtil.writeBoolean(child, "forced", forced);
}
@Override
public OptimizationConfigModule createEditor() {
return new OptimizationConfigModule(serializedName);
}
@Override
@SneakyThrows
public OptimizationConfigurable clone() {
return (OptimizationConfigurable) super.clone();
}
@RequiredArgsConstructor
public static class OptimizationConfigModule implements ZigConfigModule<OptimizationConfigurable> {
private final String serializedName;
private final ComboBox<OptimizationLevel> levels = new ComboBox<>(OptimizationLevel.values());
private final JBCheckBox forced = new JBCheckBox("Force even in debug runs");
@Override
public @Nullable OptimizationConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof OptimizationConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
public void apply(OptimizationConfigurable s) throws ConfigurationException {
s.level = levels.getItem();
s.forced = forced.isSelected();
}
@Override
public void reset(OptimizationConfigurable s) {
levels.setItem(s.level);
forced.setSelected(s.forced);
}
@Override
public void construct(Panel p) {
p.row("Optimization level", (r) -> {
r.cell(levels);
r.cell(forced);
return null;
});
}
@Override
public void dispose() {
}
}
}
@RequiredArgsConstructor
public static class ArgsConfigurable implements ZigConfigurable<ArgsConfigurable> {
private transient final String serializedName;
private transient final String guiName;
public String[] args = new String[0];
@Override
public void readExternal(@NotNull Element element) {
ElementUtil.readStrings(element, serializedName).ifPresent(x -> args = x);
}
@Override
public void writeExternal(@NotNull Element element) {
ElementUtil.writeStrings(element, serializedName, args);
}
@Override
public ArgsConfigModule createEditor() {
return new ArgsConfigModule(serializedName, guiName);
}
@Override
@SneakyThrows
public ArgsConfigurable clone() {
return (ArgsConfigurable) super.clone();
}
@RequiredArgsConstructor
public static class ArgsConfigModule implements ZigConfigModule<ArgsConfigurable> {
private final String serializedName;
private final String guiName;
private final JBTextField argsField = new JBTextField();
@Override
public @Nullable ArgsConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof ArgsConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
public void apply(ArgsConfigurable s) throws ConfigurationException {
s.args = CLIUtil.translateCommandline(argsField.getText());
}
@Override
public void reset(ArgsConfigurable s) {
argsField.setText(String.join(" ", s.args));
}
@Override
public void construct(Panel p) {
p.row(guiName, (r) -> {
r.cell(argsField).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
}
@Override
public void dispose() {
}
}
} }
} }

View file

@ -22,24 +22,60 @@ import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.LocatableConfigurationBase; import com.intellij.execution.configurations.LocatableConfigurationBase;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.NlsActions; import com.intellij.openapi.util.NlsActions;
import com.intellij.openapi.vfs.VirtualFile;
import lombok.Getter;
import lombok.val;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Getter
public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends LocatableConfigurationBase<ProfileStateBase<T>> { public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends LocatableConfigurationBase<ProfileStateBase<T>> {
public @Nullable Path workingDirectory; private ZigConfigEditor.WorkDirectoryConfigurable workingDirectory = new ZigConfigEditor.WorkDirectoryConfigurable("workingDirectory");
public ZigExecConfigBase(@NotNull Project project, @NotNull ConfigurationFactory factory, @Nullable String name) { public ZigExecConfigBase(@NotNull Project project, @NotNull ConfigurationFactory factory, @Nullable String name) {
super(project, factory, name); super(project, factory, name);
workingDirectory = project.isDefault() ? null : Optional.ofNullable(project.getBasePath()) workingDirectory.setPath(getProject().isDefault() ? null : Optional.ofNullable(ProjectUtil.guessProjectDir(getProject()))
.map(Path::of) .map(VirtualFile::toNioPath)
.orElse(null); .orElse(null));
}
@Override
public @NotNull ZigConfigEditor<T> getConfigurationEditor() {
return new ZigConfigEditor<>(this);
}
@Override
public void readExternal(@NotNull Element element) throws InvalidDataException {
super.readExternal(element);
getConfigurables().forEach(cfg -> cfg.readExternal(element));
}
@Override
public void writeExternal(@NotNull Element element) {
super.writeExternal(element);
getConfigurables().forEach(cfg -> cfg.writeExternal(element));
} }
public abstract String[] buildCommandLineArgs(); public abstract String[] buildCommandLineArgs();
public String[] buildDebugCommandLineArgs() {
return buildCommandLineArgs();
}
@Override
public T clone() {
val myClone = (ZigExecConfigBase<?>) super.clone();
myClone.workingDirectory = workingDirectory.clone();
return (T) myClone;
}
@Override @Override
public abstract @Nullable @NlsActions.ActionText String suggestedName(); public abstract @Nullable @NlsActions.ActionText String suggestedName();
@ -47,4 +83,7 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
public abstract @Nullable ProfileStateBase<T> getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) public abstract @Nullable ProfileStateBase<T> getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment)
throws ExecutionException; throws ExecutionException;
public @NotNull List<ZigConfigEditor.@NotNull ZigConfigurable<?>> getConfigurables() {
return List.of(workingDirectory);
}
} }

View file

@ -0,0 +1,49 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.execution.binary;
import com.falsepattern.zigbrains.zig.Icons;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.ConfigurationTypeBase;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class ConfigTypeBinary extends ConfigurationTypeBase {
public static final String IDENTIFIER = "ZIGBRAINS_BINARY";
public ConfigTypeBinary() {
super(IDENTIFIER, "Zig-compiled native executable", "Binary executable compiled from zig code", Icons.ZIG);
addFactory(new ConfigFactoryBinary());
}
public class ConfigFactoryBinary extends ConfigurationFactory {
public ConfigFactoryBinary() {
super(ConfigTypeBinary.this);
}
@Override
public @NotNull RunConfiguration createTemplateConfiguration(@NotNull Project project) {
return new ZigExecConfigBinary(project, this);
}
@Override
public @NotNull @NonNls String getId() {
return IDENTIFIER;
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.execution.binary;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.runners.ExecutionEnvironment;
import lombok.val;
import java.nio.charset.StandardCharsets;
public class ProfileStateBinary extends ProfileStateBase<ZigExecConfigBinary> {
public ProfileStateBinary(ExecutionEnvironment environment, ZigExecConfigBinary configuration) {
super(environment, configuration);
}
@Override
public GeneralCommandLine getCommandLine(AbstractZigToolchain toolchain, boolean debug) throws ExecutionException {
val cli = new GeneralCommandLine();
val cfg = configuration();
cfg.getWorkingDirectory().getPath().ifPresent(dir -> cli.setWorkDirectory(dir.toFile()));
cli.setExePath(cfg.getExePath().getPath().orElseThrow(() -> new ExecutionException("Missing executable path")).toString());
cli.setCharset(StandardCharsets.UTF_8);
cli.setRedirectErrorStream(true);
cli.addParameters(cfg.getArgs().args);
return cli;
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.execution.binary;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.project.Project;
import lombok.Getter;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@Getter
public class ZigExecConfigBinary extends ZigExecConfigBase<ZigExecConfigBinary> {
private ZigConfigEditor.FilePathConfigurable exePath = new ZigConfigEditor.FilePathConfigurable("exePath", "Executable program path (not the zig compiler)");
private ZigConfigEditor.ArgsConfigurable args = new ZigConfigEditor.ArgsConfigurable("args", "Command line arguments");
public ZigExecConfigBinary(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig-compiled native executable");
}
@Override
public String[] buildCommandLineArgs() {
return args.args;
}
@Override
public @Nullable String suggestedName() {
return "Executable";
}
@Override
public ZigExecConfigBinary clone() {
val clone = super.clone();
clone.exePath = exePath.clone();
clone.args = args.clone();
return clone;
}
@Override
public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
return CollectionUtil.concat(super.getConfigurables(), exePath, args);
}
@Override
public @Nullable ProfileStateBinary getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) {
return new ProfileStateBinary(environment, this);
}
}

View file

@ -20,8 +20,11 @@ import com.falsepattern.zigbrains.project.execution.base.ConfigProducerBase;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild> { public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild> {
@Override @Override
public @NotNull ConfigurationFactory getConfigurationFactory() { public @NotNull ConfigurationFactory getConfigurationFactory() {
@ -29,7 +32,7 @@ public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild>
} }
@Override @Override
protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, PsiElement element, String filePath, VirtualFile theFile) { protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, PsiElement element, Path filePath, VirtualFile theFile) {
if (ZigLineMarkerBuild.UTILITY_INSTANCE.elementMatches(element)) { if (ZigLineMarkerBuild.UTILITY_INSTANCE.elementMatches(element)) {
configuration.setName("Build"); configuration.setName("Build");
return true; return true;
@ -38,7 +41,11 @@ public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild>
} }
@Override @Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, String filePath, VirtualFile vFile, PsiElement element) { protected boolean isConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, Path filePath, VirtualFile vFile, PsiElement element) {
return true; val p = configuration.getWorkingDirectory().getPath();
if (p.isEmpty())
return false;
val path = p.get();
return filePath.getParent().equals(path);
} }
} }

View file

@ -16,41 +16,33 @@
package com.falsepattern.zigbrains.project.execution.build; package com.falsepattern.zigbrains.project.execution.build;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor; import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase; import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.project.util.ElementUtil;
import com.intellij.execution.Executor; import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException; import lombok.Getter;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.AlignY;
import com.intellij.ui.dsl.builder.Panel;
import lombok.val; import lombok.val;
import org.apache.groovy.util.Arrays;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.List;
@Getter
public class ZigExecConfigBuild extends ZigExecConfigBase<ZigExecConfigBuild> { public class ZigExecConfigBuild extends ZigExecConfigBase<ZigExecConfigBuild> {
public String extraArguments = ""; private ZigConfigEditor.ArgsConfigurable extraArgs = new ZigConfigEditor.ArgsConfigurable("extraArgs", "Extra command line arguments");
private ZigConfigEditor.ColoredConfigurable colored = new ZigConfigEditor.ColoredConfigurable("colored");
private ZigConfigEditor.FilePathConfigurable exePath = new ZigConfigEditor.FilePathConfigurable("exePath", "Output executable created by the build (for debugging)");
public ZigExecConfigBuild(@NotNull Project project, @NotNull ConfigurationFactory factory) { public ZigExecConfigBuild(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig Build"); super(project, factory, "Zig Build");
} }
@Override @Override
public String[] buildCommandLineArgs() { public String[] buildCommandLineArgs() {
val base = new String[]{"build"}; val base = new String[]{"build", "--color", colored.colored ? "on" : "off"};
if (extraArguments.isBlank()) { return CollectionUtil.concat(base, extraArgs.args);
return base;
} else {
return Arrays.concat(base, extraArguments.split(" "));
}
} }
@Override @Override
@ -59,54 +51,21 @@ public class ZigExecConfigBuild extends ZigExecConfigBase<ZigExecConfigBuild> {
} }
@Override @Override
public @NotNull Editor getConfigurationEditor() { public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
return new Editor(); return CollectionUtil.concat(super.getConfigurables(), extraArgs, colored, exePath);
}
@Override
public ZigExecConfigBuild clone() {
val clone = super.clone();
clone.extraArgs = extraArgs.clone();
clone.colored = colored.clone();
clone.exePath = exePath.clone();
return clone;
} }
@Override @Override
public @Nullable ProfileStateBuild getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) { public @Nullable ProfileStateBuild getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) {
return new ProfileStateBuild(environment, this); return new ProfileStateBuild(environment, this);
} }
@Override
public void readExternal(@NotNull Element element) throws InvalidDataException {
super.readExternal(element);
val extraArguments = ElementUtil.readString(element, "extraArguments");
if (extraArguments != null) {
this.extraArguments = extraArguments;
}
}
@Override
public void writeExternal(@NotNull Element element) {
super.writeExternal(element);
ElementUtil.writeString(element, "extraArguments", extraArguments);
}
public static class Editor extends ZigConfigEditor<ZigExecConfigBuild> {
private final JBTextField extraArgs = new JBTextField();
@Override
protected void applyEditorTo(@NotNull ZigExecConfigBuild s) throws ConfigurationException {
super.applyEditorTo(s);
s.extraArguments = extraArgs.getText();
}
@Override
protected void resetEditorFrom(@NotNull ZigExecConfigBuild s) {
super.resetEditorFrom(s);
extraArgs.setText(Objects.requireNonNullElse(s.extraArguments, ""));
}
@Override
protected void constructPanel(Panel p) {
super.constructPanel(p);
p.row("Extra arguments", (r) -> {
r.cell(extraArgs).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
}
}
} }

View file

@ -23,6 +23,9 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.Objects;
public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> { public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> {
@Override @Override
public @NotNull ConfigurationFactory getConfigurationFactory() { public @NotNull ConfigurationFactory getConfigurationFactory() {
@ -30,9 +33,9 @@ public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> {
} }
@Override @Override
protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigRun configuration, PsiElement element, String filePath, VirtualFile theFile) { protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigRun configuration, PsiElement element, Path filePath, VirtualFile theFile) {
if (ZigLineMarkerRun.UTILITY_INSTANCE.elementMatches(element)) { if (ZigLineMarkerRun.UTILITY_INSTANCE.elementMatches(element)) {
configuration.filePath = filePath; configuration.getFilePath().setPath(filePath);
configuration.setName(theFile.getPresentableName()); configuration.setName(theFile.getPresentableName());
return true; return true;
} }
@ -40,8 +43,8 @@ public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> {
} }
@Override @Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigRun configuration, String filePath, VirtualFile vFile, PsiElement element) { protected boolean isConfigurationFromContext(@NotNull ZigExecConfigRun configuration, Path filePath, VirtualFile vFile, PsiElement element) {
return configuration.filePath.equals(filePath); return Objects.equals(configuration.getFilePath().getPath().orElse(null), filePath);
} }
@Override @Override

View file

@ -16,31 +16,42 @@
package com.falsepattern.zigbrains.project.execution.run; package com.falsepattern.zigbrains.project.execution.run;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase; import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor; import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.util.ElementUtil; import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.intellij.execution.Executor; import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.val;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@Setter import java.util.List;
@Getter @Getter
public class ZigExecConfigRun extends ZigExecConfigBase<ZigExecConfigRun> { public class ZigExecConfigRun extends ZigExecConfigBase<ZigExecConfigRun> {
public String filePath = ""; private ZigConfigEditor.FilePathConfigurable filePath = new ZigConfigEditor.FilePathConfigurable("filePath", "File Path");
private ZigConfigEditor.ColoredConfigurable colored = new ZigConfigEditor.ColoredConfigurable("colored");
private ZigConfigEditor.OptimizationConfigurable optimization = new ZigConfigEditor.OptimizationConfigurable("optimization");
private ZigConfigEditor.ArgsConfigurable exeArgs = new ZigConfigEditor.ArgsConfigurable("exeArgs", "Arguments for the compile exe");
public ZigExecConfigRun(@NotNull Project project, @NotNull ConfigurationFactory factory) { public ZigExecConfigRun(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig Run"); super(project, factory, "Zig Run");
} }
@Override @Override
public String[] buildCommandLineArgs() { public String[] buildCommandLineArgs() {
return new String[]{"run", filePath}; return CollectionUtil.concat(new String[]{"run", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "-O", optimization.level.name(), "--"}, exeArgs.args);
}
@Override
public String[] buildDebugCommandLineArgs() {
if (optimization.forced) {
return new String[]{"build-exe", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "-O", optimization.level.name()};
} else {
return new String[]{"build-exe", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString()};
}
} }
@Override @Override
@ -49,42 +60,22 @@ public class ZigExecConfigRun extends ZigExecConfigBase<ZigExecConfigRun> {
} }
@Override @Override
public @NotNull Editor getConfigurationEditor() { public ZigExecConfigRun clone() {
return new Editor(); val clone = super.clone();
clone.filePath = filePath.clone();
clone.colored = colored.clone();
clone.optimization = optimization.clone();
clone.exeArgs = exeArgs.clone();
return clone;
}
@Override
public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
return CollectionUtil.concat(super.getConfigurables(), filePath, optimization, colored);
} }
@Override @Override
public @Nullable ProfileStateRun getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) { public @Nullable ProfileStateRun getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) {
return new ProfileStateRun(environment, this); return new ProfileStateRun(environment, this);
} }
@Override
public void readExternal(@NotNull Element element) throws InvalidDataException {
super.readExternal(element);
var filePath = ElementUtil.readString(element, "filePath");
if (filePath != null) {
this.filePath = filePath;
}
}
@Override
public void writeExternal(@NotNull Element element) {
super.writeExternal(element);
ElementUtil.writeString(element, "filePath", filePath);
}
public static class Editor extends ZigConfigEditor.WithFilePath<ZigExecConfigRun> {
@Override
protected String getFilePath(ZigExecConfigRun config) {
return config.filePath;
}
@Override
protected void setFilePath(ZigExecConfigRun config, String path) {
config.filePath = path;
}
}
} }

View file

@ -24,6 +24,9 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.Objects;
public class ConfigProducerTest extends ConfigProducerBase<ZigExecConfigTest> { public class ConfigProducerTest extends ConfigProducerBase<ZigExecConfigTest> {
@Override @Override
public @NotNull ConfigurationFactory getConfigurationFactory() { public @NotNull ConfigurationFactory getConfigurationFactory() {
@ -31,9 +34,9 @@ public class ConfigProducerTest extends ConfigProducerBase<ZigExecConfigTest> {
} }
@Override @Override
protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigTest configuration, PsiElement element, String filePath, VirtualFile theFile) { protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigTest configuration, PsiElement element, Path filePath, VirtualFile theFile) {
if (ZigLineMarkerTest.UTILITY_INSTANCE.elementMatches(element)) { if (ZigLineMarkerTest.UTILITY_INSTANCE.elementMatches(element)) {
configuration.filePath = filePath; configuration.getFilePath().setPath(filePath);
configuration.setName("all tests in " + theFile.getPresentableName()); configuration.setName("all tests in " + theFile.getPresentableName());
return true; return true;
} }
@ -41,8 +44,8 @@ public class ConfigProducerTest extends ConfigProducerBase<ZigExecConfigTest> {
} }
@Override @Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigTest configuration, String filePath, VirtualFile vFile, PsiElement element) { protected boolean isConfigurationFromContext(@NotNull ZigExecConfigTest configuration, Path filePath, VirtualFile vFile, PsiElement element) {
return configuration.filePath.equals(filePath); return Objects.equals(configuration.getFilePath().getPath().orElse(null), filePath);
} }
@Override @Override

View file

@ -16,27 +16,42 @@
package com.falsepattern.zigbrains.project.execution.test; package com.falsepattern.zigbrains.project.execution.test;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor; import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.intellij.execution.Executor; import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import lombok.Getter;
import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List;
@Getter
public class ZigExecConfigTest extends ZigExecConfigBase<ZigExecConfigTest> { public class ZigExecConfigTest extends ZigExecConfigBase<ZigExecConfigTest> {
public String filePath = ""; private ZigConfigEditor.FilePathConfigurable filePath = new ZigConfigEditor.FilePathConfigurable("filePath", "File path");
private ZigConfigEditor.ColoredConfigurable colored = new ZigConfigEditor.ColoredConfigurable("colored");
private ZigConfigEditor.OptimizationConfigurable optimization = new ZigConfigEditor.OptimizationConfigurable("optimization");
public ZigExecConfigTest(@NotNull Project project, @NotNull ConfigurationFactory factory) { public ZigExecConfigTest(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig Test"); super(project, factory, "Zig Test");
} }
@Override @Override
public String[] buildCommandLineArgs() { public String[] buildCommandLineArgs() {
return new String[]{"test", filePath}; return new String[]{"test", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "-O", optimization.level.name()};
}
@Override
public String[] buildDebugCommandLineArgs() {
if (optimization.forced) {
return new String[]{"test", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "--test-no-exec", "-O", optimization.level.name()};
} else {
return new String[]{"test", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "--test-no-exec"};
}
} }
@Override @Override
@ -50,20 +65,16 @@ public class ZigExecConfigTest extends ZigExecConfigBase<ZigExecConfigTest> {
} }
@Override @Override
public @NotNull SettingsEditor<? extends RunConfiguration> getConfigurationEditor() { public ZigExecConfigTest clone() {
return new Editor(); val clone = super.clone();
clone.filePath = filePath.clone();
clone.colored = colored.clone();
clone.optimization = optimization.clone();
return clone;
} }
public static class Editor extends ZigConfigEditor.WithFilePath<ZigExecConfigTest> { @Override
public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
@Override return CollectionUtil.concat(super.getConfigurables(), filePath, optimization, colored);
protected String getFilePath(ZigExecConfigTest config) {
return config.filePath;
}
@Override
protected void setFilePath(ZigExecConfigTest config, String path) {
config.filePath = path;
}
} }
} }

Some files were not shown because too many files have changed in this diff Show more