diff --git a/.gitignore b/.gitignore index 40ea525d..b6f33eb8 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ gradle-app.setting .intellijPlatform jbr secrets +!modules/**/src/**/build/ \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index e2cdbc62..817e703e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension plugins { 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.changelog") version "2.2.1" id("org.jetbrains.grammarkit") version "2022.3.2.2" apply false @@ -81,7 +82,6 @@ dependencies { } implementation(project(":core")) - implementation(project(":lsp")) } intellijPlatform { diff --git a/modules/core/build.gradle.kts b/modules/core/build.gradle.kts index 7bb66cc4..c235dd3a 100644 --- a/modules/core/build.gradle.kts +++ b/modules/core/build.gradle.kts @@ -4,12 +4,21 @@ import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType plugins { 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 { intellijPlatform { 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 diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSBundle.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSBundle.kt similarity index 100% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSBundle.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSBundle.kt diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStreamConnectionProvider.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStreamConnectionProvider.kt similarity index 100% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStreamConnectionProvider.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStreamConnectionProvider.kt diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt similarity index 96% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt index 28d5b41d..0f639125 100644 --- a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt @@ -22,6 +22,7 @@ package com.falsepattern.zigbrains.lsp +import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key @@ -68,7 +69,7 @@ class ZigLanguageServerFactory: LanguageServerFactory, LanguageServerEnablementS class ZLSStarter: LanguageServerStarter { override fun startLSP(project: Project, restart: Boolean) { - project.service().cs.launch { + project.zigCoroutineScope.launch { val manager = project.service() val status = manager.getServerStatus("ZigBrains") if ((status == ServerStatus.started || status == ServerStatus.starting) && !restart) diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfig.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfig.kt similarity index 96% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfig.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfig.kt index c96590af..1768afd8 100644 --- a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfig.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfig.kt @@ -25,8 +25,10 @@ package com.falsepattern.zigbrains.lsp.config import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import org.jetbrains.annotations.NonNls +@Serializable data class ZLSConfig( @SerialName("zig_exe_path") val zigExePath: @NonNls String? = null, @SerialName("zig_lib_path") val zigLibPath: @NonNls String? = null, diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfigProviderBase.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfigProviderBase.kt similarity index 100% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfigProviderBase.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/config/ZLSConfigProviderBase.kt diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenColorsProvider.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenColorsProvider.kt similarity index 100% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenColorsProvider.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenColorsProvider.kt diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenModifiers.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenModifiers.kt similarity index 100% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenModifiers.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenModifiers.kt diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenTypes.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenTypes.kt similarity index 100% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenTypes.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/highlighting/ZLSSemanticTokenTypes.kt diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt similarity index 100% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt similarity index 68% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt index c0b2354e..dd22faa1 100644 --- a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt @@ -24,17 +24,16 @@ package com.falsepattern.zigbrains.lsp.settings import org.jetbrains.annotations.NonNls -@JvmRecord data class ZLSSettings( - val direnv: Boolean = true, - val zlsPath: @NonNls String = "", - val zlsConfigPath: @NonNls String = "", - val debug: Boolean = false, - val messageTrace: Boolean = false, - val buildOnSave: Boolean = false, - val buildOnSaveStep: @NonNls String = "install", - val globalVarDeclarations: Boolean = false, - val comptimeInterpreter: Boolean = false, - val inlayHints: Boolean = true, - val inlayHintsCompact: Boolean = true + var direnv: Boolean = true, + var zlsPath: @NonNls String = "", + var zlsConfigPath: @NonNls String = "", + var debug: Boolean = false, + var messageTrace: Boolean = false, + var buildOnSave: Boolean = false, + var buildOnSaveStep: @NonNls String = "install", + var globalVarDeclarations: Boolean = false, + var comptimeInterpreter: Boolean = false, + var inlayHints: Boolean = true, + var inlayHintsCompact: Boolean = true ) diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigProvider.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigProvider.kt similarity index 100% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigProvider.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigProvider.kt diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt similarity index 77% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt index b862dd9d..388fba1c 100644 --- a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt @@ -22,19 +22,16 @@ package com.falsepattern.zigbrains.lsp.settings -import com.falsepattern.zigbrains.lsp.ZLSBundle import com.falsepattern.zigbrains.lsp.startLSP -import com.falsepattern.zigbrains.shared.NestedConfigurable -import com.intellij.openapi.components.service +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 -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 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 { @@ -56,12 +53,7 @@ class ZLSSettingsConfigurable(private val project: Project): NestedConfigurable appSettingsComponent?.data = project.zlsSettings.state } - override fun disposeUIResources() { - appSettingsComponent?.dispose() + override fun dispose() { appSettingsComponent = null } - - override fun getDisplayName(): String { - return ZLSBundle.message("configurable.name.zls.settings") - } } \ No newline at end of file diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt similarity index 92% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt index 167e5105..1b9432f9 100644 --- a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt @@ -24,12 +24,12 @@ package com.falsepattern.zigbrains.lsp.settings import com.falsepattern.zigbrains.direnv.* import com.falsepattern.zigbrains.lsp.ZLSBundle -import com.falsepattern.zigbrains.lsp.ZigLSPApplicationService -import com.falsepattern.zigbrains.lsp.ZigLSPProjectService +import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.intellij.openapi.Disposable import com.intellij.openapi.components.service import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer import com.intellij.ui.components.JBCheckBox import com.intellij.ui.components.fields.ExtendableTextField import com.intellij.ui.components.textFieldWithBrowseButton @@ -44,12 +44,12 @@ class ZLSSettingsPanel(private val project: Project?) : Disposable { project, ZLSBundle.message("settings.zls-path.browse.title"), FileChooserDescriptorFactory.createSingleFileDescriptor(), - ) + ).also { Disposer.register(this, it) } private val zlsConfigPath = textFieldWithBrowseButton( project, ZLSBundle.message("settings.zls-config-path.browse.title"), FileChooserDescriptorFactory.createSingleFileDescriptor() - ) + ).also { Disposer.register(this, it) } 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") } @@ -69,7 +69,11 @@ class ZLSSettingsPanel(private val project: Project?) : Disposable { if (DirenvCmd.direnvInstalled() && project != null) { 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.inlay-hints.label")) { cell(inlayHints) } @@ -113,15 +117,11 @@ class ZLSSettingsPanel(private val project: Project?) : Disposable { inlayHintsCompact.isSelected = value.inlayHintsCompact } - fun autodetect() { - (project?.service()?.cs ?: application.service().cs).launch { - getDirenv().findExecutableOnPATH("zls")?.let { zlsPath.text = it.pathString } - } + suspend fun autodetect() { + getDirenv().findExecutableOnPATH("zls")?.let { zlsPath.text = it.pathString } } override fun dispose() { - zlsPath.dispose() - zlsConfigPath.dispose() } private suspend fun getDirenv(): Env { diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/actions/ZigNewFileAction.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/actions/ZigNewFileAction.kt new file mode 100644 index 00000000..27816e57 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/actions/ZigNewFileAction.kt @@ -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 . + */ + +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" + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/console/ZigConsoleFilterProvider.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/console/ZigConsoleFilterProvider.kt new file mode 100644 index 00000000..3c96bf42 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/console/ZigConsoleFilterProvider.kt @@ -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 . + */ + +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 { + return arrayOf(ZigSourceFileFilter(project)) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/console/ZigSourceFileFilter.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/console/ZigSourceFileFilter.kt new file mode 100644 index 00000000..6dc2ba04 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/console/ZigSourceFileFilter.kt @@ -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 . + */ + +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() + 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? { + 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+)") \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/Util.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/Util.kt new file mode 100644 index 00000000..90757e9a --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/Util.kt @@ -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 . + */ + +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 firstConfigFactory(): ConfigurationFactory { + return runConfigurationType().configurationFactories.first() +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/Configuration.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/Configuration.kt new file mode 100644 index 00000000..28dd47a0 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/Configuration.kt @@ -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 . + */ + +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>(private val state: ZigExecConfig) : SettingsEditor() { + private val configModules = ArrayList>() + + 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> : Serializable, Cloneable { + fun readExternal(element: Element) + fun writeExternal(element: Element) + fun createEditor(): ZigConfigModule + public override fun clone(): T + + interface ZigConfigModule> : 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> : ZigConfigurable { + 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> : ZigConfigModule { + 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(), Cloneable { + override fun createEditor(): ZigConfigModule { + return WorkDirectoryConfigModule(serializedName) + } + + override fun clone(): WorkDirectoryConfigurable { + return super.clone() as WorkDirectoryConfigurable + } + + class WorkDirectoryConfigModule(private val serializedName: String) : PathConfigModule() { + 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(), Cloneable { + + override fun createEditor(): ZigConfigModule { + return FilePathConfigModule(serializedName, guiLabel) + } + + override fun clone(): FilePathConfigurable { + return super.clone() as FilePathConfigurable + } + + class FilePathConfigModule(private val serializedName: String, @Nls private val label: String) : PathConfigModule() { + 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, Cloneable { + override fun readExternal(element: Element) { + value = element.readBoolean(serializedName) ?: return + } + + override fun writeExternal(element: Element) { + element.writeBoolean(serializedName, value) + } + + override fun createEditor(): ZigConfigModule { + return CheckboxConfigModule(serializedName, label) + } + + override fun clone(): CheckboxConfigurable { + return super.clone() as CheckboxConfigurable + } + + class CheckboxConfigModule( + private val serializedName: String, + @Nls private val label: String + ) : ZigConfigModule { + 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, Cloneable { + override fun readExternal(element: Element) { + element.readChild(serializedName)?.apply { + readEnum("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 { + return OptimizationConfigModule(serializedName) + } + + override fun clone(): OptimizationConfigurable { + return super.clone() as OptimizationConfigurable + } + + class OptimizationConfigModule(private val serializedName: String) : ZigConfigModule { + 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, Cloneable { + var args: List = emptyList() + + override fun readExternal(element: Element) { + args = element.readStrings(serializedName) ?: return + } + + override fun writeExternal(element: Element) { + element.writeStrings(serializedName, args) + } + + override fun createEditor(): ZigConfigModule { + return ArgsConfigModule(serializedName, guiName) + } + + override fun clone(): ArgsConfigurable { + return super.clone() as ArgsConfigurable + } + + class ArgsConfigModule( + private val serializedName: String, + @Nls private val guiName: String + ) : ZigConfigModule { + 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() { + + } + } +} \ No newline at end of file diff --git a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLSPServices.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/OptimizationLevel.kt similarity index 75% rename from modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLSPServices.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/OptimizationLevel.kt index db943a5c..c2868a93 100644 --- a/modules/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLSPServices.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/OptimizationLevel.kt @@ -20,17 +20,11 @@ * along with ZigBrains. If not, see . */ -package com.falsepattern.zigbrains.lsp +package com.falsepattern.zigbrains.project.execution.base -import com.intellij.openapi.components.Service -import kotlinx.coroutines.CoroutineScope - -@Service(Service.Level.PROJECT) -class ZigLSPProjectService( - val cs: CoroutineScope -) - -@Service -class ZigLSPApplicationService( - val cs: CoroutineScope -) \ No newline at end of file +enum class OptimizationLevel { + Debug, + ReleaseSafe, + ReleaseFast, + ReleaseSmall +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigConfigProducer.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigConfigProducer.kt new file mode 100644 index 00000000..5fd6c73e --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigConfigProducer.kt @@ -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 . + */ + +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>: LazyRunConfigurationProducer() { + abstract override fun getConfigurationFactory(): ConfigurationFactory + + override fun setupConfigurationFromContext(configuration: T, context: ConfigurationContext, sourceElement: Ref): 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 +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt index 3437b0a5..20ba08db 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt @@ -22,7 +22,68 @@ 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.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>: LocatableConfigurationBase { +abstract class ZigExecConfig>(project: Project, factory: ConfigurationFactory, name: String): LocatableConfigurationBase>(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 + abstract override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState + + override fun getConfigurationEditor(): SettingsEditor { + 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> = listOf(workingDirectory, pty, direnv) } \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt index d5e4daa2..aa679c67 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt @@ -22,12 +22,68 @@ 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.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.platform.ide.progress.ModalTaskOwner +import kotlin.io.path.pathString -abstract class ZigProfileState>( +abstract class ZigProfileState> ( environment: ExecutionEnvironment, - protected val configuration: T + val configuration: T ): 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 } \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigProducerBuild.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigProducerBuild.kt new file mode 100644 index 00000000..a2777ce9 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigProducerBuild.kt @@ -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 . + */ + +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() { + override fun getConfigurationFactory(): ConfigurationFactory { + return firstConfigFactory() + } + + 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() \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigTypeBuild.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigTypeBuild.kt new file mode 100644 index 00000000..3e01a0b6 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigTypeBuild.kt @@ -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 . + */ + +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" \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt new file mode 100644 index 00000000..cf86b9b3 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt @@ -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 . + */ + +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(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 { + val result = ArrayList() + result.add("build") + var steps = buildSteps.args + if (debug) { + val truncatedSteps = ArrayList() + 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> { + 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 { + return ZigProfileStateBuild(environment, this) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigLineMarkerBuild.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigLineMarkerBuild.kt new file mode 100644 index 00000000..2035b86a --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigLineMarkerBuild.kt @@ -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 . + */ + +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 + } +} diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigProfileStateBuild.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigProfileStateBuild.kt new file mode 100644 index 00000000..22772d9f --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigProfileStateBuild.kt @@ -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 . + */ + +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(environment, configuration) { +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigProducerRun.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigProducerRun.kt new file mode 100644 index 00000000..ab5b258b --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigProducerRun.kt @@ -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 . + */ + +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() { + override fun getConfigurationFactory(): ConfigurationFactory { + return firstConfigFactory() + } + + 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() \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigTypeRun.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigTypeRun.kt new file mode 100644 index 00000000..4bc1d6c6 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigTypeRun.kt @@ -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 . + */ + +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" \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt new file mode 100644 index 00000000..c01bbf6e --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt @@ -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 . + */ + +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(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 { + val result = ArrayList() + 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> { + return super.getConfigurables() + listOf(filePath, optimization, colored, compilerArgs, exeArgs) + } + + override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState { + return ZigProfileStateRun(environment, this) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigLineMarkerRun.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigLineMarkerRun.kt new file mode 100644 index 00000000..6cbffa1f --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigLineMarkerRun.kt @@ -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 . + */ + +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 + } +} diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigProfileStateRun.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigProfileStateRun.kt new file mode 100644 index 00000000..664773d8 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigProfileStateRun.kt @@ -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 . + */ + +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(environment, configuration) { +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigProducerTest.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigProducerTest.kt new file mode 100644 index 00000000..946e0e9d --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigProducerTest.kt @@ -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 . + */ + +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() { + override fun getConfigurationFactory(): ConfigurationFactory { + return firstConfigFactory() + } + + 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() \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigTypeTest.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigTypeTest.kt new file mode 100644 index 00000000..49d7684e --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigTypeTest.kt @@ -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 . + */ + +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" \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt new file mode 100644 index 00000000..576e1bf8 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt @@ -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 . + */ + +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(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 { + val result = ArrayList() + 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> { + return super.getConfigurables() + listOf(filePath, optimization, colored, compilerArgs) + } + + override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState { + return ZigProfileStateTest(environment, this) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigLineMarkerTest.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigLineMarkerTest.kt new file mode 100644 index 00000000..2118b5a8 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigLineMarkerTest.kt @@ -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 . + */ + +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 + } +} diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigProfileStateTest.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigProfileStateTest.kt new file mode 100644 index 00000000..12248947 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigProfileStateTest.kt @@ -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 . + */ + +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(environment, configuration) { +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleBuilder.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleBuilder.kt new file mode 100644 index 00000000..b1376cef --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleBuilder.kt @@ -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 . + */ + +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.withBorder(): T { + border = JBUI.Borders.empty(14, 20) + return this +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleType.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleType.kt new file mode 100644 index 00000000..7710e08d --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleType.kt @@ -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 . + */ + +package com.falsepattern.zigbrains.project.module + +import com.falsepattern.zigbrains.Icons +import com.intellij.openapi.module.ModuleType +import javax.swing.Icon + +object ZigModuleType: ModuleType("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 + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigDirectoryProjectGenerator.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigDirectoryProjectGenerator.kt new file mode 100644 index 00000000..e1c17f24 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigDirectoryProjectGenerator.kt @@ -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 . + */ + +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, CustomStepProjectGenerator { + override fun getName(): String { + return "Zig" + } + + override fun getLogo(): Icon { + return Icons.ZIG + } + + override fun createPeer(): ProjectGeneratorPeer { + 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?, + callback: AbstractNewProjectStep.AbstractCallback? + ): AbstractActionWithPanel { + return ZigProjectSettingsStep(projectGenerator!!, callback) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigNewProjectPanel.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigNewProjectPanel.kt new file mode 100644 index 00000000..35120f3a --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigNewProjectPanel.kt @@ -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 . + */ + +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() { + override fun customizeCellRenderer(list: JList, 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() +) \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigNewProjectWizard.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigNewProjectWizard.kt new file mode 100644 index 00000000..52519c67 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigNewProjectWizard.kt @@ -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 . + */ + +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) + } + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt new file mode 100644 index 00000000..27fa1f2b --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt @@ -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 . + */ + +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() +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectGeneratorPeer.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectGeneratorPeer.kt new file mode 100644 index 00000000..36873e2a --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectGeneratorPeer.kt @@ -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 . + */ + +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, 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() { + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectSettingsStep.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectSettingsStep.kt new file mode 100644 index 00000000..2f5df17a --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectSettingsStep.kt @@ -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 . + */ + +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, + callback: AbstractNewProjectStep.AbstractCallback? +): ProjectSettingsStepBase(projectGenerator, callback) \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/ZigProcessHandler.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProcessHandler.kt similarity index 51% rename from modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/ZigProcessHandler.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProcessHandler.kt index 18953a8c..7da57a32 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/ZigProcessHandler.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProcessHandler.kt @@ -20,9 +20,8 @@ * along with ZigBrains. If not, see . */ -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.PtyCommandLine import com.intellij.execution.process.AnsiEscapeDecoder.ColoredTextAcceptor @@ -47,53 +46,51 @@ class ZigProcessHandler : KillableColoredProcessHandler, ColoredTextAcceptor { } } -object VT100Util { - private val VT100_CHARS = CharArray(256).apply { - this.fill(' ') - this[0x6A] = '┘'; - this[0x6B] = '┐'; - this[0x6C] = '┌'; - this[0x6D] = '└'; - this[0x6E] = '┼'; - this[0x71] = '─'; - this[0x74] = '├'; - this[0x75] = '┤'; - this[0x76] = '┴'; - this[0x77] = '┬'; - this[0x78] = '│'; - } +private val VT100_CHARS = CharArray(256).apply { + this.fill(' ') + this[0x6A] = '┘'; + this[0x6B] = '┐'; + this[0x6C] = '┌'; + this[0x6D] = '└'; + this[0x6E] = '┼'; + this[0x71] = '─'; + this[0x74] = '├'; + this[0x75] = '┤'; + this[0x76] = '┴'; + this[0x77] = '┬'; + this[0x78] = '│'; +} - private const val VT100_BEGIN_SEQ = "\u001B(0" - private const val VT100_END_SEQ = "\u001B(B" - 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_BEGIN_SEQ = "\u001B(0" +private const val VT100_END_SEQ = "\u001B(B" +private const val VT100_BEGIN_SEQ_LENGTH: Int = VT100_BEGIN_SEQ.length +private const val VT100_END_SEQ_LENGTH: Int = VT100_END_SEQ.length - fun String.translateVT100Escapes(): String { - var offset = 0 - val result = StringBuilder() - val textLength = length - while (offset < textLength) { - val startIndex = indexOf(VT100_BEGIN_SEQ, offset) - if (startIndex < 0) { - result.append(substring(offset, textLength).replace(VT100_END_SEQ, "")) - break - } - result.append(this, offset, startIndex) - val blockOffset = startIndex + VT100_BEGIN_SEQ_LENGTH - var endIndex = indexOf(VT100_END_SEQ, blockOffset) - if (endIndex < 0) { - endIndex = textLength - } - for (i in blockOffset until endIndex) { - val c = this[i].code - if (c >= 256) { - result.append(c) - } else { - result.append(VT100_CHARS[c]) - } - } - offset = endIndex + VT100_END_SEQ_LENGTH +private fun String.translateVT100Escapes(): String { + var offset = 0 + val result = StringBuilder() + val textLength = length + while (offset < textLength) { + val startIndex = indexOf(VT100_BEGIN_SEQ, offset) + if (startIndex < 0) { + result.append(substring(offset, textLength).replace(VT100_END_SEQ, "")) + break } - return result.toString() + result.append(this, offset, startIndex) + val blockOffset = startIndex + VT100_BEGIN_SEQ_LENGTH + var endIndex = indexOf(VT100_END_SEQ, blockOffset) + if (endIndex < 0) { + endIndex = textLength + } + for (i in blockOffset until endIndex) { + val c = this[i].code + if (c >= 256) { + result.append(c) + } else { + result.append(VT100_CHARS[c]) + } + } + offset = endIndex + VT100_END_SEQ_LENGTH } -} \ No newline at end of file + return result.toString() +} diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProgramRunner.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProgramRunner.kt index f8207cce..737358f7 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProgramRunner.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProgramRunner.kt @@ -23,22 +23,27 @@ package com.falsepattern.zigbrains.project.run import com.falsepattern.zigbrains.project.execution.base.ZigProfileState -import com.falsepattern.zigbrains.project.zigService -import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain +import com.falsepattern.zigbrains.project.settings.zigProjectSettings +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.configurations.RunnerSettings import com.intellij.execution.runners.AsyncProgramRunner import com.intellij.execution.runners.ExecutionEnvironment 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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async +import kotlinx.coroutines.withContext import org.jetbrains.concurrency.Promise abstract class ZigProgramRunner>(protected val executorId: String): AsyncProgramRunner() { @OptIn(ExperimentalCoroutinesApi::class) override fun execute(environment: ExecutionEnvironment, state: RunProfileState): Promise { - return environment.project.zigService.cs.async { + return environment.project.zigCoroutineScope.async { executeAsync(environment, state) }.toPromise() } @@ -49,10 +54,16 @@ abstract class ZigProgramRunner>(protected val 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? - abstract suspend fun execute(state: ProfileState, toolchain: ZigToolchain, environment: ExecutionEnvironment) + abstract suspend fun execute(state: ProfileState, toolchain: AbstractZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor? } \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt new file mode 100644 index 00000000..f2e690bc --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt @@ -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 . + */ + +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>(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" + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigConfigurable.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigConfigurable.kt new file mode 100644 index 00000000..be1bf725 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigConfigurable.kt @@ -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 . + */ + +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" + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectConfigurable.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectConfigurable.kt new file mode 100644 index 00000000..91672806 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectConfigurable.kt @@ -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 . + */ + +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 + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettings.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettings.kt index 31bb8790..6c928d85 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettings.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettings.kt @@ -22,14 +22,22 @@ package com.falsepattern.zigbrains.project.settings -import com.falsepattern.zigbrains.project.toolchain.ZigToolchainConverter -import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain -import com.intellij.util.xmlb.annotations.OptionTag +import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain +import com.intellij.openapi.util.io.toNioPathOrNull +import com.intellij.util.xmlb.annotations.Transient +import kotlin.io.path.pathString -@JvmRecord data class ZigProjectSettings( - val direnv: Boolean = true, - val overrideStdPath: Boolean = false, - val explicitPathToStd: String? = null, - @OptionTag(converter = ZigToolchainConverter::class) val toolchain: ZigToolchain? = null -) + var direnv: Boolean = true, + var overrideStdPath: Boolean = false, + var explicitPathToStd: String? = null, + var toolchainPath: String? = null +) { + @get:Transient + @set:Transient + var toolchain: LocalZigToolchain? + get() = toolchainPath?.toNioPathOrNull()?.let { LocalZigToolchain(it) } + set(value) { + toolchainPath = value?.location?.pathString + } +} diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt new file mode 100644 index 00000000..717d04ad --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt @@ -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 . + */ + +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") + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigDiscoverStepsAction.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigDiscoverStepsAction.kt new file mode 100644 index 00000000..01ad8359 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigDiscoverStepsAction.kt @@ -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 . + */ + +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() + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryListener.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryListener.kt new file mode 100644 index 00000000..e01e8511 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryListener.kt @@ -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 . + */ + +package com.falsepattern.zigbrains.project.steps.discovery + +interface ZigStepDiscoveryListener { + suspend fun preReload() {} + suspend fun postReload(steps: List>) {} + suspend fun errorReload(type: ErrorType, details: String?) {} + suspend fun timeoutReload(seconds: Int) {} + + companion object { + } + + enum class ErrorType { + MissingToolchain, + MissingBuildZig, + GeneralError + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt new file mode 100644 index 00000000..59b8179a --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt @@ -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 . + */ + +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() + 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>() + 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>) { + 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() + +private val SPACES = Regex("\\s+") + +private const val DEFAULT_TIMEOUT_SEC = 10 + +private val LOG = Logger.getInstance(ZigStepDiscoveryService::class.java) \ No newline at end of file diff --git a/modules/lsp/build.gradle.kts b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BaseNodeDescriptor.kt similarity index 51% rename from modules/lsp/build.gradle.kts rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BaseNodeDescriptor.kt index 66d8570d..f049f74a 100644 --- a/modules/lsp/build.gradle.kts +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BaseNodeDescriptor.kt @@ -20,23 +20,28 @@ * along with ZigBrains. If not, see . */ -import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +package com.falsepattern.zigbrains.project.steps.ui -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" -tasks.withType { - kotlinOptions { - freeCompilerArgs += "-Xcontext-receivers" +import com.intellij.ide.projectView.PresentationData +import com.intellij.ide.util.treeView.PresentableNodeDescriptor +import com.intellij.openapi.project.Project +import com.intellij.ui.SimpleTextAttributes +import javax.swing.Icon + +open class BaseNodeDescriptor(project: Project?, displayName: String, displayIcon: Icon, private var description: String? = null): PresentableNodeDescriptor(project, null) { + init { + icon = displayIcon + myName = displayName + update() } -} -dependencies { - intellijPlatform { - create(IntelliJPlatformType.IntellijIdeaCommunity, providers.gradleProperty("ideaCommunityVersion")) + + override fun update(presentation: PresentationData) { + presentation.setIcon(icon) + presentation.addText(myName, SimpleTextAttributes.REGULAR_ATTRIBUTES) + presentation.tooltip = description } - intellijPlatformPluginDependency(lsp4ijDepString) - compileOnly("org.eclipse.lsp4j:org.eclipse.lsp4j:$lsp4jVersion") - implementation(project(":core")) -} + + override fun getElement(): T? { + return null + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt new file mode 100644 index 00000000..6424712e --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt @@ -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 . + */ + +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(project, project.name, AllIcons.Actions.ProjectDirectory)) + private val buildZig: DefaultMutableTreeNode = DefaultMutableTreeNode(BaseNodeDescriptor(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() + 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>) { + 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("MODEL") diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowFactory.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowFactory.kt new file mode 100644 index 00000000..657f90a9 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowFactory.kt @@ -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 . + */ + +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) + } + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/StepDetails.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/StepDetails.kt new file mode 100644 index 00000000..6893fdec --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/StepDetails.kt @@ -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 . + */ + +package com.falsepattern.zigbrains.project.steps.ui + +@JvmRecord +data class StepDetails(val name: String) diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/StepNodeDescriptor.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/StepNodeDescriptor.kt new file mode 100644 index 00000000..642ddb9a --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/StepNodeDescriptor.kt @@ -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 . + */ + +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(project, stepName, displayIcon, description) { + override fun getElement(): StepDetails? { + return StepDetails(stepName) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigExecutableTemplate.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigExecutableTemplate.kt new file mode 100644 index 00000000..c0dbb6c7 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigExecutableTemplate.kt @@ -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 . + */ + +package com.falsepattern.zigbrains.project.template + +class ZigExecutableTemplate: ZigProjectTemplate("Executable (application)", true) { + override fun fileTemplates(): Map { + return mapOf( + "src/main.zig" to "application", + "build.zig" to "application", + "build.zig.zon" to "shared" + ) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigInitTemplate.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigInitTemplate.kt new file mode 100644 index 00000000..8eb299a7 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigInitTemplate.kt @@ -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 . + */ + +package com.falsepattern.zigbrains.project.template + +class ZigInitTemplate: ZigProjectTemplate("Generate using \"zig init\"", true) { + override fun fileTemplates(): Map { + throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigLibraryTemplate.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigLibraryTemplate.kt new file mode 100644 index 00000000..8141f199 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigLibraryTemplate.kt @@ -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 . + */ + +package com.falsepattern.zigbrains.project.template + +class ZigLibraryTemplate: ZigProjectTemplate("Library (static)", true) { + override fun fileTemplates(): Map { + return mapOf( + "src/root.zig" to "static", + "build.zig" to "static", + "build.zig.zon" to "shared" + ) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigProjectTemplate.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigProjectTemplate.kt new file mode 100644 index 00000000..c7b56bb6 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/template/ZigProjectTemplate.kt @@ -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 . + */ + +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 +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/AbstractZigToolchain.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/AbstractZigToolchain.kt index fb8a7088..65228329 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/AbstractZigToolchain.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/AbstractZigToolchain.kt @@ -22,12 +22,18 @@ 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.util.Key import java.nio.file.Path -abstract class AbstractZigToolchain( - val location: Path, - val project: Project? -) { - abstract fun pathToExecutable(toolName: String): Path +abstract class AbstractZigToolchain { + val zig: ZigCompilerTool by lazy { ZigCompilerTool(this) } + + abstract fun workingDirectory(project: Project? = null): Path? + + abstract suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project? = null): GeneralCommandLine + + abstract fun pathToExecutable(toolName: String, project: Project? = null): Path } \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt new file mode 100644 index 00000000..111876ec --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt @@ -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 . + */ + +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("ZIG_LOCAL_DIRENV") + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainProvider.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainProvider.kt new file mode 100644 index 00000000..962cb4c1 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainProvider.kt @@ -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 . + */ + +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 { + 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) + } + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ToolchainZLSConfigProvider.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ToolchainZLSConfigProvider.kt new file mode 100644 index 00000000..00c99533 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ToolchainZLSConfigProvider.kt @@ -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 . + */ + +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) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainEnvironmentSerializable.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainEnvironmentSerializable.kt index e8b3c719..aba69cbb 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainEnvironmentSerializable.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainEnvironmentSerializable.kt @@ -23,10 +23,12 @@ package com.falsepattern.zigbrains.project.toolchain import com.intellij.openapi.util.io.toNioPathOrNull import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import java.nio.file.Path @JvmRecord +@Serializable data class ZigToolchainEnvironmentSerializable( @SerialName("zig_exe") val zigExecutable: String, @SerialName("std_dir") val stdDirectory: String, @@ -35,7 +37,7 @@ data class ZigToolchainEnvironmentSerializable( @SerialName("version") val version: String, @SerialName("target") val target: String ) { - fun stdPath(toolchain: AbstractZigToolchain): Path? { + fun stdPath(toolchain: LocalZigToolchain): Path? { val path = stdDirectory.toNioPathOrNull() ?: return null if (path.isAbsolute) return path diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainProvider.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainProvider.kt index 16ca0c46..8151d1df 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainProvider.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainProvider.kt @@ -23,31 +23,34 @@ package com.falsepattern.zigbrains.project.toolchain 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.project.Project +import com.intellij.openapi.util.UserDataHolder import com.intellij.util.xmlb.Converter import kotlinx.serialization.json.* -sealed interface ZigToolchainProvider { - suspend fun getToolchain(project: Project?): ZigToolchain? +sealed interface ZigToolchainProvider { + suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? val serialMarker: String - fun deserialize(data: JsonElement): ZigToolchain? - fun canSerialize(toolchain: ZigToolchain): Boolean - fun serialize(toolchain: ZigToolchain): JsonElement + fun deserialize(data: JsonElement): AbstractZigToolchain? + fun canSerialize(toolchain: AbstractZigToolchain): Boolean + fun serialize(toolchain: T): JsonElement companion object { - val EXTENSION_POINT_NAME = ExtensionPointName.create("com.falsepattern.zigbrains.toolchainProvider") + val EXTENSION_POINT_NAME = ExtensionPointName.create>("com.falsepattern.zigbrains.toolchainProvider") - suspend fun findToolchains(project: Project?): ZigToolchain? { - return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.getToolchain(project) } + suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? { + return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(project, extraData) } } } } -class ZigToolchainConverter: Converter() { - override fun fromString(value: String): ZigToolchain? { +@Suppress("UNCHECKED_CAST") +private fun ZigToolchainProvider.serialize(toolchain: AbstractZigToolchain) = serialize(toolchain as T) + +class ZigToolchainConverter: Converter() { + override fun fromString(value: String): AbstractZigToolchain? { val json = Json.parseToJsonElement(value) as? JsonObject ?: return null val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null val data = json["data"] ?: return null @@ -55,7 +58,7 @@ class ZigToolchainConverter: Converter() { 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 return buildJsonObject { put("marker", provider.serialMarker) diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigLibraryRootProvider.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigLibraryRootProvider.kt new file mode 100644 index 00000000..611db01d --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigLibraryRootProvider.kt @@ -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 . + */ + +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 { + return setOf(ZigSyntheticLibrary(project)) + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigSyntheticLibrary.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigSyntheticLibrary.kt new file mode 100644 index 00000000..0221da44 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigSyntheticLibrary.kt @@ -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 . + */ + +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 { + 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 { + 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() +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigCompilerTool.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigCompilerTool.kt index c4441be1..2b1342ad 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigCompilerTool.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigCompilerTool.kt @@ -24,13 +24,23 @@ package com.falsepattern.zigbrains.project.toolchain.tools import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSerializable +import com.intellij.openapi.project.Project 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 get() = "zig" - suspend fun getEnv(): ZigToolchainEnvironmentSerializable { - return Json.decodeFromString(callWithArgs(toolchain.location, "env").stdout) + fun path(): Path { + return toolchain.pathToExecutable(toolName) } + + suspend fun getEnv(project: Project?): ZigToolchainEnvironmentSerializable { + return envJson.decodeFromString(callWithArgs(toolchain.workingDirectory(project), "env").stdout) + } +} + +private val envJson = Json { + ignoreUnknownKeys = true } \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt index 60512f49..6271f29a 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt @@ -26,35 +26,37 @@ import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.process.ProcessOutput import com.intellij.util.io.awaitExit -import com.intellij.util.io.readLineAsync import kotlinx.coroutines.runInterruptible -import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull import java.nio.file.Path -import java.time.Duration abstract class ZigTool(val toolchain: AbstractZigToolchain) { 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 exitCode = process.awaitExit() + + val exitCode = withTimeoutOrNull(timeoutMillis) { + process.awaitExit() + } return runInterruptible { ProcessOutput( process.inputStream.bufferedReader().use { it.readText() }, process.errorStream.bufferedReader().use { it.readText() }, - exitCode, - false, + exitCode ?: -1, + exitCode == null, false ) } } - protected suspend fun createBaseCommandLine(workingDirectory: Path?, - vararg parameters: String): GeneralCommandLine { - return GeneralCommandLine() + private suspend fun createBaseCommandLine(workingDirectory: Path?, + vararg parameters: String): GeneralCommandLine { + val cli = GeneralCommandLine() .withExePath(toolchain.pathToExecutable(toolName).toString()) .withWorkDirectory(workingDirectory?.toString()) .withParameters(*parameters) .withCharset(Charsets.UTF_8) + return toolchain.patchCommandLine(cli) } } \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/MultiConfigurable.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/MultiConfigurable.kt new file mode 100644 index 00000000..4ffa4619 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/MultiConfigurable.kt @@ -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 . + */ + +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) } + } +} diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/NestedConfigurable.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/SubConfigurable.kt similarity index 79% rename from modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/NestedConfigurable.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/SubConfigurable.kt index f3742ab5..adf02965 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/NestedConfigurable.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/SubConfigurable.kt @@ -22,15 +22,14 @@ 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 javax.swing.JComponent - -interface NestedConfigurable: Configurable { - override fun createComponent() = panel { - createComponent(this) - } +interface SubConfigurable: Disposable { fun createComponent(panel: Panel) + fun isModified(): Boolean + @Throws(ConfigurationException::class) + fun apply() + fun reset() } \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/ZBFeatures.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/ZBFeatures.kt new file mode 100644 index 00000000..f94c4a73 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/ZBFeatures.kt @@ -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 . + */ + +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("com.falsepattern.zigbrains.featureProvider") + + fun debug(): Boolean { + val extensions = EXTENSION_POINT_NAME.extensionList + return extensions.any { it.getDebug() } + } + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/ZigProjectService.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/ZigService.kt similarity index 74% rename from modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/ZigProjectService.kt rename to modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/ZigService.kt index 63aaa57d..ba6f1f8b 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/ZigProjectService.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/ZigService.kt @@ -20,14 +20,24 @@ * along with ZigBrains. If not, see . */ -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.project.Project +import com.intellij.util.application import kotlinx.coroutines.CoroutineScope @Service(Service.Level.PROJECT) class ZigProjectService(val cs: CoroutineScope) -val Project.zigService get() = service() \ No newline at end of file +@Service +class ZigAppService(val cs: CoroutineScope) + +val Project?.zigCoroutineScope get() = + if (this == null || this.isDefault) + application.service().cs + else + this.service().cs + +val zigCoroutineScope get() = application.service().cs \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt new file mode 100644 index 00000000..d582f5f9 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt @@ -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 . + */ + +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 { + 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() + 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 { + return if (debug) { + emptyList() + } else { + listOf("--color", if (colored) "on" else "off") + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/coroutine/CoroutinesUtil.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/coroutine/CoroutinesUtil.kt new file mode 100644 index 00000000..618637c7 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/coroutine/CoroutinesUtil.kt @@ -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 . + */ + +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 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 SuspendingLazy.getOrAwaitModalOrBlocking(taskOwnerFactory: () -> ModalTaskOwner, titleFactory: () -> String, cancellationFactory: () -> TaskCancellation = TaskCancellation::cancellable): T { + if (isInitialized()) { + return getInitialized() + } else { + return runModalOrBlocking(taskOwnerFactory, titleFactory, cancellationFactory) { + getValue() + } + } +} \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/element/ElementUtil.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/element/ElementUtil.kt new file mode 100644 index 00000000..6ef187c5 --- /dev/null +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/element/ElementUtil.kt @@ -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 . + */ + +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 > Element.readEnum(name: String): T? { + return readEnum(name, T::class.java) +} + +fun > Element.readEnum(name: String, klass: Class): 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? { + 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 > Element.writeEnum(name: String, value: T) { + writeString(name, value.name) +} + +fun Element.writeStrings(name: String, values: List) { + 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 > valueOf(type: String, klass: Class): T? { + return try { + java.lang.Enum.valueOf(klass, type) + } catch (e: IllegalArgumentException) { + null + } +} diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/util/ZigStringUtil.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/util/ZigStringUtil.kt index b0e45757..9979d5a5 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/util/ZigStringUtil.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/zig/util/ZigStringUtil.kt @@ -26,9 +26,7 @@ package com.falsepattern.zigbrains.zig.util import com.intellij.openapi.util.TextRange import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap -import org.jetbrains.annotations.NonNls import java.util.regex.Pattern -import java.util.stream.Collectors import kotlin.math.min diff --git a/modules/core/src/main/resources/META-INF/zigbrains-core.xml b/modules/core/src/main/resources/META-INF/zigbrains-core.xml index 334949cf..5992bfbf 100644 --- a/modules/core/src/main/resources/META-INF/zigbrains-core.xml +++ b/modules/core/src/main/resources/META-INF/zigbrains-core.xml @@ -1,4 +1,5 @@ + zigbrains.Bundle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/core/src/main/resources/project-gen/application/build.zig.template b/modules/core/src/main/resources/project-gen/application/build.zig.template new file mode 100644 index 00000000..6f7fdcfb --- /dev/null +++ b/modules/core/src/main/resources/project-gen/application/build.zig.template @@ -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); +} diff --git a/modules/core/src/main/resources/project-gen/application/main.zig.template b/modules/core/src/main/resources/project-gen/application/main.zig.template new file mode 100644 index 00000000..c8a3f67d --- /dev/null +++ b/modules/core/src/main/resources/project-gen/application/main.zig.template @@ -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()); +} diff --git a/modules/core/src/main/resources/project-gen/shared/build.zig.zon.template b/modules/core/src/main/resources/project-gen/shared/build.zig.zon.template new file mode 100644 index 00000000..03ff25ff --- /dev/null +++ b/modules/core/src/main/resources/project-gen/shared/build.zig.zon.template @@ -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 ` 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", + }, +} diff --git a/modules/core/src/main/resources/project-gen/static/build.zig.template b/modules/core/src/main/resources/project-gen/static/build.zig.template new file mode 100644 index 00000000..5f1ac65a --- /dev/null +++ b/modules/core/src/main/resources/project-gen/static/build.zig.template @@ -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); +} diff --git a/modules/core/src/main/resources/project-gen/static/root.zig.template b/modules/core/src/main/resources/project-gen/static/root.zig.template new file mode 100644 index 00000000..ecfeade1 --- /dev/null +++ b/modules/core/src/main/resources/project-gen/static/root.zig.template @@ -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); +} diff --git a/modules/core/src/main/resources/zigbrains/Bundle.properties b/modules/core/src/main/resources/zigbrains/Bundle.properties index ad12cec2..f8809b62 100644 --- a/modules/core/src/main/resources/zigbrains/Bundle.properties +++ b/modules/core/src/main/resources/zigbrains/Bundle.properties @@ -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.market=Install from Marketplace 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. \ No newline at end of file diff --git a/modules/lsp/src/main/resources/zigbrains/lsp/Bundle.properties b/modules/core/src/main/resources/zigbrains/lsp/Bundle.properties similarity index 100% rename from modules/lsp/src/main/resources/zigbrains/lsp/Bundle.properties rename to modules/core/src/main/resources/zigbrains/lsp/Bundle.properties diff --git a/settings.gradle.kts b/settings.gradle.kts index 8cc027ef..0d1e0c3c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,7 +3,7 @@ plugins { } rootProject.name = "ZigBrains" -for (module in arrayOf("core", "lsp")) { +for (module in arrayOf("core")) { include(module) project(":$module").projectDir = file("modules/$module") } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f30702ba..f9140bda 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -12,13 +12,17 @@ - - + name="zlsConfigProvider" + /> + \ No newline at end of file