PROJECT STUFF!

This commit is contained in:
FalsePattern 2024-11-02 23:08:57 +01:00
parent 4a132be408
commit a7e3f3e161
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
99 changed files with 4533 additions and 180 deletions

1
.gitignore vendored
View file

@ -60,3 +60,4 @@ gradle-app.setting
.intellijPlatform .intellijPlatform
jbr jbr
secrets secrets
!modules/**/src/**/build/

View file

@ -5,6 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
plugins { plugins {
kotlin("jvm") version "1.9.24" apply false kotlin("jvm") version "1.9.24" apply false
kotlin("plugin.serialization") version "1.9.24" apply false
id("org.jetbrains.intellij.platform") version "2.1.0" id("org.jetbrains.intellij.platform") version "2.1.0"
id("org.jetbrains.changelog") version "2.2.1" id("org.jetbrains.changelog") version "2.2.1"
id("org.jetbrains.grammarkit") version "2022.3.2.2" apply false id("org.jetbrains.grammarkit") version "2022.3.2.2" apply false
@ -81,7 +82,6 @@ dependencies {
} }
implementation(project(":core")) implementation(project(":core"))
implementation(project(":lsp"))
} }
intellijPlatform { intellijPlatform {

View file

@ -4,12 +4,21 @@ import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
plugins { plugins {
id("org.jetbrains.grammarkit") id("org.jetbrains.grammarkit")
kotlin("plugin.serialization")
} }
val lsp4ijVersion: String by project
val lsp4jVersion: String by project
val lsp4ijNightly = property("lsp4ijNightly").toString().toBoolean()
val lsp4ijDepString = "${if (lsp4ijNightly) "nightly." else ""}com.jetbrains.plugins:com.redhat.devtools.lsp4ij:$lsp4ijVersion"
dependencies { dependencies {
intellijPlatform { intellijPlatform {
create(IntelliJPlatformType.IntellijIdeaCommunity, providers.gradleProperty("ideaCommunityVersion")) create(IntelliJPlatformType.IntellijIdeaCommunity, providers.gradleProperty("ideaCommunityVersion"))
} }
intellijPlatformPluginDependency(lsp4ijDepString)
compileOnly("org.eclipse.lsp4j:org.eclipse.lsp4j:$lsp4jVersion")
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3")
} }
//region grammars //region grammars

View file

@ -22,6 +22,7 @@
package com.falsepattern.zigbrains.lsp package com.falsepattern.zigbrains.lsp
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
@ -68,7 +69,7 @@ class ZigLanguageServerFactory: LanguageServerFactory, LanguageServerEnablementS
class ZLSStarter: LanguageServerStarter { class ZLSStarter: LanguageServerStarter {
override fun startLSP(project: Project, restart: Boolean) { override fun startLSP(project: Project, restart: Boolean) {
project.service<ZigLSPProjectService>().cs.launch { project.zigCoroutineScope.launch {
val manager = project.service<LanguageServerManager>() val manager = project.service<LanguageServerManager>()
val status = manager.getServerStatus("ZigBrains") val status = manager.getServerStatus("ZigBrains")
if ((status == ServerStatus.started || status == ServerStatus.starting) && !restart) if ((status == ServerStatus.started || status == ServerStatus.starting) && !restart)

View file

@ -25,8 +25,10 @@
package com.falsepattern.zigbrains.lsp.config package com.falsepattern.zigbrains.lsp.config
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
@Serializable
data class ZLSConfig( data class ZLSConfig(
@SerialName("zig_exe_path") val zigExePath: @NonNls String? = null, @SerialName("zig_exe_path") val zigExePath: @NonNls String? = null,
@SerialName("zig_lib_path") val zigLibPath: @NonNls String? = null, @SerialName("zig_lib_path") val zigLibPath: @NonNls String? = null,

View file

@ -24,17 +24,16 @@ package com.falsepattern.zigbrains.lsp.settings
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
@JvmRecord
data class ZLSSettings( data class ZLSSettings(
val direnv: Boolean = true, var direnv: Boolean = true,
val zlsPath: @NonNls String = "", var zlsPath: @NonNls String = "",
val zlsConfigPath: @NonNls String = "", var zlsConfigPath: @NonNls String = "",
val debug: Boolean = false, var debug: Boolean = false,
val messageTrace: Boolean = false, var messageTrace: Boolean = false,
val buildOnSave: Boolean = false, var buildOnSave: Boolean = false,
val buildOnSaveStep: @NonNls String = "install", var buildOnSaveStep: @NonNls String = "install",
val globalVarDeclarations: Boolean = false, var globalVarDeclarations: Boolean = false,
val comptimeInterpreter: Boolean = false, var comptimeInterpreter: Boolean = false,
val inlayHints: Boolean = true, var inlayHints: Boolean = true,
val inlayHintsCompact: Boolean = true var inlayHintsCompact: Boolean = true
) )

View file

@ -22,19 +22,16 @@
package com.falsepattern.zigbrains.lsp.settings package com.falsepattern.zigbrains.lsp.settings
import com.falsepattern.zigbrains.lsp.ZLSBundle
import com.falsepattern.zigbrains.lsp.startLSP import com.falsepattern.zigbrains.lsp.startLSP
import com.falsepattern.zigbrains.shared.NestedConfigurable import com.falsepattern.zigbrains.shared.SubConfigurable
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.ui.dsl.builder.Panel import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.panel
import javax.swing.JComponent
class ZLSSettingsConfigurable(private val project: Project): NestedConfigurable { class ZLSSettingsConfigurable(private val project: Project): SubConfigurable {
private var appSettingsComponent: ZLSSettingsPanel? = null private var appSettingsComponent: ZLSSettingsPanel? = null
override fun createComponent(panel: Panel) { override fun createComponent(panel: Panel) {
appSettingsComponent = ZLSSettingsPanel(project).apply { attach(panel) } appSettingsComponent = ZLSSettingsPanel(project).apply { attach(panel) }.also { Disposer.register(this, it) }
} }
override fun isModified(): Boolean { override fun isModified(): Boolean {
@ -56,12 +53,7 @@ class ZLSSettingsConfigurable(private val project: Project): NestedConfigurable
appSettingsComponent?.data = project.zlsSettings.state appSettingsComponent?.data = project.zlsSettings.state
} }
override fun disposeUIResources() { override fun dispose() {
appSettingsComponent?.dispose()
appSettingsComponent = null appSettingsComponent = null
} }
override fun getDisplayName(): String {
return ZLSBundle.message("configurable.name.zls.settings")
}
} }

View file

@ -24,12 +24,12 @@ package com.falsepattern.zigbrains.lsp.settings
import com.falsepattern.zigbrains.direnv.* import com.falsepattern.zigbrains.direnv.*
import com.falsepattern.zigbrains.lsp.ZLSBundle import com.falsepattern.zigbrains.lsp.ZLSBundle
import com.falsepattern.zigbrains.lsp.ZigLSPApplicationService import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.falsepattern.zigbrains.lsp.ZigLSPProjectService
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.ui.components.JBCheckBox import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.fields.ExtendableTextField import com.intellij.ui.components.fields.ExtendableTextField
import com.intellij.ui.components.textFieldWithBrowseButton import com.intellij.ui.components.textFieldWithBrowseButton
@ -44,12 +44,12 @@ class ZLSSettingsPanel(private val project: Project?) : Disposable {
project, project,
ZLSBundle.message("settings.zls-path.browse.title"), ZLSBundle.message("settings.zls-path.browse.title"),
FileChooserDescriptorFactory.createSingleFileDescriptor(), FileChooserDescriptorFactory.createSingleFileDescriptor(),
) ).also { Disposer.register(this, it) }
private val zlsConfigPath = textFieldWithBrowseButton( private val zlsConfigPath = textFieldWithBrowseButton(
project, project,
ZLSBundle.message("settings.zls-config-path.browse.title"), ZLSBundle.message("settings.zls-config-path.browse.title"),
FileChooserDescriptorFactory.createSingleFileDescriptor() FileChooserDescriptorFactory.createSingleFileDescriptor()
) ).also { Disposer.register(this, it) }
private val buildOnSave = JBCheckBox().apply { toolTipText = ZLSBundle.message("settings.build-on-save.tooltip") } private val buildOnSave = JBCheckBox().apply { toolTipText = ZLSBundle.message("settings.build-on-save.tooltip") }
private val buildOnSaveStep = ExtendableTextField().apply { toolTipText = ZLSBundle.message("settings.build-on-save-step.tooltip") } private val buildOnSaveStep = ExtendableTextField().apply { toolTipText = ZLSBundle.message("settings.build-on-save-step.tooltip") }
@ -69,7 +69,11 @@ class ZLSSettingsPanel(private val project: Project?) : Disposable {
if (DirenvCmd.direnvInstalled() && project != null) { if (DirenvCmd.direnvInstalled() && project != null) {
cell(direnv) cell(direnv)
} }
button(ZLSBundle.message("settings.zls-path.autodetect.label")) { autodetect() } button(ZLSBundle.message("settings.zls-path.autodetect.label")) {
project.zigCoroutineScope.launch {
autodetect()
}
}
} }
row(ZLSBundle.message("settings.zls-config-path.label")) { cell(zlsConfigPath).align(AlignX.FILL) } row(ZLSBundle.message("settings.zls-config-path.label")) { cell(zlsConfigPath).align(AlignX.FILL) }
row(ZLSBundle.message("settings.inlay-hints.label")) { cell(inlayHints) } row(ZLSBundle.message("settings.inlay-hints.label")) { cell(inlayHints) }
@ -113,15 +117,11 @@ class ZLSSettingsPanel(private val project: Project?) : Disposable {
inlayHintsCompact.isSelected = value.inlayHintsCompact inlayHintsCompact.isSelected = value.inlayHintsCompact
} }
fun autodetect() { suspend fun autodetect() {
(project?.service<ZigLSPProjectService>()?.cs ?: application.service<ZigLSPApplicationService>().cs).launch { getDirenv().findExecutableOnPATH("zls")?.let { zlsPath.text = it.pathString }
getDirenv().findExecutableOnPATH("zls")?.let { zlsPath.text = it.pathString }
}
} }
override fun dispose() { override fun dispose() {
zlsPath.dispose()
zlsConfigPath.dispose()
} }
private suspend fun getDirenv(): Env { private suspend fun getDirenv(): Env {

View file

@ -0,0 +1,40 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.actions
import com.falsepattern.zigbrains.Icons
import com.intellij.ide.actions.CreateFileFromTemplateAction
import com.intellij.ide.actions.CreateFileFromTemplateDialog
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDirectory
class ZigNewFileAction: CreateFileFromTemplateAction() {
override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) {
builder.setTitle("Zig File")
.addKind("Empty file", Icons.ZIG, "blank_zig_file")
}
override fun getActionName(directory: PsiDirectory?, newName: String, templateName: String?): String {
return "Zig File"
}
}

View file

@ -0,0 +1,33 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.console
import com.intellij.execution.filters.ConsoleFilterProvider
import com.intellij.execution.filters.Filter
import com.intellij.openapi.project.Project
class ZigConsoleFilterProvider: ConsoleFilterProvider {
override fun getDefaultFilters(project: Project): Array<Filter> {
return arrayOf(ZigSourceFileFilter(project))
}
}

View file

@ -0,0 +1,80 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.console
import com.intellij.execution.filters.Filter
import com.intellij.execution.filters.Filter.ResultItem
import com.intellij.execution.filters.OpenFileHyperlinkInfo
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.vfs.refreshAndFindVirtualFile
import com.intellij.openapi.vfs.toNioPathOrNull
import java.nio.file.InvalidPathException
import java.nio.file.Path
import kotlin.io.path.isRegularFile
import kotlin.io.path.notExists
import kotlin.math.max
class ZigSourceFileFilter(private val project: Project): Filter {
override fun applyFilter(line: String, entireLength: Int): Filter.Result? {
val lineStart = entireLength - line.length
val projectPath = project.guessProjectDir()?.toNioPathOrNull()
val results = ArrayList<ResultItem>()
val matcher = LEN_REGEX.findAll(line)
for (match in matcher) {
val start = match.range.first
val pair = findLongestParsablePathFromOffset(line, start, projectPath)
val path = pair?.first ?: return null
val file = path.refreshAndFindVirtualFile() ?: return null
val lineNumber = max(match.groups[1]!!.value.toInt() - 1, 0)
val lineOffset = max(match.groups[2]!!.value.toInt() - 1, 0)
results.add(ResultItem(lineStart + pair.second, lineStart + match.range.last + 1, OpenFileHyperlinkInfo(project, file, lineNumber, lineOffset)))
}
return Filter.Result(results)
}
private fun findLongestParsablePathFromOffset(line: String, end: Int, projectPath: Path?): Pair<Path, Int>? {
var longestStart = -1
var longest: Path? = null
for (i in end - 1 downTo 0) {
try {
val pathStr = line.substring(i, end)
var path: Path = pathStr.toNioPathOrNull() ?: continue
if ((path.notExists() || !path.isRegularFile()) && projectPath != null) {
path = projectPath.resolve(pathStr)
if (path.notExists() || !path.isRegularFile())
continue
}
longest = path
longestStart = i
} catch (ignored: InvalidPathException) {
}
}
longest ?: return null
return Pair(longest, longestStart)
}
}
private val LEN_REGEX = Regex(":(\\d+):(\\d+)")

View file

@ -0,0 +1,32 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.ConfigurationTypeUtil
import com.intellij.execution.configurations.runConfigurationType
inline fun <reified T: ConfigurationTypeBase> firstConfigFactory(): ConfigurationFactory {
return runConfigurationType<T>().configurationFactories.first()
}

View file

@ -0,0 +1,390 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.base
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.ZigConfigurable.ZigConfigModule
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.shared.cli.translateCommandline
import com.falsepattern.zigbrains.shared.element.*
import com.intellij.openapi.Disposable
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.TextBrowseFolderListener
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.toNioPathOrNull
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.Panel
import com.intellij.ui.dsl.builder.panel
import org.jdom.Element
import org.jetbrains.annotations.Nls
import java.io.Serializable
import java.nio.file.Path
import javax.swing.JComponent
import kotlin.io.path.pathString
class ZigConfigEditor<T : ZigExecConfig<T>>(private val state: ZigExecConfig<T>) : SettingsEditor<T>() {
private val configModules = ArrayList<ZigConfigModule<*>>()
override fun resetEditorFrom(s: T) {
outer@ for (cfg in s.getConfigurables()) {
for (module in configModules) {
if (module.tryReset(cfg))
continue@outer
}
}
}
override fun applyEditorTo(s: T) {
outer@ for (cfg in s.getConfigurables()) {
for (module in configModules) {
if (module.tryApply(cfg)) {
continue@outer
}
}
}
}
override fun createEditor(): JComponent {
configModules.clear()
configModules.addAll(state.getConfigurables().map { it.createEditor() })
return panel {
for (module in configModules) {
module.construct(this)
}
}
}
override fun disposeEditor() {
for (module in configModules) {
module.dispose()
}
configModules.clear()
}
}
interface ZigConfigurable<T : ZigConfigurable<T>> : Serializable, Cloneable {
fun readExternal(element: Element)
fun writeExternal(element: Element)
fun createEditor(): ZigConfigModule<T>
public override fun clone(): T
interface ZigConfigModule<T : ZigConfigurable<T>> : Disposable {
fun tryMatch(cfg: ZigConfigurable<*>): T?
fun apply(configurable: T): Boolean
fun reset(configurable: T)
fun tryApply(cfg: ZigConfigurable<*>): Boolean {
val x = tryMatch(cfg)
if (x != null) {
return apply(x)
}
return false
}
fun tryReset(cfg: ZigConfigurable<*>): Boolean {
val x = tryMatch(cfg)
if (x != null) {
reset(x)
return true
}
return false
}
fun construct(p: Panel)
}
}
abstract class PathConfigurable<T : PathConfigurable<T>> : ZigConfigurable<T> {
var path: Path? = null
override fun readExternal(element: Element) {
path = element.readString(serializedName)?.ifBlank { null }?.toNioPathOrNull() ?: return
}
override fun writeExternal(element: Element) {
element.writeString(serializedName, path?.pathString ?: "")
}
abstract val serializedName: String
abstract class PathConfigModule<T : PathConfigurable<T>> : ZigConfigModule<T> {
override fun apply(configurable: T): Boolean {
val str = stringValue
if (str.isBlank()) {
configurable.path = null
} else {
configurable.path = str.toNioPathOrNull() ?: return false
}
return true
}
override fun reset(configurable: T) {
stringValue = configurable.path?.pathString ?: ""
}
protected abstract var stringValue: String
}
}
class WorkDirectoryConfigurable(@Transient override val serializedName: String) : PathConfigurable<WorkDirectoryConfigurable>(), Cloneable {
override fun createEditor(): ZigConfigModule<WorkDirectoryConfigurable> {
return WorkDirectoryConfigModule(serializedName)
}
override fun clone(): WorkDirectoryConfigurable {
return super<Cloneable>.clone() as WorkDirectoryConfigurable
}
class WorkDirectoryConfigModule(private val serializedName: String) : PathConfigModule<WorkDirectoryConfigurable>() {
private val field = TextFieldWithBrowseButton(
TextBrowseFolderListener(
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.working-directory"))
),
this
)
override var stringValue by field::text
override fun tryMatch(cfg: ZigConfigurable<*>): WorkDirectoryConfigurable? {
return if (cfg is WorkDirectoryConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun construct(p: Panel): Unit = with(p) {
row(ZigBrainsBundle.message("exec.option.label.working-directory")) {
cell(field).resizableColumn().align(AlignX.FILL)
}
}
override fun dispose() {
field.dispose()
}
}
}
class FilePathConfigurable(
@Transient override val serializedName: String,
@Transient @Nls private val guiLabel: String
) : PathConfigurable<FilePathConfigurable>(), Cloneable {
override fun createEditor(): ZigConfigModule<FilePathConfigurable> {
return FilePathConfigModule(serializedName, guiLabel)
}
override fun clone(): FilePathConfigurable {
return super<Cloneable>.clone() as FilePathConfigurable
}
class FilePathConfigModule(private val serializedName: String, @Nls private val label: String) : PathConfigModule<FilePathConfigurable>() {
private val field = TextFieldWithBrowseButton(
TextBrowseFolderListener(FileChooserDescriptorFactory.createSingleFileDescriptor()),
this
)
override var stringValue by field::text
override fun tryMatch(cfg: ZigConfigurable<*>): FilePathConfigurable? {
return if (cfg is FilePathConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun construct(p: Panel): Unit = with(p) {
row(label) {
cell(field).resizableColumn().align(AlignX.FILL)
}
}
override fun dispose() {
Disposer.dispose(field)
}
}
}
class CheckboxConfigurable(
@Transient private val serializedName: String,
@Transient @Nls private val label: String,
var value: Boolean
) : ZigConfigurable<CheckboxConfigurable>, Cloneable {
override fun readExternal(element: Element) {
value = element.readBoolean(serializedName) ?: return
}
override fun writeExternal(element: Element) {
element.writeBoolean(serializedName, value)
}
override fun createEditor(): ZigConfigModule<CheckboxConfigurable> {
return CheckboxConfigModule(serializedName, label)
}
override fun clone(): CheckboxConfigurable {
return super<Cloneable>.clone() as CheckboxConfigurable
}
class CheckboxConfigModule(
private val serializedName: String,
@Nls private val label: String
) : ZigConfigModule<CheckboxConfigurable> {
private val checkBox = JBCheckBox()
override fun tryMatch(cfg: ZigConfigurable<*>): CheckboxConfigurable? {
return if (cfg is CheckboxConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun apply(configurable: CheckboxConfigurable): Boolean {
configurable.value = checkBox.isSelected
return true
}
override fun reset(configurable: CheckboxConfigurable) {
checkBox.isSelected = configurable.value
}
override fun construct(p: Panel): Unit = with(p) {
row(label) {
cell(checkBox)
}
}
override fun dispose() {}
}
}
fun ColoredConfigurable(serializedName: String) = CheckboxConfigurable(serializedName, ZigBrainsBundle.message("exec.option.label.colored-terminal"), true)
fun DirenvConfigurable(serializedName: String, project: Project) =
CheckboxConfigurable(serializedName, ZigBrainsBundle.message("exec.option.label.direnv"), project.zigProjectSettings.state.direnv)
class OptimizationConfigurable(
@Transient private val serializedName: String,
var level: OptimizationLevel = OptimizationLevel.Debug,
var forced: Boolean = false
) : ZigConfigurable<OptimizationConfigurable>, Cloneable {
override fun readExternal(element: Element) {
element.readChild(serializedName)?.apply {
readEnum<OptimizationLevel>("level")?.let { level = it }
readBoolean("forced")?.let { forced = it }
}
}
override fun writeExternal(element: Element) {
element.writeChild(serializedName).apply {
writeEnum("level", level)
writeBoolean("forced", forced)
}
}
override fun createEditor(): ZigConfigModule<OptimizationConfigurable> {
return OptimizationConfigModule(serializedName)
}
override fun clone(): OptimizationConfigurable {
return super<Cloneable>.clone() as OptimizationConfigurable
}
class OptimizationConfigModule(private val serializedName: String) : ZigConfigModule<OptimizationConfigurable> {
private val levels = ComboBox(OptimizationLevel.entries.toTypedArray())
private val forced = JBCheckBox(ZigBrainsBundle.message("exec.option.label.optimization.force"))
override fun tryMatch(cfg: ZigConfigurable<*>): OptimizationConfigurable? {
return if (cfg is OptimizationConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun apply(configurable: OptimizationConfigurable): Boolean {
configurable.level = levels.item
configurable.forced = forced.isSelected
return true
}
override fun reset(configurable: OptimizationConfigurable) {
levels.item = configurable.level
forced.isSelected = configurable.forced
}
override fun construct(p: Panel): Unit = with(p) {
row(ZigBrainsBundle.message("exec.option.label.optimization")) {
cell(levels)
cell(forced)
}
}
override fun dispose() {
}
}
}
class ArgsConfigurable(
@Transient private val serializedName: String,
@Transient @Nls private val guiName: String
) : ZigConfigurable<ArgsConfigurable>, Cloneable {
var args: List<String> = emptyList()
override fun readExternal(element: Element) {
args = element.readStrings(serializedName) ?: return
}
override fun writeExternal(element: Element) {
element.writeStrings(serializedName, args)
}
override fun createEditor(): ZigConfigModule<ArgsConfigurable> {
return ArgsConfigModule(serializedName, guiName)
}
override fun clone(): ArgsConfigurable {
return super<Cloneable>.clone() as ArgsConfigurable
}
class ArgsConfigModule(
private val serializedName: String,
@Nls private val guiName: String
) : ZigConfigModule<ArgsConfigurable> {
private val argsField = JBTextField()
override fun tryMatch(cfg: ZigConfigurable<*>): ArgsConfigurable? {
return if (cfg is ArgsConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun apply(configurable: ArgsConfigurable): Boolean {
configurable.args = translateCommandline(argsField.text)
return true
}
override fun reset(configurable: ArgsConfigurable) {
argsField.text = configurable.args.joinToString(separator = " ")
}
override fun construct(p: Panel): Unit = with(p) {
row(guiName) {
cell(argsField).resizableColumn().align(AlignX.FILL)
}
}
override fun dispose() {
}
}
}

View file

@ -20,17 +20,11 @@
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>. * along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.falsepattern.zigbrains.lsp package com.falsepattern.zigbrains.project.execution.base
import com.intellij.openapi.components.Service enum class OptimizationLevel {
import kotlinx.coroutines.CoroutineScope Debug,
ReleaseSafe,
@Service(Service.Level.PROJECT) ReleaseFast,
class ZigLSPProjectService( ReleaseSmall
val cs: CoroutineScope }
)
@Service
class ZigLSPApplicationService(
val cs: CoroutineScope
)

View file

@ -0,0 +1,84 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.base
import com.falsepattern.zigbrains.zig.psi.ZigFile
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.actions.LazyRunConfigurationProducer
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.openapi.util.Ref
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.toNioPathOrNull
import com.intellij.psi.PsiElement
import java.nio.file.Path
abstract class ZigConfigProducer<T: ZigExecConfig<T>>: LazyRunConfigurationProducer<T>() {
abstract override fun getConfigurationFactory(): ConfigurationFactory
override fun setupConfigurationFromContext(configuration: T, context: ConfigurationContext, sourceElement: Ref<PsiElement>): Boolean {
val element = context.location?.psiElement ?: return false
val psiFile = element.containingFile as? ZigFile ?: return false
val theFile = psiFile.virtualFile ?: return false
val filePath = theFile.toNioPathOrNull() ?: return false
return setupConfigurationFromContext(configuration, element, filePath, theFile)
}
override fun isConfigurationFromContext(configuration: T, context: ConfigurationContext): Boolean {
val element = context.location?.psiElement ?: return false
val psiFile = element.containingFile as? ZigFile ?: return false
val theFile = psiFile.virtualFile ?: return false
val filePath = theFile.toNioPathOrNull() ?: return false
return isConfigurationFromContext(configuration, element, filePath, theFile)
}
/*
TODO implement these
@Override
protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigRun configuration, PsiElement element, String filePath, VirtualFile theFile) {
if (PsiUtil.getElementType(element) == ZigTypes.KEYWORD_TEST) {
configuration.command = "test " + filePath;
configuration.setName("Test " + theFile.getPresentableName());
} else if ("build.zig".equals(theFile.getName())) {
configuration.command = "build";
configuration.setName("Build");
} else {
configuration.extraArgs = filePath;
configuration.setName(theFile.getPresentableName());
}
return true;
}
@Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigRun configuration, String filePath, VirtualFile vFile, PsiElement element) {
if (!configuration.command.contains(filePath)) {
return configuration.command.startsWith("build") && vFile.getName().equals("build.zig");
}
return (PsiUtil.getElementType(element) == ZigTypes.KEYWORD_TEST) == configuration.command.startsWith("test ");
}
*/
protected abstract fun setupConfigurationFromContext(configuration: T, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean
protected abstract fun isConfigurationFromContext(configuration: T, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean
abstract override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean
}

View file

@ -22,7 +22,68 @@
package com.falsepattern.zigbrains.project.execution.base package com.falsepattern.zigbrains.project.execution.base
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.LocatableConfigurationBase import com.intellij.execution.configurations.LocatableConfigurationBase
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.util.NlsActions.ActionText
import com.intellij.openapi.vfs.toNioPathOrNull
import org.jdom.Element
abstract class ZigExecConfig<T: ZigExecConfig<T>>: LocatableConfigurationBase { abstract class ZigExecConfig<T: ZigExecConfig<T>>(project: Project, factory: ConfigurationFactory, name: String): LocatableConfigurationBase<ZigProfileState<T>>(project, factory, name) {
var workingDirectory = WorkDirectoryConfigurable("workingDirectory").apply { path = project.guessProjectDir()?.toNioPathOrNull() }
private set
var pty = CheckboxConfigurable("pty", ZigBrainsBundle.message("exec.option.label.emulate-terminal"), false)
private set
var direnv = DirenvConfigurable("direnv", project)
private set
abstract val suggestedName: @ActionText String
abstract suspend fun buildCommandLineArgs(debug: Boolean): List<String>
abstract override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<T>
override fun getConfigurationEditor(): SettingsEditor<out RunConfiguration> {
return ZigConfigEditor(this)
}
override fun readExternal(element: Element) {
super.readExternal(element)
getConfigurables().forEach { it.readExternal(element) }
}
override fun writeExternal(element: Element) {
super.writeExternal(element)
getConfigurables().forEach { it.writeExternal(element) }
}
suspend fun patchCommandLine(commandLine: GeneralCommandLine, toolchain: AbstractZigToolchain): GeneralCommandLine {
if (direnv.value) {
commandLine.withEnvironment(DirenvCmd.importDirenv(project).env)
}
return commandLine
}
fun emulateTerminal(): Boolean {
return pty.value
}
override fun clone(): T {
val myClone = super.clone() as ZigExecConfig<*>
myClone.workingDirectory = workingDirectory.clone()
myClone.pty = pty.clone()
myClone.direnv = direnv.clone()
@Suppress("UNCHECKED_CAST")
return myClone as T
}
open fun getConfigurables(): List<ZigConfigurable<*>> = listOf(workingDirectory, pty, direnv)
} }

View file

@ -22,12 +22,68 @@
package com.falsepattern.zigbrains.project.execution.base package com.falsepattern.zigbrains.project.execution.base
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking
import com.intellij.build.BuildTextConsoleView
import com.intellij.execution.DefaultExecutionResult
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.CommandLineState import com.intellij.execution.configurations.CommandLineState
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PtyCommandLine
import com.intellij.execution.process.ProcessHandler
import com.intellij.execution.process.ProcessTerminatedListener
import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.platform.ide.progress.ModalTaskOwner
import kotlin.io.path.pathString
abstract class ZigProfileState<T: ZigExecConfig<T>>( abstract class ZigProfileState<T: ZigExecConfig<T>> (
environment: ExecutionEnvironment, environment: ExecutionEnvironment,
protected val configuration: T val configuration: T
): CommandLineState(environment) { ): CommandLineState(environment) {
@Throws(ExecutionException::class)
override fun startProcess(): ProcessHandler {
return runModalOrBlocking({ModalTaskOwner.project(environment.project)}, {"ZigProfileState.startProcess"}) {
startProcessSuspend()
}
}
@Throws(ExecutionException::class)
suspend fun startProcessSuspend(): ProcessHandler {
val toolchain = environment.project.zigProjectSettings.state.toolchain ?: throw ExecutionException(ZigBrainsBundle.message("exception.zig-profile-state.start-process.no-toolchain"))
return ZigProcessHandler(getCommandLine(toolchain, false))
}
@Throws(ExecutionException::class)
suspend fun getCommandLine(toolchain: AbstractZigToolchain, debug: Boolean): GeneralCommandLine {
val workingDir = configuration.workingDirectory
val zigExePath = toolchain.zig.path()
// TODO remove this check once JetBrains implements colored terminal in the debugger
// https://youtrack.jetbrains.com/issue/CPP-11622/ANSI-color-codes-not-honored-in-Debug-Run-Configuration-output-window
val cli = if (configuration.emulateTerminal() && !debug) PtyCommandLine() else GeneralCommandLine()
cli.exePath = zigExePath.pathString
workingDir.path?.let { cli.withWorkingDirectory(it) }
cli.charset = Charsets.UTF_8
cli.addParameters(configuration.buildCommandLineArgs(debug))
return configuration.patchCommandLine(cli, toolchain)
}
}
@Throws(ExecutionException::class)
fun executeCommandLine(commandLine: GeneralCommandLine, environment: ExecutionEnvironment): DefaultExecutionResult {
val handler = startProcess(commandLine)
val console = BuildTextConsoleView(environment.project, false, emptyList())
console.attachToProcess(handler)
return DefaultExecutionResult(console, handler)
}
@Throws(ExecutionException::class)
fun startProcess(commandLine: GeneralCommandLine): ProcessHandler {
val handler = ZigProcessHandler(commandLine)
ProcessTerminatedListener.attach(handler)
return handler
} }

View file

@ -0,0 +1,56 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.build
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer
import com.falsepattern.zigbrains.project.execution.firstConfigFactory
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import java.nio.file.Path
class ZigConfigProducerBuild: ZigConfigProducer<ZigExecConfigBuild>() {
override fun getConfigurationFactory(): ConfigurationFactory {
return firstConfigFactory<ZigConfigTypeBuild>()
}
override fun setupConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
if (LINE_MARKER.elementMatches(element)) {
configuration.name = ZigBrainsBundle.message("configuration.build.marker-name")
return true
}
return false
}
override fun isConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
return filePath.parent == (configuration.workingDirectory.path ?: return false)
}
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
return self.configurationType is ZigConfigTypeBuild
}
}
private val LINE_MARKER = ZigLineMarkerBuild()

View file

@ -0,0 +1,53 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.build
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.openapi.project.Project
class ZigConfigTypeBuild : ConfigurationTypeBase(
IDENTIFIER,
ZigBrainsBundle.message("configuration.build.name"),
ZigBrainsBundle.message("configuration.build.description"),
Icons.ZIG
) {
init {
addFactory(ConfigFactoryRun())
}
inner class ConfigFactoryRun: ConfigurationFactory(this@ZigConfigTypeBuild) {
override fun createTemplateConfiguration(project: Project): RunConfiguration {
return ZigExecConfigBuild(project, this)
}
override fun getId(): String {
return IDENTIFIER
}
}
}
private const val IDENTIFIER = "ZIGBRAINS_BUILD"

View file

@ -0,0 +1,94 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.build
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.*
import com.falsepattern.zigbrains.shared.ZBFeatures
import com.falsepattern.zigbrains.shared.cli.coloredCliFlags
import com.intellij.execution.ExecutionException
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.project.Project
class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigExecConfig<ZigExecConfigBuild>(project, factory, "Zig Run") {
var buildSteps = ArgsConfigurable("buildSteps", ZigBrainsBundle.message("exec.option.label.build.steps"))
private set
var extraArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.build.args"))
private set
var colored = ColoredConfigurable("colored")
private set
var exePath = FilePathConfigurable("exePath", ZigBrainsBundle.message("exec.option.label.build.exe-path-debug"))
private set
var exeArgs = ArgsConfigurable("exeArgs", ZigBrainsBundle.message("exec.option.label.build.exe-args-debug"))
private set
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
val result = ArrayList<String>()
result.add("build")
var steps = buildSteps.args
if (debug) {
val truncatedSteps = ArrayList<String>()
for (step in steps) {
if (step == "run")
continue
if (step == "test")
throw ExecutionException(ZigBrainsBundle.message("exception.zig-build.debug.test-not-supported"))
truncatedSteps.add(step)
}
}
result.addAll(steps)
result.addAll(coloredCliFlags(colored.value, debug))
result.addAll(extraArgs.args)
return result
}
override val suggestedName: String
get() = ZigBrainsBundle.message("configuration.build.suggested-name")
override fun clone(): ZigExecConfigBuild {
val clone = super.clone()
clone.buildSteps = buildSteps.clone()
clone.exeArgs = exeArgs.clone()
clone.colored = colored.clone()
clone.exePath = exePath.clone()
clone.exeArgs = exeArgs.clone()
return clone
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
val baseCfg = super.getConfigurables() + listOf(buildSteps, extraArgs, colored)
if (ZBFeatures.debug()) {
return baseCfg + listOf(exePath, exeArgs)
} else {
return baseCfg
}
}
override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<ZigExecConfigBuild> {
return ZigProfileStateBuild(environment, this)
}
}

View file

@ -0,0 +1,56 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.build
import com.falsepattern.zigbrains.execution.base.ZigTopLevelLineMarker
import com.falsepattern.zigbrains.zig.psi.ZigTypes
import com.intellij.icons.AllIcons.RunConfigurations.TestState
import com.intellij.psi.PsiElement
import com.intellij.psi.util.elementType
import javax.swing.Icon
class ZigLineMarkerBuild: ZigTopLevelLineMarker() {
override fun getDeclaration(element: PsiElement): PsiElement? {
if (element.elementType != ZigTypes.IDENTIFIER)
return null
if (!element.textMatches("build"))
return null
val parent = element.parent ?: return null
if (parent.elementType != ZigTypes.FN_PROTO)
return null
val file = element.containingFile ?: return null
val fileName = file.virtualFile.name
if (fileName != "build.zig")
return null
return parent.parent
}
override fun getIcon(element: PsiElement): Icon {
return TestState.Run
}
}

View file

@ -0,0 +1,29 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.build
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.intellij.execution.runners.ExecutionEnvironment
class ZigProfileStateBuild(environment: ExecutionEnvironment, configuration: ZigExecConfigBuild) : ZigProfileState<ZigExecConfigBuild>(environment, configuration) {
}

View file

@ -0,0 +1,57 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.run
import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer
import com.falsepattern.zigbrains.project.execution.firstConfigFactory
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import java.nio.file.Path
class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
override fun getConfigurationFactory(): ConfigurationFactory {
return firstConfigFactory<ZigConfigTypeRun>()
}
override fun setupConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
if (LINE_MARKER.elementMatches(element)) {
configuration.filePath.path = filePath
configuration.name = theFile.presentableName
return true
}
return false
}
override fun isConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
return filePath == configuration.filePath.path
}
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
return self.configurationType is ZigConfigTypeRun
}
}
private val LINE_MARKER = ZigLineMarkerRun()

View file

@ -0,0 +1,53 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.run
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.openapi.project.Project
class ZigConfigTypeRun : ConfigurationTypeBase(
IDENTIFIER,
ZigBrainsBundle.message("configuration.run.name"),
ZigBrainsBundle.message("configuration.run.description"),
Icons.ZIG
) {
init {
addFactory(ConfigFactoryRun())
}
inner class ConfigFactoryRun: ConfigurationFactory(this@ZigConfigTypeRun) {
override fun createTemplateConfiguration(project: Project): RunConfiguration {
return ZigExecConfigRun(project, this)
}
override fun getId(): String {
return IDENTIFIER
}
}
}
private const val IDENTIFIER = "ZIGBRAINS_RUN"

View file

@ -0,0 +1,82 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.run
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.*
import com.falsepattern.zigbrains.shared.cli.coloredCliFlags
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.project.Project
import kotlin.io.path.pathString
class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExecConfig<ZigExecConfigRun>(project, factory, "Zig Run") {
var filePath = FilePathConfigurable("filePath", ZigBrainsBundle.message("exec.option.label.file-path"))
private set
var colored = ColoredConfigurable("colored")
private set
var optimization = OptimizationConfigurable("optimization")
private set
var compilerArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.compiler-args"))
private set
var exeArgs = ArgsConfigurable("exeArgs", ZigBrainsBundle.message("exec.option.label.exe-args"))
private set
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
val result = ArrayList<String>()
result.add(if (debug) "build-exe" else "run")
result.addAll(coloredCliFlags(colored.value, debug))
result.add(filePath.path?.pathString ?: throw IllegalArgumentException("Empty file path!"))
if (!debug || optimization.forced) {
result.addAll(listOf("-O", optimization.level.name))
}
result.addAll(compilerArgs.args)
if (!debug) {
result.add("--")
result.addAll(exeArgs.args)
}
return result
}
override val suggestedName: String
get() = ZigBrainsBundle.message("configuration.run.suggested-name")
override fun clone(): ZigExecConfigRun {
val clone = super.clone()
clone.filePath = filePath.clone()
clone.colored = colored.clone()
clone.compilerArgs = compilerArgs.clone()
clone.optimization = optimization.clone()
clone.exeArgs = exeArgs.clone()
return clone
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
return super.getConfigurables() + listOf(filePath, optimization, colored, compilerArgs, exeArgs)
}
override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<ZigExecConfigRun> {
return ZigProfileStateRun(environment, this)
}
}

View file

@ -0,0 +1,50 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.run
import com.falsepattern.zigbrains.execution.base.ZigTopLevelLineMarker
import com.falsepattern.zigbrains.zig.psi.ZigTypes
import com.intellij.icons.AllIcons.RunConfigurations.TestState
import com.intellij.psi.PsiElement
import com.intellij.psi.util.elementType
import javax.swing.Icon
class ZigLineMarkerRun: ZigTopLevelLineMarker() {
override fun getDeclaration(element: PsiElement): PsiElement? {
if (element.elementType != ZigTypes.IDENTIFIER)
return null
if (!element.textMatches("main"))
return null
val parent = element.parent
if (parent.elementType != ZigTypes.FN_PROTO)
return null
return parent.parent
}
override fun getIcon(element: PsiElement): Icon {
return TestState.Run
}
}

View file

@ -0,0 +1,29 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.run
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.intellij.execution.runners.ExecutionEnvironment
class ZigProfileStateRun(environment: ExecutionEnvironment, configuration: ZigExecConfigRun) : ZigProfileState<ZigExecConfigRun>(environment, configuration) {
}

View file

@ -0,0 +1,57 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.test
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer
import com.falsepattern.zigbrains.project.execution.firstConfigFactory
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import java.nio.file.Path
class ZigConfigProducerTest: ZigConfigProducer<ZigExecConfigTest>() {
override fun getConfigurationFactory(): ConfigurationFactory {
return firstConfigFactory<ZigConfigTypeTest>()
}
override fun setupConfigurationFromContext(configuration: ZigExecConfigTest, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
if (LINE_MARKER.elementMatches(element)) {
configuration.filePath.path = filePath
configuration.name = ZigBrainsBundle.message("configuration.test.marker-name", theFile.presentableName)
return true
}
return false
}
override fun isConfigurationFromContext(configuration: ZigExecConfigTest, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
return filePath == configuration.filePath.path
}
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
return self.configurationType is ZigConfigTypeTest
}
}
private val LINE_MARKER = ZigLineMarkerTest()

View file

@ -0,0 +1,53 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.test
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.openapi.project.Project
class ZigConfigTypeTest : ConfigurationTypeBase(
IDENTIFIER,
ZigBrainsBundle.message("configuration.test.name"),
ZigBrainsBundle.message("configuration.test.description"),
Icons.ZIG
) {
init {
addFactory(ConfigFactoryRun())
}
inner class ConfigFactoryRun: ConfigurationFactory(this@ZigConfigTypeTest) {
override fun createTemplateConfiguration(project: Project): RunConfiguration {
return ZigExecConfigTest(project, this)
}
override fun getId(): String {
return IDENTIFIER
}
}
}
private const val IDENTIFIER = "ZIGBRAINS_TEST"

View file

@ -0,0 +1,78 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.test
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.*
import com.falsepattern.zigbrains.shared.cli.coloredCliFlags
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.project.Project
import kotlin.io.path.pathString
class ZigExecConfigTest(project: Project, factory: ConfigurationFactory): ZigExecConfig<ZigExecConfigTest>(project, factory, "Zig Run") {
var filePath = FilePathConfigurable("filePath", ZigBrainsBundle.message("exec.option.label.file-path"))
private set
var colored = ColoredConfigurable("colored")
private set
var optimization = OptimizationConfigurable("optimization")
private set
var compilerArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.compiler-args"))
private set
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
val result = ArrayList<String>()
result.add(if (debug) "build-exe" else "run")
result.addAll(coloredCliFlags(colored.value, debug))
result.add(filePath.path?.pathString ?: throw IllegalArgumentException("Empty file path!"))
if (!debug || optimization.forced) {
result.addAll(listOf("-O", optimization.level.name))
}
result.addAll(compilerArgs.args)
if (debug) {
result.add("--test-no-exec")
}
return result
}
override val suggestedName: String
get() = ZigBrainsBundle.message("configuration.test.suggested-name")
override fun clone(): ZigExecConfigTest {
val clone = super.clone()
clone.filePath = filePath.clone()
clone.colored = colored.clone()
clone.compilerArgs = compilerArgs.clone()
clone.optimization = optimization.clone()
return clone
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
return super.getConfigurables() + listOf(filePath, optimization, colored, compilerArgs)
}
override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<ZigExecConfigTest> {
return ZigProfileStateTest(environment, this)
}
}

View file

@ -0,0 +1,47 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.test
import com.falsepattern.zigbrains.execution.base.ZigTopLevelLineMarker
import com.falsepattern.zigbrains.zig.psi.ZigTypes
import com.intellij.icons.AllIcons.RunConfigurations.TestState
import com.intellij.psi.PsiElement
import com.intellij.psi.util.elementType
import javax.swing.Icon
class ZigLineMarkerTest: ZigTopLevelLineMarker() {
override fun getDeclaration(element: PsiElement): PsiElement? {
if (element.elementType != ZigTypes.KEYWORD_TEST)
return null
val parent = element.parent
if (parent.elementType != ZigTypes.TEST_DECL)
return null
return parent
}
override fun getIcon(element: PsiElement): Icon {
return TestState.Run_run
}
}

View file

@ -0,0 +1,29 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.test
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.intellij.execution.runners.ExecutionEnvironment
class ZigProfileStateTest(environment: ExecutionEnvironment, configuration: ZigExecConfigTest) : ZigProfileState<ZigExecConfigTest>(environment, configuration) {
}

View file

@ -0,0 +1,90 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.module
import com.falsepattern.zigbrains.project.newproject.ZigProjectConfigurationData
import com.falsepattern.zigbrains.project.newproject.ZigProjectGeneratorPeer
import com.intellij.ide.util.projectWizard.ModuleBuilder
import com.intellij.ide.util.projectWizard.ModuleWizardStep
import com.intellij.ide.util.projectWizard.WizardContext
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.Experiments
import com.intellij.openapi.module.ModuleType
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.openapi.util.Disposer
import com.intellij.util.ui.JBUI
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.swing.JComponent
class ZigModuleBuilder: ModuleBuilder() {
var configurationData: ZigProjectConfigurationData? = null
var forceGitignore = false
override fun getModuleType(): ModuleType<*> {
return ZigModuleType
}
override fun setupRootModel(modifiableRootModel: ModifiableRootModel) {
super.setupRootModel(modifiableRootModel)
}
override fun getCustomOptionsStep(context: WizardContext?, parentDisposable: Disposable?): ModuleWizardStep? {
val step = ZigModuleWizardStep()
parentDisposable?.let { Disposer.register(it, step.peer) }
return step
}
suspend fun createProject(rootModel: ModifiableRootModel) {
val contentEntry = doAddContentEntry(rootModel) ?: return
val root = contentEntry.file ?: return
val config = configurationData ?: return
config.generateProject(this, rootModel.project, root, forceGitignore)
withContext(Dispatchers.EDT) {
root.refresh(false, true)
}
}
inner class ZigModuleWizardStep: ModuleWizardStep() {
internal val peer = ZigProjectGeneratorPeer(true)
override fun getComponent(): JComponent {
return peer.component.withBorder()
}
override fun disposeUIResources() {
Disposer.dispose(peer)
}
override fun updateDataModel() {
this@ZigModuleBuilder.configurationData = peer.settings
}
}
}
private fun <T: JComponent> T.withBorder(): T {
border = JBUI.Borders.empty(14, 20)
return this
}

View file

@ -0,0 +1,45 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.module
import com.falsepattern.zigbrains.Icons
import com.intellij.openapi.module.ModuleType
import javax.swing.Icon
object ZigModuleType: ModuleType<ZigModuleBuilder>("com.falsepattern.zigbrains.zigModuleType") {
override fun createModuleBuilder(): ZigModuleBuilder {
return ZigModuleBuilder()
}
override fun getName(): String {
return "Zig"
}
override fun getDescription(): String {
return "Zig module"
}
override fun getNodeIcon(isOpened: Boolean): Icon {
return Icons.ZIG
}
}

View file

@ -0,0 +1,69 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.newproject
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking
import com.intellij.execution.runners.ExecutionUtil
import com.intellij.facet.ui.ValidationResult
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep
import com.intellij.ide.util.projectWizard.CustomStepProjectGenerator
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.wm.impl.welcomeScreen.AbstractActionWithPanel
import com.intellij.platform.DirectoryProjectGenerator
import com.intellij.platform.ProjectGeneratorPeer
import com.intellij.platform.ide.progress.ModalTaskOwner
import javax.swing.Icon
class ZigDirectoryProjectGenerator: DirectoryProjectGenerator<ZigProjectConfigurationData>, CustomStepProjectGenerator<ZigProjectConfigurationData> {
override fun getName(): String {
return "Zig"
}
override fun getLogo(): Icon {
return Icons.ZIG
}
override fun createPeer(): ProjectGeneratorPeer<ZigProjectConfigurationData> {
return ZigProjectGeneratorPeer(true)
}
override fun validate(baseDirPath: String): ValidationResult {
return ValidationResult.OK
}
override fun generateProject(project: Project, baseDir: VirtualFile, settings: ZigProjectConfigurationData, module: Module) {
runModalOrBlocking({ ModalTaskOwner.project(project)}, {"ZigDirectoryProjectGenerator.generateProject"}) {
settings.generateProject(this, project, baseDir, false)
}
}
override fun createStep(
projectGenerator: DirectoryProjectGenerator<ZigProjectConfigurationData>?,
callback: AbstractNewProjectStep.AbstractCallback<ZigProjectConfigurationData>?
): AbstractActionWithPanel {
return ZigProjectSettingsStep(projectGenerator!!, callback)
}
}

View file

@ -0,0 +1,106 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.newproject
import com.falsepattern.zigbrains.lsp.settings.ZLSSettingsPanel
import com.falsepattern.zigbrains.project.settings.ZigProjectSettingsPanel
import com.falsepattern.zigbrains.project.template.ZigExecutableTemplate
import com.falsepattern.zigbrains.project.template.ZigInitTemplate
import com.falsepattern.zigbrains.project.template.ZigLibraryTemplate
import com.falsepattern.zigbrains.project.template.ZigProjectTemplate
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionToolbarPosition
import com.intellij.openapi.util.Disposer
import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.ToolbarDecorator
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBList
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.AlignY
import com.intellij.ui.dsl.builder.Panel
import com.intellij.util.ui.JBUI
import javax.swing.JList
import javax.swing.ListSelectionModel
class ZigNewProjectPanel(private var handleGit: Boolean): Disposable {
private val git = JBCheckBox()
private val projConf = ZigProjectSettingsPanel(null).also { Disposer.register(this, it) }
private val zlsConf = ZLSSettingsPanel(null).also { Disposer.register(this, it) }
private val templateList = JBList(JBList.createDefaultListModel(defaultTemplates)).apply {
selectionMode = ListSelectionModel.SINGLE_SELECTION
selectedIndex = 0
cellRenderer = object : ColoredListCellRenderer<ZigProjectTemplate>() {
override fun customizeCellRenderer(list: JList<out ZigProjectTemplate>, value: ZigProjectTemplate?, index: Int, selected: Boolean, hasFocus: Boolean) {
value?.let {
icon = it.icon
append(it.name)
}
}
}
}
private val templateToolbar get() = ToolbarDecorator.createDecorator(templateList)
.setToolbarPosition(ActionToolbarPosition.BOTTOM)
.setPreferredSize(JBUI.size(0, 125))
.disableUpDownActions()
.disableAddAction()
.disableRemoveAction()
fun getData(): ZigProjectConfigurationData {
val selectedTemplate = templateList.selectedValue
return ZigProjectConfigurationData(handleGit && git.isSelected, projConf.data, zlsConf.data, selectedTemplate)
}
fun attach(p: Panel): Unit = with(p) {
if (handleGit) {
row("Create Git repository") {
cell(git)
}
}
group("Zig Project Template") {
row {
resizableRow()
cell(templateToolbar.createPanel())
.align(AlignX.FILL)
.align(AlignY.FILL)
}
}
projConf.attach(p)
zlsConf.attach(p)
}
suspend fun autodetect() {
projConf.autodetect()
zlsConf.autodetect()
}
override fun dispose() {
}
}
private val defaultTemplates get() = listOf(
ZigExecutableTemplate(),
ZigLibraryTemplate(),
ZigInitTemplate()
)

View file

@ -0,0 +1,65 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.newproject
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.project.module.ZigModuleBuilder
import com.intellij.ide.wizard.AbstractNewProjectWizardStep
import com.intellij.ide.wizard.GitNewProjectWizardData.Companion.gitData
import com.intellij.ide.wizard.NewProjectWizardStep
import com.intellij.ide.wizard.language.LanguageGeneratorNewProjectWizard
import com.intellij.openapi.project.Project
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Panel
import javax.swing.Icon
class ZigNewProjectWizard: LanguageGeneratorNewProjectWizard {
override val name: String
get() = "Zig"
override val icon: Icon
get() = Icons.ZIG
override val ordinal: Int
get() = 900
override fun createStep(parent: NewProjectWizardStep): NewProjectWizardStep {
return ZigNewProjectWizardStep(parent)
}
private class ZigNewProjectWizardStep(parentStep: NewProjectWizardStep): AbstractNewProjectWizardStep(parentStep) {
private val peer = ZigProjectGeneratorPeer(false)
override fun setupUI(builder: Panel): Unit = with(builder) {
row {
cell(peer.component).align(AlignX.FILL)
}
}
override fun setupProject(project: Project) {
val builder = ZigModuleBuilder()
builder.configurationData = peer.settings
val gitData = gitData
builder.forceGitignore = gitData != null && gitData.git
builder.commit(project)
}
}
}

View file

@ -0,0 +1,142 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.newproject
import com.falsepattern.zigbrains.lsp.settings.ZLSSettings
import com.falsepattern.zigbrains.lsp.settings.zlsSettings
import com.falsepattern.zigbrains.project.settings.ZigProjectSettings
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.template.ZigInitTemplate
import com.falsepattern.zigbrains.project.template.ZigProjectTemplate
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.openapi.GitRepositoryInitializer
import com.intellij.openapi.application.EDT
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.toNioPathOrNull
import com.intellij.util.ResourceUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@JvmRecord
data class ZigProjectConfigurationData(
val git: Boolean,
val projConf: ZigProjectSettings,
val zlsConf: ZLSSettings,
val selectedTemplate: ZigProjectTemplate
) {
suspend fun generateProject(requestor: Any, project: Project, baseDir: VirtualFile, forceGitignore: Boolean): Boolean {
withContext(Dispatchers.EDT) {
project.zigProjectSettings.loadState(projConf)
project.zlsSettings.loadState(zlsConf)
}
val template = selectedTemplate
if (template is ZigInitTemplate) {
val toolchain = projConf.toolchain ?: run {
Notification(
"zigbrains",
"Tried to generate project with zig init, but zig toolchain is invalid",
NotificationType.ERROR
).notify(project)
return false
}
val zig = toolchain.zig
val workDir = baseDir.toNioPathOrNull() ?: run {
Notification(
"zigbrains",
"Tried to generate project with zig init, but base directory is invalid",
NotificationType.ERROR
).notify(project)
return false
}
val result = zig.callWithArgs(workDir, "init")
if (result.exitCode != 0) {
Notification(
"zigbrains",
"\"zig init\" failed with exit code ${result.exitCode}! Check the IDE log files!",
NotificationType.ERROR
)
System.err.println(result.stderr)
return false
}
} else {
val projectName = project.name
for (fileTemplate in template.fileTemplates()) {
val (fileName, parentDir) = fileTemplate.key.let {
if (it.contains("/")) {
val slashIndex = it.indexOf("/")
val parentDir = withContext(Dispatchers.EDT) {
baseDir.createChildDirectory(requestor, it.substring(0, slashIndex))
}
Pair(it.substring(slashIndex + 1), parentDir)
} else {
Pair(it, baseDir)
}
}
val templateDir = fileTemplate.value
val resourceData = getResourceString("project-gen/$templateDir/$fileName.template")
?.replace("@@PROJECT_NAME@@", projectName)
?: continue
withContext(Dispatchers.EDT) {
val targetFile = parentDir.createChildData(requestor, fileName)
VfsUtil.saveText(targetFile, resourceData)
}
}
}
if (git) {
withContext(Dispatchers.IO) {
GitRepositoryInitializer.getInstance()?.initRepository(project, baseDir)
}
}
if (git || forceGitignore) {
createGitIgnoreFile(baseDir, requestor)
}
return true
}
}
private suspend fun createGitIgnoreFile(projectDir: VirtualFile, requestor: Any) {
if (projectDir.findChild(".gitignore") != null) {
return
}
withContext(Dispatchers.IO) {
ZigProjectConfigurationData::class.java.getResourceAsStream("/fileTemplates/internal/gitignore")?.use {
val file = projectDir.createChildData(requestor, ".gitignore")
file.setCharset(Charsets.UTF_8)
file.setBinaryContent(it.readAllBytes())
}
}
}
private fun getResourceString(path: String): String? {
return ResourceUtil.getResourceAsBytes(path, ZigProjectConfigurationData::class.java.classLoader)?.decodeToString()
}

View file

@ -0,0 +1,62 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.newproject
import com.intellij.ide.util.projectWizard.SettingsStep
import com.intellij.openapi.Disposable
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.Disposer
import com.intellij.platform.ProjectGeneratorPeer
import com.intellij.ui.dsl.builder.panel
import javax.swing.JComponent
class ZigProjectGeneratorPeer(var handleGit: Boolean): ProjectGeneratorPeer<ZigProjectConfigurationData>, Disposable {
private val newProjectPanel: ZigNewProjectPanel = ZigNewProjectPanel(handleGit).also { Disposer.register(this, it) }
private val myComponent: JComponent by lazy {
panel {
newProjectPanel.attach(this)
}
}
override fun getComponent(): JComponent {
return myComponent
}
override fun buildUI(settingsStep: SettingsStep) {
myComponent
}
override fun getSettings(): ZigProjectConfigurationData {
return newProjectPanel.getData()
}
override fun validate(): ValidationInfo? {
return null
}
override fun isBackgroundJobRunning(): Boolean {
return false
}
override fun dispose() {
}
}

View file

@ -0,0 +1,32 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.newproject
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep
import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase
import com.intellij.platform.DirectoryProjectGenerator
class ZigProjectSettingsStep(
projectGenerator: DirectoryProjectGenerator<ZigProjectConfigurationData>,
callback: AbstractNewProjectStep.AbstractCallback<ZigProjectConfigurationData>?
): ProjectSettingsStepBase<ZigProjectConfigurationData>(projectGenerator, callback)

View file

@ -20,9 +20,8 @@
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>. * along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.falsepattern.zigbrains.project.execution package com.falsepattern.zigbrains.project.run
import com.falsepattern.zigbrains.project.execution.VT100Util.translateVT100Escapes
import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PtyCommandLine import com.intellij.execution.configurations.PtyCommandLine
import com.intellij.execution.process.AnsiEscapeDecoder.ColoredTextAcceptor import com.intellij.execution.process.AnsiEscapeDecoder.ColoredTextAcceptor
@ -47,53 +46,51 @@ class ZigProcessHandler : KillableColoredProcessHandler, ColoredTextAcceptor {
} }
} }
object VT100Util { private val VT100_CHARS = CharArray(256).apply {
private val VT100_CHARS = CharArray(256).apply { this.fill(' ')
this.fill(' ') this[0x6A] = '┘';
this[0x6A] = '┘'; this[0x6B] = '┐';
this[0x6B] = '┐'; this[0x6C] = '┌';
this[0x6C] = '┌'; this[0x6D] = '└';
this[0x6D] = '└'; this[0x6E] = '┼';
this[0x6E] = '┼'; this[0x71] = '─';
this[0x71] = '─'; this[0x74] = '├';
this[0x74] = '├'; this[0x75] = '┤';
this[0x75] = '┤'; this[0x76] = '┴';
this[0x76] = '┴'; this[0x77] = '┬';
this[0x77] = '┬'; this[0x78] = '│';
this[0x78] = '│'; }
}
private const val VT100_BEGIN_SEQ = "\u001B(0"
private const val VT100_BEGIN_SEQ = "\u001B(0" private const val VT100_END_SEQ = "\u001B(B"
private const val VT100_END_SEQ = "\u001B(B" private const val VT100_BEGIN_SEQ_LENGTH: Int = VT100_BEGIN_SEQ.length
private const val VT100_BEGIN_SEQ_LENGTH: Int = VT100_BEGIN_SEQ.length private const val VT100_END_SEQ_LENGTH: Int = VT100_END_SEQ.length
private const val VT100_END_SEQ_LENGTH: Int = VT100_END_SEQ.length
private fun String.translateVT100Escapes(): String {
fun String.translateVT100Escapes(): String { var offset = 0
var offset = 0 val result = StringBuilder()
val result = StringBuilder() val textLength = length
val textLength = length while (offset < textLength) {
while (offset < textLength) { val startIndex = indexOf(VT100_BEGIN_SEQ, offset)
val startIndex = indexOf(VT100_BEGIN_SEQ, offset) if (startIndex < 0) {
if (startIndex < 0) { result.append(substring(offset, textLength).replace(VT100_END_SEQ, ""))
result.append(substring(offset, textLength).replace(VT100_END_SEQ, "")) break
break }
} result.append(this, offset, startIndex)
result.append(this, offset, startIndex) val blockOffset = startIndex + VT100_BEGIN_SEQ_LENGTH
val blockOffset = startIndex + VT100_BEGIN_SEQ_LENGTH var endIndex = indexOf(VT100_END_SEQ, blockOffset)
var endIndex = indexOf(VT100_END_SEQ, blockOffset) if (endIndex < 0) {
if (endIndex < 0) { endIndex = textLength
endIndex = textLength }
} for (i in blockOffset until endIndex) {
for (i in blockOffset until endIndex) { val c = this[i].code
val c = this[i].code if (c >= 256) {
if (c >= 256) { result.append(c)
result.append(c) } else {
} else { result.append(VT100_CHARS[c])
result.append(VT100_CHARS[c]) }
} }
} offset = endIndex + VT100_END_SEQ_LENGTH
offset = endIndex + VT100_END_SEQ_LENGTH }
} return result.toString()
return result.toString()
}
} }

View file

@ -23,22 +23,27 @@
package com.falsepattern.zigbrains.project.run package com.falsepattern.zigbrains.project.run
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.falsepattern.zigbrains.project.zigService import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.configurations.RunProfileState
import com.intellij.execution.configurations.RunnerSettings import com.intellij.execution.configurations.RunnerSettings
import com.intellij.execution.runners.AsyncProgramRunner import com.intellij.execution.runners.AsyncProgramRunner
import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.ui.RunContentDescriptor import com.intellij.execution.ui.RunContentDescriptor
import com.intellij.openapi.application.EDT
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.rd.util.toPromise import com.intellij.openapi.rd.util.toPromise
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import org.jetbrains.concurrency.Promise import org.jetbrains.concurrency.Promise
abstract class ZigProgramRunner<ProfileState: ZigProfileState<*>>(protected val executorId: String): AsyncProgramRunner<RunnerSettings>() { abstract class ZigProgramRunner<ProfileState: ZigProfileState<*>>(protected val executorId: String): AsyncProgramRunner<RunnerSettings>() {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
override fun execute(environment: ExecutionEnvironment, state: RunProfileState): Promise<RunContentDescriptor?> { override fun execute(environment: ExecutionEnvironment, state: RunProfileState): Promise<RunContentDescriptor?> {
return environment.project.zigService.cs.async { return environment.project.zigCoroutineScope.async {
executeAsync(environment, state) executeAsync(environment, state)
}.toPromise() }.toPromise()
} }
@ -49,10 +54,16 @@ abstract class ZigProgramRunner<ProfileState: ZigProfileState<*>>(protected val
val state = castProfileState(state) ?: return null val state = castProfileState(state) ?: return null
execute(state, null, environment) val toolchain = environment.project.zigProjectSettings.state.toolchain ?: return null
withContext(Dispatchers.EDT) {
FileDocumentManager.getInstance().saveAllDocuments()
}
return execute(state, toolchain, environment)
} }
protected abstract fun castProfileState(state: ZigProfileState<*>): ProfileState? protected abstract fun castProfileState(state: ZigProfileState<*>): ProfileState?
abstract suspend fun execute(state: ProfileState, toolchain: ZigToolchain, environment: ExecutionEnvironment) abstract suspend fun execute(state: ProfileState, toolchain: AbstractZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor?
} }

View file

@ -0,0 +1,60 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.run
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfig
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.falsepattern.zigbrains.project.execution.base.executeCommandLine
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.execution.configurations.RunProfile
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.runners.showRunContent
import com.intellij.execution.ui.RunContentDescriptor
import com.intellij.openapi.application.EDT
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ZigRegularRunner: ZigProgramRunner<ZigProfileState<*>>(DefaultRunExecutor.EXECUTOR_ID) {
override suspend fun execute(state: ZigProfileState<*>, toolchain: AbstractZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor? {
val cli = state.getCommandLine(toolchain, false)
val exec = executeCommandLine(cli, environment)
return withContext(Dispatchers.EDT) {
showRunContent(exec, environment)
}
}
override fun castProfileState(state: ZigProfileState<*>): ZigProfileState<*>? {
return state
}
override fun canRun(executorId: String, profile: RunProfile): Boolean {
return this.executorId == executorId && profile is ZigExecConfig<*>
}
override fun getRunnerId(): String {
return "ZigRegularRunner"
}
}

View file

@ -0,0 +1,33 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.settings
import com.falsepattern.zigbrains.lsp.settings.ZLSSettingsConfigurable
import com.falsepattern.zigbrains.shared.MultiConfigurable
import com.intellij.openapi.project.Project
class ZigConfigurable(project: Project): MultiConfigurable(ZigProjectConfigurable(project), ZLSSettingsConfigurable(project)) {
override fun getDisplayName(): String {
return "Zig"
}
}

View file

@ -0,0 +1,59 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.settings
import com.falsepattern.zigbrains.lsp.startLSP
import com.falsepattern.zigbrains.shared.SubConfigurable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.ui.dsl.builder.Panel
class ZigProjectConfigurable(private val project: Project): SubConfigurable {
private var settingsPanel: ZigProjectSettingsPanel? = null
override fun createComponent(panel: Panel) {
settingsPanel?.let { Disposer.dispose(it) }
settingsPanel = ZigProjectSettingsPanel(project).apply { attach(panel) }.also { Disposer.register(this, it) }
}
override fun isModified(): Boolean {
return project.zigProjectSettings.isModified(settingsPanel?.data ?: return false)
}
override fun apply() {
val service = project.zigProjectSettings
val data = settingsPanel?.data ?: return
val modified = service.isModified(data)
service.state = data
if (modified) {
startLSP(project, true)
}
}
override fun reset() {
settingsPanel?.data = project.zigProjectSettings.state
}
override fun dispose() {
settingsPanel = null
}
}

View file

@ -22,14 +22,22 @@
package com.falsepattern.zigbrains.project.settings package com.falsepattern.zigbrains.project.settings
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainConverter import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.util.xmlb.annotations.OptionTag import com.intellij.util.xmlb.annotations.Transient
import kotlin.io.path.pathString
@JvmRecord
data class ZigProjectSettings( data class ZigProjectSettings(
val direnv: Boolean = true, var direnv: Boolean = true,
val overrideStdPath: Boolean = false, var overrideStdPath: Boolean = false,
val explicitPathToStd: String? = null, var explicitPathToStd: String? = null,
@OptionTag(converter = ZigToolchainConverter::class) val toolchain: ZigToolchain? = null var toolchainPath: String? = null
) ) {
@get:Transient
@set:Transient
var toolchain: LocalZigToolchain?
get() = toolchainPath?.toNioPathOrNull()?.let { LocalZigToolchain(it) }
set(value) {
toolchainPath = value?.location?.pathString
}
}

View file

@ -0,0 +1,162 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.settings
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.EDT
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.JBColor
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.textFieldWithBrowseButton
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Panel
import kotlinx.coroutines.*
import javax.swing.event.DocumentEvent
import kotlin.io.path.pathString
class ZigProjectSettingsPanel(private val project: Project?) : Disposable {
private val direnv = JBCheckBox(ZigBrainsBundle.message("settings.project.label.direnv"))
private val pathToToolchain = textFieldWithBrowseButton(
project,
ZigBrainsBundle.message("dialog.title.zig-toolchain"),
FileChooserDescriptorFactory.createSingleFolderDescriptor()
).also {
it.textField.document.addDocumentListener(object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
dispatchUpdateUI()
}
})
Disposer.register(this, it)
}
private val toolchainVersion = JBLabel()
private val stdFieldOverride = JBCheckBox(ZigBrainsBundle.message("settings.project.label.override-std")).apply {
addChangeListener {
if (isSelected) {
pathToStd.isEnabled = true
} else {
pathToStd.isEnabled = false
updateUI()
}
}
}
private val pathToStd = textFieldWithBrowseButton(
project,
ZigBrainsBundle.message("dialog.title.zig-std"),
FileChooserDescriptorFactory.createSingleFolderDescriptor()
).also { Disposer.register(this, it) }
private var debounce: Job? = null
suspend fun autodetect() {
val data = UserDataHolderBase()
data.putUserData(LocalZigToolchain.DIRENV_KEY, direnv.isSelected)
val tc = ZigToolchainProvider.suggestToolchain(project, data) ?: return
if (tc !is LocalZigToolchain) {
TODO("Implement non-local zig toolchain in config")
}
pathToToolchain.text = tc.location.pathString
dispatchUpdateUI()
}
var data
get() = ZigProjectSettings(
direnv.isSelected,
stdFieldOverride.isSelected,
pathToStd.text,
pathToToolchain.text
)
set(value) {
direnv.isSelected = value.direnv
pathToToolchain.text = value.toolchainPath ?: ""
stdFieldOverride.isSelected = value.overrideStdPath
pathToStd.text = value.explicitPathToStd ?: ""
pathToStd.isEnabled = value.overrideStdPath
dispatchUpdateUI()
}
fun attach(p: Panel): Unit = with(p) {
val project = project ?: ProjectManager.getInstance().defaultProject
data = project.zigProjectSettings.state
group(ZigBrainsBundle.message("settings.project.group.title")) {
row(ZigBrainsBundle.message("settings.project.label.toolchain")) {
cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
if (DirenvCmd.direnvInstalled() && !project.isDefault) {
cell(direnv)
}
button(ZigBrainsBundle.message("settings.project.label.toolchain-autodetect")) {
this@ZigProjectSettingsPanel.project.zigCoroutineScope.launch {
autodetect()
}
}
}
row(ZigBrainsBundle.message("settings.project.label.toolchain-version")) {
cell(toolchainVersion)
}
row(ZigBrainsBundle.message("settings.project.label.std-location")) {
cell(pathToStd).resizableColumn().align(AlignX.FILL)
cell(stdFieldOverride)
}
}
}
private fun dispatchUpdateUI() {
debounce?.cancel("New debounce")
debounce = project.zigCoroutineScope.launch {
updateUI()
}
}
private suspend fun updateUI() {
val pathToToolchain = this.pathToToolchain.text.toNioPathOrNull()
delay(200)
val toolchain = pathToToolchain?.let { LocalZigToolchain(it) }
val zig = toolchain?.zig
val env = zig?.getEnv(project)
val version = env?.version
val stdPath = env?.stdPath(toolchain)
withContext(Dispatchers.EDT) {
toolchainVersion.text = version ?: ""
toolchainVersion.foreground = JBColor.foreground()
if (!stdFieldOverride.isSelected) {
pathToStd.text = stdPath?.pathString ?: ""
}
}
}
override fun dispose() {
debounce?.cancel("Disposed")
}
}

View file

@ -0,0 +1,33 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.steps.discovery
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
class ZigDiscoverStepsAction: AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
project.zigStepDiscovery.triggerReload()
}
}

View file

@ -0,0 +1,39 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.steps.discovery
interface ZigStepDiscoveryListener {
suspend fun preReload() {}
suspend fun postReload(steps: List<Pair<String, String?>>) {}
suspend fun errorReload(type: ErrorType, details: String?) {}
suspend fun timeoutReload(seconds: Int) {}
companion object {
}
enum class ErrorType {
MissingToolchain,
MissingBuildZig,
GeneralError
}
}

View file

@ -0,0 +1,160 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.steps.discovery
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener.ErrorType
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.vfs.toNioPathOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicBoolean
@Service(Service.Level.PROJECT)
class ZigStepDiscoveryService(private val project: Project) {
private val reloading = AtomicBoolean(false)
private val reloadScheduled = AtomicBoolean(false)
private val reloadMutex = Mutex()
private var CURRENT_TIMEOUT_SEC = DEFAULT_TIMEOUT_SEC
private val listeners = ArrayList<ZigStepDiscoveryListener>()
private val listenerMutex = Mutex()
suspend fun register(listener: ZigStepDiscoveryListener): Disposable {
return listenerMutex.withLock {
listeners.add(listener)
Disposable {
zigCoroutineScope.launch {
listenerMutex.withLock {
listeners.remove(listener)
}
}
}
}
}
fun triggerReload() {
project.zigCoroutineScope.launch {
reloadMutex.withLock {
if (reloading.getAndSet(true)) {
reloadScheduled.set(true)
return@launch
}
}
dispatchReload()
}
}
private tailrec suspend fun doReload() {
preReload()
val toolchain = project.zigProjectSettings.state.toolchain ?: run {
errorReload(ErrorType.MissingToolchain)
return
}
val zig = toolchain.zig
val result = zig.callWithArgs(
project.guessProjectDir()?.toNioPathOrNull(),
"build", "-l",
timeoutMillis = CURRENT_TIMEOUT_SEC * 1000L
)
if (result.checkSuccess(LOG)) {
CURRENT_TIMEOUT_SEC = DEFAULT_TIMEOUT_SEC
val lines = result.stdoutLines
val steps = ArrayList<Pair<String, String?>>()
for (line in lines) {
val parts = line.trim().split(SPACES, 2)
if (parts.size == 2) {
steps.add(Pair(parts[0], parts[1]))
} else {
steps.add(Pair(parts[0], null))
}
}
postReload(steps)
} else if (result.isTimeout) {
timeoutReload(CURRENT_TIMEOUT_SEC)
CURRENT_TIMEOUT_SEC *= 2
} else if (result.stderrLines.any { it.contains("error: no build.zig file found, in the current directory or any parent directories") }) {
errorReload(ErrorType.MissingBuildZig, result.stderr)
} else {
errorReload(ErrorType.GeneralError, result.stderr)
}
if (reloadMutex.withLock {
if (reloadScheduled.getAndSet(false)) {
return@withLock true
}
reloading.set(false)
return@withLock false
}) {
doReload()
}
}
private suspend fun dispatchReload() {
withContext(Dispatchers.EDT) {
FileDocumentManager.getInstance().saveAllDocuments()
}
doReload()
}
private suspend fun preReload() {
listenerMutex.withLock {
listeners.forEach { it.preReload() }
}
}
private suspend fun postReload(steps: List<Pair<String, String?>>) {
listenerMutex.withLock {
listeners.forEach { it.postReload(steps) }
}
}
private suspend fun errorReload(type: ErrorType, details: String? = null) {
listenerMutex.withLock {
listeners.forEach { it.errorReload(type, details) }
}
}
private suspend fun timeoutReload(seconds: Int) {
listenerMutex.withLock {
listeners.forEach { it.timeoutReload(seconds) }
}
}
}
val Project.zigStepDiscovery get() = service<ZigStepDiscoveryService>()
private val SPACES = Regex("\\s+")
private const val DEFAULT_TIMEOUT_SEC = 10
private val LOG = Logger.getInstance(ZigStepDiscoveryService::class.java)

View file

@ -20,23 +20,28 @@
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>. * along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/ */
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType package com.falsepattern.zigbrains.project.steps.ui
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val lsp4ijVersion: String by project import com.intellij.ide.projectView.PresentationData
val lsp4jVersion: String by project import com.intellij.ide.util.treeView.PresentableNodeDescriptor
val lsp4ijNightly = property("lsp4ijNightly").toString().toBoolean() import com.intellij.openapi.project.Project
val lsp4ijDepString = "${if (lsp4ijNightly) "nightly." else ""}com.jetbrains.plugins:com.redhat.devtools.lsp4ij:$lsp4ijVersion" import com.intellij.ui.SimpleTextAttributes
tasks.withType<KotlinCompile> { import javax.swing.Icon
kotlinOptions {
freeCompilerArgs += "-Xcontext-receivers" open class BaseNodeDescriptor<T>(project: Project?, displayName: String, displayIcon: Icon, private var description: String? = null): PresentableNodeDescriptor<T>(project, null) {
init {
icon = displayIcon
myName = displayName
update()
}
override fun update(presentation: PresentationData) {
presentation.setIcon(icon)
presentation.addText(myName, SimpleTextAttributes.REGULAR_ATTRIBUTES)
presentation.tooltip = description
}
override fun getElement(): T? {
return null
} }
} }
dependencies {
intellijPlatform {
create(IntelliJPlatformType.IntellijIdeaCommunity, providers.gradleProperty("ideaCommunityVersion"))
}
intellijPlatformPluginDependency(lsp4ijDepString)
compileOnly("org.eclipse.lsp4j:org.eclipse.lsp4j:$lsp4jVersion")
implementation(project(":core"))
}

View file

@ -0,0 +1,229 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.steps.ui
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.build.ZigConfigTypeBuild
import com.falsepattern.zigbrains.project.execution.build.ZigExecConfigBuild
import com.falsepattern.zigbrains.project.execution.firstConfigFactory
import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener
import com.falsepattern.zigbrains.project.steps.discovery.zigStepDiscovery
import com.intellij.execution.ProgramRunnerUtil
import com.intellij.execution.RunManager
import com.intellij.execution.RunnerAndConfigurationSettings
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.icons.AllIcons
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.application.EDT
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.SimpleToolWindowPanel
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.ui.AnimatedIcon
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.components.JBTextArea
import com.intellij.ui.content.Content
import com.intellij.ui.content.ContentFactory
import com.intellij.ui.treeStructure.Tree
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.intellij.lang.annotations.JdkConstants.BoxLayoutAxis
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.BoxLayout
import javax.swing.JPanel
import javax.swing.SwingConstants
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreePath
class BuildToolWindowContext(private val project: Project): Disposable {
val rootNode: DefaultMutableTreeNode = DefaultMutableTreeNode(BaseNodeDescriptor<Any>(project, project.name, AllIcons.Actions.ProjectDirectory))
private val buildZig: DefaultMutableTreeNode = DefaultMutableTreeNode(BaseNodeDescriptor<Any>(project, ZigBrainsBundle.message("build.tool.window.tree.steps.label"), Icons.ZIG))
init {
rootNode.add(buildZig)
}
private fun setViewportTree(viewport: JBScrollPane) {
val model = DefaultTreeModel(rootNode)
val tree = Tree(model)
tree.expandPath(TreePath(model.getPathToRoot(buildZig)))
viewport.setViewportView(tree)
tree.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
if (e.clickCount == 2) {
val node = tree.lastSelectedPathComponent as? DefaultMutableTreeNode ?: return
val step = node.userObject as? StepNodeDescriptor ?: return
val stepName = step.element?.name ?: return
val manager = RunManager.getInstance(project)
val config = getExistingRunConfig(manager, stepName) ?: run {
val factory = firstConfigFactory<ZigConfigTypeBuild>()
val newConfig = manager.createConfiguration("zig build $stepName", factory)
val config = newConfig.configuration as ZigExecConfigBuild
config.buildSteps.args = listOf(stepName)
manager.addConfiguration(newConfig)
return@run newConfig
}
manager.selectedConfiguration = config
ProgramRunnerUtil.executeConfiguration(config, DefaultRunExecutor.getRunExecutorInstance())
}
}
})
}
private fun createContentPanel(): Content {
val contentPanel = SimpleToolWindowPanel(false)
val body = JPanel(GridBagLayout())
contentPanel.setLayout(GridBagLayout())
val c = GridBagConstraints()
c.fill = GridBagConstraints.BOTH
c.weightx = 1.0
c.weighty = 1.0
contentPanel.add(body, c)
c.fill = GridBagConstraints.HORIZONTAL
c.anchor = GridBagConstraints.NORTHWEST
c.weightx = 1.0
c.weighty = 0.0
val toolbar = ActionManager.getInstance().createActionToolbar("ZigToolbar", DefaultActionGroup(ActionManager.getInstance().getAction("zigbrains.discover.steps")), true)
toolbar.targetComponent = null
body.add(toolbar.component, c)
c.gridwidth = 1
c.gridy = 1
c.weighty = 1.0
c.fill = GridBagConstraints.BOTH
val viewport = JBScrollPane()
viewport.setViewportNoContent()
body.add(viewport, c)
val content = ContentFactory.getInstance().createContent(contentPanel, "", false)
content.putUserData(VIEWPORT, viewport)
return content
}
override fun dispose() {
}
companion object {
suspend fun create(project: Project, window: ToolWindow) {
withContext(Dispatchers.EDT) {
val context = BuildToolWindowContext(project)
Disposer.register(context, project.zigStepDiscovery.register(context.BuildReloadListener()))
Disposer.register(window.disposable, context)
window.contentManager.addContent(context.createContentPanel())
}
}
}
inner class BuildReloadListener: ZigStepDiscoveryListener {
override suspend fun preReload() {
getViewport(project)?.setViewportLoading()
}
override suspend fun postReload(steps: List<Pair<String, String?>>) {
buildZig.removeAllChildren()
for ((task, description) in steps) {
val icon = when(task) {
"install" -> AllIcons.Actions.Install
"uninstall" -> AllIcons.Actions.Uninstall
else -> AllIcons.RunConfigurations.TestState.Run
}
buildZig.add(DefaultMutableTreeNode(StepNodeDescriptor(project, task, icon, description)))
}
withContext(Dispatchers.EDT) {
getViewport(project)?.let { setViewportTree(it) }
}
}
override suspend fun errorReload(type: ZigStepDiscoveryListener.ErrorType, details: String?) {
withContext(Dispatchers.EDT) {
getViewport(project)?.setViewportError(ZigBrainsBundle.message(when(type) {
ZigStepDiscoveryListener.ErrorType.MissingToolchain -> "build.tool.window.status.error.missing-toolchain"
ZigStepDiscoveryListener.ErrorType.MissingBuildZig -> "build.tool.window.status.error.missing-build-zig"
ZigStepDiscoveryListener.ErrorType.GeneralError -> "build.tool.window.status.error.general"
}), details)
}
}
override suspend fun timeoutReload(seconds: Int) {
withContext(Dispatchers.EDT) {
getViewport(project)?.setViewportError(ZigBrainsBundle.message("build.tool.window.status.timeout", seconds), null)
}
}
}
}
private fun JBScrollPane.setViewportLoading() {
setViewportView(JBLabel(ZigBrainsBundle.message("build.tool.window.status.loading"), AnimatedIcon.Default(), SwingConstants.CENTER))
}
private fun JBScrollPane.setViewportNoContent() {
setViewportView(JBLabel(ZigBrainsBundle.message("build.tool.window.status.not-scanned"), AllIcons.General.Information, SwingConstants.CENTER))
}
private fun JBScrollPane.setViewportError(msg: String, details: String?) {
val result = JPanel()
result.layout = BoxLayout(result, BoxLayout.Y_AXIS)
result.add(JBLabel(msg, AllIcons.General.Error, SwingConstants.CENTER))
if (details != null) {
val code = JBTextArea()
code.isEditable = false
code.text = details
val scroll = JBScrollPane(code)
result.add(scroll)
}
setViewportView(result)
}
private fun getViewport(project: Project): JBScrollPane? {
val toolWindow = ToolWindowManager.getInstance(project).getToolWindow("zigbrains.build") ?: return null
val cm = toolWindow.contentManager
val content = cm.getContent(0) ?: return null
return content.getUserData(VIEWPORT)
}
private fun getExistingRunConfig(manager: RunManager, stepName: String): RunnerAndConfigurationSettings? {
for (config in manager.getConfigurationSettingsList(ZigConfigTypeBuild::class.java)) {
val build = config.configuration as? ZigExecConfigBuild ?: continue
val steps = build.buildSteps.args
if (steps.size != 1)
continue
if (steps[0] != stepName)
continue
return config
}
return null
}
private val VIEWPORT = Key.create<JBScrollPane>("MODEL")

View file

@ -0,0 +1,37 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.steps.ui
import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.platform.ide.progress.ModalTaskOwner
class BuildToolWindowFactory: ToolWindowFactory {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
runModalOrBlocking({ModalTaskOwner.project(project)}, {"BuildToolWindowFactory.createToolWindowContent"}) {
BuildToolWindowContext.create(project, toolWindow)
}
}
}

View file

@ -0,0 +1,26 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.steps.ui
@JvmRecord
data class StepDetails(val name: String)

View file

@ -0,0 +1,33 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.steps.ui
import com.intellij.openapi.project.Project
import javax.swing.Icon
class StepNodeDescriptor(project: Project?, private val stepName: String, displayIcon: Icon, description: String? = null) :
BaseNodeDescriptor<StepDetails>(project, stepName, displayIcon, description) {
override fun getElement(): StepDetails? {
return StepDetails(stepName)
}
}

View file

@ -0,0 +1,33 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.template
class ZigExecutableTemplate: ZigProjectTemplate("Executable (application)", true) {
override fun fileTemplates(): Map<String, String> {
return mapOf(
"src/main.zig" to "application",
"build.zig" to "application",
"build.zig.zon" to "shared"
)
}
}

View file

@ -0,0 +1,29 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.template
class ZigInitTemplate: ZigProjectTemplate("Generate using \"zig init\"", true) {
override fun fileTemplates(): Map<String, String> {
throw UnsupportedOperationException()
}
}

View file

@ -0,0 +1,33 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.template
class ZigLibraryTemplate: ZigProjectTemplate("Library (static)", true) {
override fun fileTemplates(): Map<String, String> {
return mapOf(
"src/root.zig" to "static",
"build.zig" to "static",
"build.zig.zon" to "shared"
)
}
}

View file

@ -0,0 +1,35 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.template
import com.falsepattern.zigbrains.Icons
import com.intellij.openapi.util.NlsContexts.ListItem
import javax.swing.Icon
sealed class ZigProjectTemplate(
@ListItem val name: String,
val isBinary: Boolean,
val icon: Icon = Icons.ZIG
) {
abstract fun fileTemplates(): Map<String, String>
}

View file

@ -22,12 +22,18 @@
package com.falsepattern.zigbrains.project.toolchain package com.falsepattern.zigbrains.project.toolchain
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import java.nio.file.Path import java.nio.file.Path
abstract class AbstractZigToolchain( abstract class AbstractZigToolchain {
val location: Path, val zig: ZigCompilerTool by lazy { ZigCompilerTool(this) }
val project: Project?
) { abstract fun workingDirectory(project: Project? = null): Path?
abstract fun pathToExecutable(toolName: String): Path
abstract suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project? = null): GeneralCommandLine
abstract fun pathToExecutable(toolName: String, project: Project? = null): Path
} }

View file

@ -0,0 +1,56 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain
import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.KeyWithDefaultValue
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.vfs.toNioPathOrNull
import java.nio.file.Path
class LocalZigToolchain(val location: Path): AbstractZigToolchain() {
override fun workingDirectory(project: Project?): Path? {
return project?.guessProjectDir()?.toNioPathOrNull()
}
override suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project?): GeneralCommandLine {
if (project != null && (commandLine.getUserData(DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) {
commandLine.withEnvironment(DirenvCmd.importDirenv(project).env)
}
return commandLine
}
override fun pathToExecutable(toolName: String, project: Project?): Path {
val exeName = if (SystemInfo.isWindows) "$toolName.exe" else toolName
return location.resolve(exeName)
}
companion object {
val DIRENV_KEY = KeyWithDefaultValue.create<Boolean>("ZIG_LOCAL_DIRENV")
}
}

View file

@ -0,0 +1,66 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain
import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.direnv.emptyEnv
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.io.toNioPathOrNull
import kotlinx.serialization.json.*
import kotlin.io.path.pathString
class LocalZigToolchainProvider: ZigToolchainProvider<LocalZigToolchain> {
override suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): LocalZigToolchain? {
val env = if (project != null && (extraData.getUserData(LocalZigToolchain.DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) {
DirenvCmd.importDirenv(project)
} else {
emptyEnv
}
val zigExePath = env.findExecutableOnPATH("zig") ?: return null
return LocalZigToolchain(zigExePath.parent)
}
override val serialMarker: String
get() = "local"
override fun deserialize(data: JsonElement): LocalZigToolchain? {
if (data !is JsonObject)
return null
val loc = data["location"] as? JsonPrimitive ?: return null
val path = loc.content.toNioPathOrNull() ?: return null
return LocalZigToolchain(path)
}
override fun canSerialize(toolchain: AbstractZigToolchain): Boolean {
return toolchain is LocalZigToolchain
}
override fun serialize(toolchain: LocalZigToolchain): JsonElement {
return buildJsonObject {
put("location", toolchain.location.pathString)
}
}
}

View file

@ -0,0 +1,86 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain
import com.falsepattern.zigbrains.lsp.config.SuspendingZLSConfigProvider
import com.falsepattern.zigbrains.lsp.config.ZLSConfig
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.io.toNioPathOrNull
import kotlin.io.path.notExists
import kotlin.io.path.pathString
class ToolchainZLSConfigProvider: SuspendingZLSConfigProvider {
override suspend fun getEnvironment(project: Project, previous: ZLSConfig): ZLSConfig {
val svc = project.zigProjectSettings
var state = svc.state
val toolchain = state.toolchain ?: ZigToolchainProvider.suggestToolchain(project, UserDataHolderBase()) ?: return previous
val env = toolchain.zig.getEnv(project)
val exe = env.zigExecutable.toNioPathOrNull() ?: run {
Notification(
"zigbrains-lsp",
"Invalid zig executable path: ${env.zigExecutable}",
NotificationType.ERROR
).notify(project)
return previous
}
if (exe.notExists()) {
Notification(
"zigbrains-lsp",
"Zig executable does not exiust: $exe",
NotificationType.ERROR
).notify(project)
return previous
}
var lib = if (state.overrideStdPath && state.explicitPathToStd != null) {
state.explicitPathToStd?.toNioPathOrNull() ?: run {
Notification(
"zigbrains-lsp",
"Invalid zig standard library path override: ${state.explicitPathToStd}",
NotificationType.ERROR
).notify(project)
null
}
} else null
if (lib == null) {
lib = env.libDirectory.toNioPathOrNull() ?: run {
Notification(
"zigbrains-lsp",
"Invalid zig standard library path: ${env.libDirectory}",
NotificationType.ERROR
).notify(project)
null
}
}
if (lib == null)
return previous
return previous.copy(zigExePath = exe.pathString, zigLibPath = lib.pathString)
}
}

View file

@ -23,10 +23,12 @@ package com.falsepattern.zigbrains.project.toolchain
import com.intellij.openapi.util.io.toNioPathOrNull import com.intellij.openapi.util.io.toNioPathOrNull
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.nio.file.Path import java.nio.file.Path
@JvmRecord @JvmRecord
@Serializable
data class ZigToolchainEnvironmentSerializable( data class ZigToolchainEnvironmentSerializable(
@SerialName("zig_exe") val zigExecutable: String, @SerialName("zig_exe") val zigExecutable: String,
@SerialName("std_dir") val stdDirectory: String, @SerialName("std_dir") val stdDirectory: String,
@ -35,7 +37,7 @@ data class ZigToolchainEnvironmentSerializable(
@SerialName("version") val version: String, @SerialName("version") val version: String,
@SerialName("target") val target: String @SerialName("target") val target: String
) { ) {
fun stdPath(toolchain: AbstractZigToolchain): Path? { fun stdPath(toolchain: LocalZigToolchain): Path? {
val path = stdDirectory.toNioPathOrNull() ?: return null val path = stdDirectory.toNioPathOrNull() ?: return null
if (path.isAbsolute) if (path.isAbsolute)
return path return path

View file

@ -23,31 +23,34 @@
package com.falsepattern.zigbrains.project.toolchain package com.falsepattern.zigbrains.project.toolchain
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider.Companion.EXTENSION_POINT_NAME import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider.Companion.EXTENSION_POINT_NAME
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.UserDataHolder
import com.intellij.util.xmlb.Converter import com.intellij.util.xmlb.Converter
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
sealed interface ZigToolchainProvider { sealed interface ZigToolchainProvider<in T: AbstractZigToolchain> {
suspend fun getToolchain(project: Project?): ZigToolchain? suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain?
val serialMarker: String val serialMarker: String
fun deserialize(data: JsonElement): ZigToolchain? fun deserialize(data: JsonElement): AbstractZigToolchain?
fun canSerialize(toolchain: ZigToolchain): Boolean fun canSerialize(toolchain: AbstractZigToolchain): Boolean
fun serialize(toolchain: ZigToolchain): JsonElement fun serialize(toolchain: T): JsonElement
companion object { companion object {
val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider") val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider<*>>("com.falsepattern.zigbrains.toolchainProvider")
suspend fun findToolchains(project: Project?): ZigToolchain? { suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? {
return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.getToolchain(project) } return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(project, extraData) }
} }
} }
} }
class ZigToolchainConverter: Converter<ZigToolchain>() { @Suppress("UNCHECKED_CAST")
override fun fromString(value: String): ZigToolchain? { private fun <T: AbstractZigToolchain> ZigToolchainProvider<T>.serialize(toolchain: AbstractZigToolchain) = serialize(toolchain as T)
class ZigToolchainConverter: Converter<AbstractZigToolchain>() {
override fun fromString(value: String): AbstractZigToolchain? {
val json = Json.parseToJsonElement(value) as? JsonObject ?: return null val json = Json.parseToJsonElement(value) as? JsonObject ?: return null
val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null
val data = json["data"] ?: return null val data = json["data"] ?: return null
@ -55,7 +58,7 @@ class ZigToolchainConverter: Converter<ZigToolchain>() {
return provider.deserialize(data) return provider.deserialize(data)
} }
override fun toString(value: ZigToolchain): String? { override fun toString(value: AbstractZigToolchain): String? {
val provider = EXTENSION_POINT_NAME.extensionList.find { it.canSerialize(value) } ?: return null val provider = EXTENSION_POINT_NAME.extensionList.find { it.canSerialize(value) } ?: return null
return buildJsonObject { return buildJsonObject {
put("marker", provider.serialMarker) put("marker", provider.serialMarker)

View file

@ -0,0 +1,33 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain.stdlib
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.AdditionalLibraryRootsProvider
import com.intellij.openapi.roots.SyntheticLibrary
class ZigLibraryRootProvider: AdditionalLibraryRootsProvider() {
override fun getAdditionalProjectLibraries(project: Project): Collection<SyntheticLibrary> {
return setOf(ZigSyntheticLibrary(project))
}
}

View file

@ -0,0 +1,113 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain.stdlib
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.project.settings.ZigProjectSettings
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.shared.coroutine.getOrAwaitModalOrBlocking
import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.navigation.ItemPresentation
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.SyntheticLibrary
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.refreshAndFindVirtualDirectory
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.util.suspendingLazy
import kotlinx.coroutines.async
import java.util.*
import javax.swing.Icon
class ZigSyntheticLibrary(val project: Project) : SyntheticLibrary(), ItemPresentation {
private val roots = project.zigCoroutineScope.suspendingLazy {
getRoots(project.zigProjectSettings.state, project)
}
private val name = project.zigCoroutineScope.suspendingLazy {
getName(project.zigProjectSettings.state, project)
}
override fun equals(other: Any?): Boolean {
if (other !is ZigSyntheticLibrary)
return false
return roots == other.roots
}
override fun hashCode(): Int {
return Objects.hash(roots)
}
override fun getPresentableText(): String {
return name.getOrAwaitModalOrBlocking({ModalTaskOwner.project(project)}, {"ZigSyntheticLibrary.getPresentableText"})
}
override fun getIcon(unused: Boolean): Icon {
return Icons.ZIG
}
override fun getSourceRoots(): Collection<VirtualFile> {
return roots.getOrAwaitModalOrBlocking({ ModalTaskOwner.project(project)}, {"ZigSyntheticLibrary.getSourceRoots"})
}
}
private suspend fun getName(
state: ZigProjectSettings,
project: Project
): String {
val tc = state.toolchain ?: return "Zig"
val version = tc.zig.getEnv(project).version
return "Zig $version"
}
private suspend fun getRoots(
state: ZigProjectSettings,
project: Project
): Set<VirtualFile> {
val toolchain = state.toolchain
run {
val ePathStr = state.explicitPathToStd ?: return@run
val ePath = ePathStr.toNioPathOrNull() ?: return@run
if (ePath.isAbsolute) {
val roots = ePath.refreshAndFindVirtualDirectory() ?: return@run
return setOf(roots)
} else if (toolchain != null) {
val stdPath = toolchain.location.resolve(ePath)
if (stdPath.isAbsolute) {
val roots = stdPath.refreshAndFindVirtualDirectory() ?: return@run
return setOf(roots)
}
}
}
if (toolchain != null) {
val stdPath = toolchain.zig.getEnv(project).stdPath(toolchain) ?: return emptySet()
val roots = stdPath.refreshAndFindVirtualDirectory() ?: return emptySet()
return setOf(roots)
}
return emptySet()
}

View file

@ -24,13 +24,23 @@ package com.falsepattern.zigbrains.project.toolchain.tools
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSerializable import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSerializable
import com.intellij.openapi.project.Project
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.nio.file.Path
class ZigCompilerTool(toolchain: AbstractZigToolchain): ZigTool(toolchain) { class ZigCompilerTool(toolchain: AbstractZigToolchain) : ZigTool(toolchain) {
override val toolName: String override val toolName: String
get() = "zig" get() = "zig"
suspend fun getEnv(): ZigToolchainEnvironmentSerializable { fun path(): Path {
return Json.decodeFromString<ZigToolchainEnvironmentSerializable>(callWithArgs(toolchain.location, "env").stdout) return toolchain.pathToExecutable(toolName)
}
suspend fun getEnv(project: Project?): ZigToolchainEnvironmentSerializable {
return envJson.decodeFromString<ZigToolchainEnvironmentSerializable>(callWithArgs(toolchain.workingDirectory(project), "env").stdout)
} }
} }
private val envJson = Json {
ignoreUnknownKeys = true
}

View file

@ -26,35 +26,37 @@ import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.ProcessOutput import com.intellij.execution.process.ProcessOutput
import com.intellij.util.io.awaitExit import com.intellij.util.io.awaitExit
import com.intellij.util.io.readLineAsync
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeoutOrNull
import java.nio.file.Path import java.nio.file.Path
import java.time.Duration
abstract class ZigTool(val toolchain: AbstractZigToolchain) { abstract class ZigTool(val toolchain: AbstractZigToolchain) {
abstract val toolName: String abstract val toolName: String
suspend fun callWithArgs(workingDirectory: Path?, vararg parameters: String): ProcessOutput { suspend fun callWithArgs(workingDirectory: Path?, vararg parameters: String, timeoutMillis: Long = Long.MAX_VALUE): ProcessOutput {
val process = createBaseCommandLine(workingDirectory, *parameters).createProcess() val process = createBaseCommandLine(workingDirectory, *parameters).createProcess()
val exitCode = process.awaitExit()
val exitCode = withTimeoutOrNull(timeoutMillis) {
process.awaitExit()
}
return runInterruptible { return runInterruptible {
ProcessOutput( ProcessOutput(
process.inputStream.bufferedReader().use { it.readText() }, process.inputStream.bufferedReader().use { it.readText() },
process.errorStream.bufferedReader().use { it.readText() }, process.errorStream.bufferedReader().use { it.readText() },
exitCode, exitCode ?: -1,
false, exitCode == null,
false false
) )
} }
} }
protected suspend fun createBaseCommandLine(workingDirectory: Path?, private suspend fun createBaseCommandLine(workingDirectory: Path?,
vararg parameters: String): GeneralCommandLine { vararg parameters: String): GeneralCommandLine {
return GeneralCommandLine() val cli = GeneralCommandLine()
.withExePath(toolchain.pathToExecutable(toolName).toString()) .withExePath(toolchain.pathToExecutable(toolName).toString())
.withWorkDirectory(workingDirectory?.toString()) .withWorkDirectory(workingDirectory?.toString())
.withParameters(*parameters) .withParameters(*parameters)
.withCharset(Charsets.UTF_8) .withCharset(Charsets.UTF_8)
return toolchain.patchCommandLine(cli)
} }
} }

View file

@ -0,0 +1,54 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.shared
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.util.Disposer
import com.intellij.ui.dsl.builder.panel
import javax.swing.JComponent
abstract class MultiConfigurable(private vararg val configurables: SubConfigurable): Configurable {
override fun createComponent(): JComponent? {
return panel {
for (configurable in configurables) {
configurable.createComponent(this)
}
}
}
override fun isModified(): Boolean {
return configurables.any { it.isModified() }
}
override fun apply() {
configurables.forEach { it.apply() }
}
override fun reset() {
configurables.forEach { it.reset() }
}
override fun disposeUIResources() {
configurables.forEach { Disposer.dispose(it) }
}
}

View file

@ -22,15 +22,14 @@
package com.falsepattern.zigbrains.shared package com.falsepattern.zigbrains.shared
import com.intellij.openapi.options.Configurable import com.intellij.openapi.Disposable
import com.intellij.openapi.options.ConfigurationException
import com.intellij.ui.dsl.builder.Panel import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.panel
import javax.swing.JComponent
interface NestedConfigurable: Configurable {
override fun createComponent() = panel {
createComponent(this)
}
interface SubConfigurable: Disposable {
fun createComponent(panel: Panel) fun createComponent(panel: Panel)
fun isModified(): Boolean
@Throws(ConfigurationException::class)
fun apply()
fun reset()
} }

View file

@ -0,0 +1,40 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.shared
import com.intellij.openapi.extensions.ExtensionPointName
interface ZBFeatures {
fun getDebug(): Boolean {
return false
}
companion object {
val EXTENSION_POINT_NAME = ExtensionPointName.create<ZBFeatures>("com.falsepattern.zigbrains.featureProvider")
fun debug(): Boolean {
val extensions = EXTENSION_POINT_NAME.extensionList
return extensions.any { it.getDebug() }
}
}
}

View file

@ -20,14 +20,24 @@
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>. * along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.falsepattern.zigbrains.project package com.falsepattern.zigbrains.shared
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.util.application
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@Service(Service.Level.PROJECT) @Service(Service.Level.PROJECT)
class ZigProjectService(val cs: CoroutineScope) class ZigProjectService(val cs: CoroutineScope)
val Project.zigService get() = service<ZigProjectService>() @Service
class ZigAppService(val cs: CoroutineScope)
val Project?.zigCoroutineScope get() =
if (this == null || this.isDefault)
application.service<ZigAppService>().cs
else
this.service<ZigProjectService>().cs
val zigCoroutineScope get() = application.service<ZigAppService>().cs

View file

@ -0,0 +1,104 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.shared.cli
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.intellij.openapi.options.ConfigurationException
import java.util.*
import kotlin.collections.ArrayList
//From Apache Ant
/**
* Crack a command line.
* @param toProcess the command line to process.
* @return the command line broken into strings.
* An empty or null toProcess parameter results in a zero sized array.
*/
@Throws(ConfigurationException::class)
fun translateCommandline(toProcess: String): List<String> {
if (toProcess.isEmpty()) {
//no command? no string
return listOf()
}
// parse with a simple finite state machine
val normal = 0
val inQuote = 1
val inDoubleQuote = 2
var state = normal
val tok = StringTokenizer(toProcess, "\"' ", true)
val result = ArrayList<String>()
val current = StringBuilder()
var lastTokenHasBeenQuoted = false
while (tok.hasMoreTokens()) {
val nextTok: String = tok.nextToken()
when (state) {
inQuote -> if ("'" == nextTok) {
lastTokenHasBeenQuoted = true
state = normal
} else {
current.append(nextTok)
}
inDoubleQuote -> if ("\"" == nextTok) {
lastTokenHasBeenQuoted = true
state = normal
} else {
current.append(nextTok)
}
else -> {
if ("'" == nextTok) {
state = inQuote
} else if ("\"" == nextTok) {
state = inDoubleQuote
} else if (" " == nextTok) {
if (lastTokenHasBeenQuoted || current.isNotEmpty()) {
result.add(current.toString())
current.setLength(0)
}
} else {
current.append(nextTok)
}
lastTokenHasBeenQuoted = false
}
}
}
if (lastTokenHasBeenQuoted || current.isNotEmpty()) {
result.add(current.toString())
}
if (state == inQuote || state == inDoubleQuote) {
throw ConfigurationException(ZigBrainsBundle.message("exception.translate-command-line.unbalanced-quotes", toProcess))
}
return result
}
fun coloredCliFlags(colored: Boolean, debug: Boolean): List<String> {
return if (debug) {
emptyList()
} else {
listOf("--color", if (colored) "on" else "off")
}
}

View file

@ -0,0 +1,49 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.shared.coroutine
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.ide.progress.TaskCancellation
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.util.SuspendingLazy
import com.intellij.util.application
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
inline fun <T> runModalOrBlocking(taskOwnerFactory: () -> ModalTaskOwner, titleFactory: () -> String, cancellationFactory: () -> TaskCancellation = TaskCancellation::cancellable, noinline action: suspend CoroutineScope.() -> T): T {
return if (application.isDispatchThread) {
runWithModalProgressBlocking(taskOwnerFactory(), titleFactory(), cancellationFactory(), action)
} else {
runBlocking(block = action)
}
}
inline fun <T> SuspendingLazy<T>.getOrAwaitModalOrBlocking(taskOwnerFactory: () -> ModalTaskOwner, titleFactory: () -> String, cancellationFactory: () -> TaskCancellation = TaskCancellation::cancellable): T {
if (isInitialized()) {
return getInitialized()
} else {
return runModalOrBlocking(taskOwnerFactory, titleFactory, cancellationFactory) {
getValue()
}
}
}

View file

@ -0,0 +1,98 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.shared.element
import org.jdom.Element
fun Element.readString(name: String): String? {
return children
.firstOrNull { it.name == "ZigBrainsOption" && it.getAttributeValue("name") == name }
?.getAttributeValue("value")
}
fun Element.readBoolean(name: String): Boolean? {
return readString(name)?.toBooleanStrictOrNull()
}
inline fun <reified T: Enum<T>> Element.readEnum(name: String): T? {
return readEnum(name, T::class.java)
}
fun <T: Enum<T>> Element.readEnum(name: String, klass: Class<T>): T? {
return readString(name)?.let { valueOf(it, klass) }
}
fun Element.readChild(name: String): Element? {
return children
.firstOrNull { it.name == "ZigBrainsNestedOption" && it.getAttributeValue("name") == name }
}
fun Element.readStrings(name: String): List<String>? {
return children
.firstOrNull { it.name == "ZigBrainsArrayOption" && it.getAttributeValue("name") == name }
?.children
?.mapNotNull { if (it.name == "ZigBrainsArrayEntry") it.getAttributeValue("value") else null }
}
fun Element.writeString(name: String, value: String) {
val option = Element("ZigBrainsOption")
option.setAttribute("name", name)
option.setAttribute("value", value)
addContent(option)
}
fun Element.writeBoolean(name: String, value: Boolean) {
writeString(name, value.toString())
}
fun <T: Enum<T>> Element.writeEnum(name: String, value: T) {
writeString(name, value.name)
}
fun Element.writeStrings(name: String, values: List<String>) {
val arr = Element("ZigBrainsArrayOption")
arr.setAttribute("name", name)
for (value in values) {
val subElem = Element("ZigBrainsArrayEntry")
subElem.setAttribute("value", value)
arr.addContent(subElem)
}
addContent(arr)
}
fun Element.writeChild(name: String): Element {
val child = Element("ZigBrainsNestedOption")
child.setAttribute("name", name)
addContent(child)
return child
}
private fun <T: Enum<T>> valueOf(type: String, klass: Class<T>): T? {
return try {
java.lang.Enum.valueOf(klass, type)
} catch (e: IllegalArgumentException) {
null
}
}

View file

@ -26,9 +26,7 @@ package com.falsepattern.zigbrains.zig.util
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
import org.jetbrains.annotations.NonNls
import java.util.regex.Pattern import java.util.regex.Pattern
import java.util.stream.Collectors
import kotlin.math.min import kotlin.math.min

View file

@ -1,4 +1,5 @@
<idea-plugin> <idea-plugin>
<resource-bundle>zigbrains.Bundle</resource-bundle>
<!-- region Zig --> <!-- region Zig -->
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<colorSettingsPage <colorSettingsPage
@ -90,6 +91,111 @@
</extensions> </extensions>
<!-- endregion Zon --> <!-- endregion Zon -->
<!-- region Project -->
<extensions defaultExtensionNs="com.intellij">
<configurationType
implementation="com.falsepattern.zigbrains.project.execution.run.ZigConfigTypeRun"
/>
<runConfigurationProducer
implementation="com.falsepattern.zigbrains.project.execution.run.ZigConfigProducerRun"
/>
<runLineMarkerContributor
language="Zig"
implementationClass="com.falsepattern.zigbrains.project.execution.run.ZigLineMarkerRun"
/>
<configurationType
implementation="com.falsepattern.zigbrains.project.execution.test.ZigConfigTypeTest"
/>
<runConfigurationProducer
implementation="com.falsepattern.zigbrains.project.execution.test.ZigConfigProducerTest"
/>
<runLineMarkerContributor
language="Zig"
implementationClass="com.falsepattern.zigbrains.project.execution.test.ZigLineMarkerTest"
/>
<configurationType
implementation="com.falsepattern.zigbrains.project.execution.build.ZigConfigTypeBuild"
/>
<runConfigurationProducer
implementation="com.falsepattern.zigbrains.project.execution.build.ZigConfigProducerBuild"
/>
<runLineMarkerContributor
language="Zig"
implementationClass="com.falsepattern.zigbrains.project.execution.build.ZigLineMarkerBuild"
/>
<directoryProjectGenerator
implementation="com.falsepattern.zigbrains.project.newproject.ZigDirectoryProjectGenerator"
/>
<newProjectWizard.languageGenerator
implementation="com.falsepattern.zigbrains.project.newproject.ZigNewProjectWizard"
/>
<moduleBuilder
builderClass="com.falsepattern.zigbrains.project.module.ZigModuleBuilder"
/>
<projectConfigurable
parentId="language"
instance="com.falsepattern.zigbrains.project.settings.ZigConfigurable"
id="ZigConfigurable"
displayName="Zig"
/>
<programRunner
implementation="com.falsepattern.zigbrains.project.run.ZigRegularRunner"
id="ZigRegularRunner"
/>
<consoleFilterProvider
implementation="com.falsepattern.zigbrains.project.console.ZigConsoleFilterProvider"
/>
<analyzeStacktraceFilter
implementation="com.falsepattern.zigbrains.project.console.ZigSourceFileFilter"
/>
<additionalLibraryRootsProvider
implementation="com.falsepattern.zigbrains.project.toolchain.stdlib.ZigLibraryRootProvider"
/>
<toolWindow
factoryClass="com.falsepattern.zigbrains.project.steps.ui.BuildToolWindowFactory"
anchor="right"
icon="/icons/zig_build_tool.svg"
id="zigbrains.build"
/>
</extensions>
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
<zlsConfigProvider
implementation="com.falsepattern.zigbrains.project.toolchain.ToolchainZLSConfigProvider"
/>
<toolchainProvider
implementation="com.falsepattern.zigbrains.project.toolchain.LocalZigToolchainProvider"
/>
</extensions>
<actions>
<action
id="zigbrains.discover.steps"
class="com.falsepattern.zigbrains.project.steps.discovery.ZigDiscoverStepsAction"
text="Reload Zig Build Steps"
description="Scan the project and detect build.zig steps using the current toolchain"
icon="AllIcons.Actions.Refresh"
/>
<action
id="zigbrains.file.new"
class="com.falsepattern.zigbrains.project.actions.ZigNewFileAction"
text="New Zig File"
>
<add-to-group group-id="NewGroup" anchor="before" relative-to-action="NewFile"/>
</action>
</actions>
<!-- endregion Project -->
<!-- region direnv --> <!-- region direnv -->
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<notificationGroup displayType="BALLOON" <notificationGroup displayType="BALLOON"

View file

@ -0,0 +1,5 @@
zig-cache/
zig-out/
build/
build-*/
docgen_tmp/

View file

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 154 140">
<g fill="#AFB1B3">
<g>
<polygon points="46,22 28,44 19,30"/>
<polygon points="46,22 33,33 28,44 22,44 22,95 31,95 20,100 12,117 0,117 0,22" shape-rendering="crispEdges"/>
<polygon points="31,95 12,117 4,106"/>
</g>
<g>
<polygon points="56,22 62,36 37,44"/>
<polygon points="56,22 111,22 111,44 37,44 56,32" shape-rendering="crispEdges"/>
<polygon points="116,95 97,117 90,104"/>
<polygon points="116,95 100,104 97,117 42,117 42,95" shape-rendering="crispEdges"/>
<polygon points="150,0 52,117 3,140 101,22"/>
</g>
<g>
<polygon points="141,22 140,40 122,45"/>
<polygon points="153,22 153,117 106,117 120,105 125,95 131,95 131,45 122,45 132,36 141,22" shape-rendering="crispEdges"/>
<polygon points="125,95 130,110 106,117"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 850 B

View file

@ -0,0 +1,66 @@
const std = @import("std");
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "@@PROJECT_NAME@@",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
b.installArtifact(exe);
// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe);
// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());
// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}
// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const exe_unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}

View file

@ -0,0 +1,24 @@
const std = @import("std");
pub fn main() !void {
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
// stdout is for the actual output of your application, for example if you
// are implementing gzip, then only the compressed bytes should be sent to
// stdout, not any debugging messages.
const stdout_file = std.io.getStdOut().writer();
var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer();
try stdout.print("Run `zig build test` to run the tests.\n", .{});
try bw.flush(); // don't forget to flush!
}
test "simple test" {
var list = std.ArrayList(i32).init(std.testing.allocator);
defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
try list.append(42);
try std.testing.expectEqual(@as(i32, 42), list.pop());
}

View file

@ -0,0 +1,67 @@
.{
.name = "@@PROJECT_NAME@@",
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",
// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
//.minimum_zig_version = "0.11.0",
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
// // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//},
},
// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package.
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
// This makes *all* files, recursively, included in this package. It is generally
// better to explicitly list the files and directories instead, to insure that
// fetching from tarballs, file system paths, and version control all result
// in the same contents hash.
"",
// For example...
//"build.zig",
//"build.zig.zon",
//"src",
//"LICENSE",
//"README.md",
},
}

View file

@ -0,0 +1,47 @@
const std = @import("std");
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
const lib = b.addStaticLibrary(.{
.name = "@@PROJECT_NAME@@",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
// This declares intent for the library to be installed into the standard
// location when the user invokes the "install" step (the default step when
// running `zig build`).
b.installArtifact(lib);
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const lib_unit_tests = b.addTest(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
}

View file

@ -0,0 +1,10 @@
const std = @import("std");
const testing = std.testing;
export fn add(a: i32, b: i32) i32 {
return a + b;
}
test "basic add functionality" {
try testing.expect(add(3, 7) == 10);
}

View file

@ -68,3 +68,47 @@ notification.title.native-debug=Zig native debugger
notification.content.native-debug=You need to install the "Native Debugging Support" plugin for Zig debugging in this IDE! notification.content.native-debug=You need to install the "Native Debugging Support" plugin for Zig debugging in this IDE!
notification.content.native-debug.market=Install from Marketplace notification.content.native-debug.market=Install from Marketplace
notification.content.native-debug.browser=Open in Browser notification.content.native-debug.browser=Open in Browser
dialog.title.working-directory=Select Working Directory
dialog.title.zig-toolchain=Path to the Zig Toolchain
dialog.title.zig-std=Path to the Standard Library
exec.option.label.working-directory=&Working directory:
exec.option.label.colored-terminal=Colored terminal
exec.option.label.direnv=Use direnv
exec.option.label.optimization=Optimization level
exec.option.label.optimization.force=Force even in debug runs
exec.option.label.emulate-terminal=Emulate terminal
exec.option.label.file-path=File Path
exec.option.label.compiler-args=Extra compiler command line arguments
exec.option.label.exe-args=Output program command line arguments
exec.option.label.build.steps=Build steps
exec.option.label.build.args=Extra command line arguments
exec.option.label.build.exe-args-debug=Output program command line arguments (debug only)
exec.option.label.build.exe-path-debug=Output executable created by the build (debug only, autodetect if empty)
exception.translate-command-line.unbalanced-quotes=Unbalanced quotes in {0}
exception.zig-profile-state.start-process.no-toolchain=Failed to get zig toolchain from project
exception.zig-build.debug.test-not-supported=Debugging "zig build test" is not yet supported
configuration.run.name=Zig run
configuration.run.suggested-name=Run
configuration.run.description=Execute "zig run" on a specific file
configuration.test.name=Zig test
configuration.test.suggested-name=Test
configuration.test.description=Execute "zig test" on a specific file
configuration.test.marker-name=all tests in {0}
configuration.build.name=Zig build
configuration.build.suggested-name=Build
configuration.build.description=Execute "zig build" with custom steps
configuration.build.marker-name=Build and Run
settings.project.group.title=Zig Settings
settings.project.label.direnv=Use direnv
settings.project.label.toolchain=Toolchain location
settings.project.label.toolchain-autodetect=Autodetect
settings.project.label.toolchain-version=Toolchain version
settings.project.label.override-std=Override standard library
settings.project.label.std-location=Standard library location
build.tool.window.tree.steps.label=Steps
build.tool.window.status.not-scanned=Build steps not yet scanned. Click the refresh button.
build.tool.window.status.loading=Running zig build -l
build.tool.window.status.error.missing-build-zig=No build.zig file found
build.tool.window.status.error.missing-toolchain=No zig toolchain configured
build.tool.window.status.error.general=Error while running zig build -l
build.tool.window.status.timeout=zig build -l timed out after {0} seconds.

View file

@ -3,7 +3,7 @@ plugins {
} }
rootProject.name = "ZigBrains" rootProject.name = "ZigBrains"
for (module in arrayOf("core", "lsp")) { for (module in arrayOf("core")) {
include(module) include(module)
project(":$module").projectDir = file("modules/$module") project(":$module").projectDir = file("modules/$module")
} }

View file

@ -12,13 +12,17 @@
<extensionPoint <extensionPoint
interface="com.falsepattern.zigbrains.lsp.config.ZLSConfigProviderBase" interface="com.falsepattern.zigbrains.lsp.config.ZLSConfigProviderBase"
dynamic="true" dynamic="true"
name="zlsConfigProvider"/> name="zlsConfigProvider"
</extensionPoints> />
<extensionPoints>
<extensionPoint <extensionPoint
interface="com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider" interface="com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider"
dynamic="true" dynamic="true"
name="toolchainProvider" name="toolchainProvider"
/> />
<extensionPoint
interface="com.falsepattern.zigbrains.shared.ZBFeatures"
dynamic="true"
name="featureProvider"
/>
</extensionPoints> </extensionPoints>
</idea-plugin> </idea-plugin>