WIP toolchains

This commit is contained in:
FalsePattern 2024-11-01 22:05:17 +01:00
parent d02f3859a1
commit 4a132be408
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
15 changed files with 657 additions and 0 deletions

View file

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

View file

@ -0,0 +1,99 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution
import com.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
import com.intellij.execution.process.KillableColoredProcessHandler
import com.intellij.openapi.util.Key
import com.pty4j.PtyProcess
import java.nio.charset.Charset
class ZigProcessHandler : KillableColoredProcessHandler, ColoredTextAcceptor {
constructor(commandLine: GeneralCommandLine) : super(commandLine) {
setHasPty(commandLine is PtyCommandLine)
setShouldDestroyProcessRecursively(!hasPty())
}
constructor (process: Process, commandLine: String, charset: Charset) : super(process, commandLine, charset) {
setHasPty(process is PtyProcess)
setShouldDestroyProcessRecursively(!hasPty())
}
override fun coloredTextAvailable(text: String, attributes: Key<*>) {
super.coloredTextAvailable(text.translateVT100Escapes(), attributes)
}
}
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 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
}
return result.toString()
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,58 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.run
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.falsepattern.zigbrains.project.zigService
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
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.rd.util.toPromise
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import org.jetbrains.concurrency.Promise
abstract class ZigProgramRunner<ProfileState: ZigProfileState<*>>(protected val executorId: String): AsyncProgramRunner<RunnerSettings>() {
@OptIn(ExperimentalCoroutinesApi::class)
override fun execute(environment: ExecutionEnvironment, state: RunProfileState): Promise<RunContentDescriptor?> {
return environment.project.zigService.cs.async {
executeAsync(environment, state)
}.toPromise()
}
private suspend inline fun executeAsync(environment: ExecutionEnvironment, state: RunProfileState): RunContentDescriptor? {
if (state !is ZigProfileState<*>)
return null
val state = castProfileState(state) ?: return null
execute(state, null, environment)
}
protected abstract fun castProfileState(state: ZigProfileState<*>): ProfileState?
abstract suspend fun execute(state: ProfileState, toolchain: ZigToolchain, environment: ExecutionEnvironment)
}

View file

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

View file

@ -0,0 +1,54 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.settings
import com.intellij.openapi.components.*
import com.intellij.openapi.project.Project
@Service(Service.Level.PROJECT)
@State(
name = "ZigProjectSettings",
storages = [Storage("zigbrains.xml")]
)
class ZigProjectSettingsService: PersistentStateComponent<ZigProjectSettings> {
@Volatile
private var state = ZigProjectSettings()
override fun getState(): ZigProjectSettings {
return state.copy()
}
fun setState(value: ZigProjectSettings) {
this.state = value
}
override fun loadState(state: ZigProjectSettings) {
this.state = state
}
fun isModified(otherData: ZigProjectSettings): Boolean {
return state != otherData
}
}
val Project.zigProjectSettings get() = service<ZigProjectSettingsService>()

View file

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

View file

@ -0,0 +1,49 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain
import com.intellij.openapi.util.io.toNioPathOrNull
import kotlinx.serialization.SerialName
import java.nio.file.Path
@JvmRecord
data class ZigToolchainEnvironmentSerializable(
@SerialName("zig_exe") val zigExecutable: String,
@SerialName("std_dir") val stdDirectory: String,
@SerialName("global_cache_dir") val globalCacheDirectory: String,
@SerialName("lib_dir") val libDirectory: String,
@SerialName("version") val version: String,
@SerialName("target") val target: String
) {
fun stdPath(toolchain: AbstractZigToolchain): Path? {
val path = stdDirectory.toNioPathOrNull() ?: return null
if (path.isAbsolute)
return path
val resolvedPath = toolchain.location.resolve(path)
if (resolvedPath.isAbsolute)
return resolvedPath
return null
}
}

View file

@ -0,0 +1,66 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain
import com.falsepattern.zigbrains.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.util.xmlb.Converter
import kotlinx.serialization.json.*
sealed interface ZigToolchainProvider {
suspend fun getToolchain(project: Project?): ZigToolchain?
val serialMarker: String
fun deserialize(data: JsonElement): ZigToolchain?
fun canSerialize(toolchain: ZigToolchain): Boolean
fun serialize(toolchain: ZigToolchain): JsonElement
companion object {
val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider")
suspend fun findToolchains(project: Project?): ZigToolchain? {
return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.getToolchain(project) }
}
}
}
class ZigToolchainConverter: Converter<ZigToolchain>() {
override fun fromString(value: String): ZigToolchain? {
val json = Json.parseToJsonElement(value) as? JsonObject ?: return null
val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null
val data = json["data"] ?: return null
val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null
return provider.deserialize(data)
}
override fun toString(value: ZigToolchain): String? {
val provider = EXTENSION_POINT_NAME.extensionList.find { it.canSerialize(value) } ?: return null
return buildJsonObject {
put("marker", provider.serialMarker)
put("data", provider.serialize(value))
}.toString()
}
}

View file

@ -0,0 +1,36 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain.tools
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSerializable
import kotlinx.serialization.json.Json
class ZigCompilerTool(toolchain: AbstractZigToolchain): ZigTool(toolchain) {
override val toolName: String
get() = "zig"
suspend fun getEnv(): ZigToolchainEnvironmentSerializable {
return Json.decodeFromString<ZigToolchainEnvironmentSerializable>(callWithArgs(toolchain.location, "env").stdout)
}
}

View file

@ -0,0 +1,60 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2024 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain.tools
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 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 {
val process = createBaseCommandLine(workingDirectory, *parameters).createProcess()
val exitCode = process.awaitExit()
return runInterruptible {
ProcessOutput(
process.inputStream.bufferedReader().use { it.readText() },
process.errorStream.bufferedReader().use { it.readText() },
exitCode,
false,
false
)
}
}
protected suspend fun createBaseCommandLine(workingDirectory: Path?,
vararg parameters: String): GeneralCommandLine {
return GeneralCommandLine()
.withExePath(toolchain.pathToExecutable(toolName).toString())
.withWorkDirectory(workingDirectory?.toString())
.withParameters(*parameters)
.withCharset(Charsets.UTF_8)
}
}

View file

@ -24,6 +24,7 @@ package com.falsepattern.zigbrains.lsp.settings
import org.jetbrains.annotations.NonNls
@JvmRecord
data class ZLSSettings(
val direnv: Boolean = true,
val zlsPath: @NonNls String = "",

View file

@ -14,4 +14,11 @@
dynamic="true"
name="zlsConfigProvider"/>
</extensionPoints>
<extensionPoints>
<extensionPoint
interface="com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider"
dynamic="true"
name="toolchainProvider"
/>
</extensionPoints>
</idea-plugin>