Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat(compiler): initial compiler integration #236

Merged
merged 1 commit into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
GetTypeAtPositionParams,
GetTypeAtPositionRequest,
GetTypeAtPositionResponse,
SetToolchainVersionNotification,
SetToolchainVersionParams,
} from "@shared/shared-msgtypes"
import type {Location, Position} from "vscode-languageclient"
import type {ClientOptions} from "@shared/config-scheme"
Expand Down Expand Up @@ -80,6 +82,26 @@ async function startServer(context: vscode.ExtensionContext): Promise<vscode.Dis

registerCommands(disposables)

const langStatusBar = vscode.window.createStatusBarItem(
"Tact",
vscode.StatusBarAlignment.Left,
60,
)

langStatusBar.text = "Tact"

client.onNotification(SetToolchainVersionNotification, (version: SetToolchainVersionParams) => {
const settings = vscode.workspace.getConfiguration("tact")
const hash =
settings.get<boolean>("toolchain.showShortCommitInStatusBar") &&
version.version.commit.length > 8
? ` (${version.version.commit.slice(-8)})`
: ""

langStatusBar.text = `Tact ${version.version.number}${hash}`
langStatusBar.show()
})

return new vscode.Disposable(() => {
disposables.forEach(d => void d.dispose())
})
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@
"default": null,
"description": "Path to Tact standard library. If empty, will try to find in node_modules"
},
"tact.toolchain.compilerPath": {
"type": "string",
"default": "",
"description": "Path to Tact compiler executable. If empty, will try to find in node_modules"
},
"tact.toolchain.showShortCommitInStatusBar": {
"type": "boolean",
"default": false,
"description": "Add short commit hash after Tact version in status bar"
},
"tact.hints.types": {
"type": "boolean",
"default": true,
Expand Down
22 changes: 22 additions & 0 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
GetTypeAtPositionParams,
GetTypeAtPositionRequest,
GetTypeAtPositionResponse,
SetToolchainVersionNotification,
SetToolchainVersionParams,
} from "@shared/shared-msgtypes"
import {KeywordsCompletionProvider} from "./completion/providers/KeywordsCompletionProvider"
import type {CompletionProvider} from "./completion/CompletionProvider"
Expand Down Expand Up @@ -111,6 +113,7 @@ import {
WrapSelectedToTryCatch,
} from "@server/intentions/WrapSelected"
import {PostfixCompletionProvider} from "@server/completion/providers/PostfixCompletionProvider"
import {Toolchain, InvalidToolchainError, fallbackToolchain} from "@server/toolchain/toolchain"

/**
* Whenever LS is initialized.
Expand All @@ -120,6 +123,8 @@ import {PostfixCompletionProvider} from "@server/completion/providers/PostfixCom
*/
let initialized = false

let toolchain: Toolchain = fallbackToolchain

let clientInfo: {name?: string; version?: string} = {name: "", version: ""}

/**
Expand Down Expand Up @@ -184,6 +189,23 @@ async function initialize(): Promise<void> {

const settings = await getDocumentSettings(rootUri)

try {
toolchain =
settings.toolchain.compilerPath === ""
? Toolchain.autoDetect(rootDir)
: Toolchain.fromPath(settings.toolchain.compilerPath)
console.info(`using toolchain ${toolchain.toString()}`)

await connection.sendNotification(SetToolchainVersionNotification, {
version: toolchain.version,
} satisfies SetToolchainVersionParams)
} catch (error) {
if (error instanceof InvalidToolchainError) {
console.info(`toolchain is invalid ${error.message}`)
showErrorMessage(error.message)
}
}

const stdlibPath = findStdlib(settings, rootDir)
if (stdlibPath !== null) {
reporter.report(50, "Indexing: (1/3) Standard Library")
Expand Down
96 changes: 96 additions & 0 deletions server/src/toolchain/toolchain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as path from "node:path"
import * as cp from "node:child_process"
import {SpawnSyncReturns} from "node:child_process"
import {existsSync} from "node:fs"
import * as console from "node:console"

export class InvalidToolchainError extends Error {
public constructor(message: string) {
super(message)
this.name = "InvalidToolchainError"
}
}

export class Toolchain {
public readonly compilerPath: string
public version: {
number: string
commit: string
}

public constructor(compilerPath: string) {
this.compilerPath = compilerPath
this.version = {
number: "",
commit: "",
}
}

public static autoDetect(root: string): Toolchain {
const defaultPath = path.join(root, "node_modules", ".bin", "tact")
if (!existsSync(defaultPath)) {
console.info(`cannot find toolchain in default directory: ${defaultPath}`)
return fallbackToolchain
}

return new Toolchain(defaultPath).setVersion()
}

public static fromPath(path: string): Toolchain {
return new Toolchain(path).validate()
}

private setVersion(): this {
try {
const result = cp.execSync(`${this.compilerPath} -v`)
const rawVersion = result.toString()
const lines = rawVersion.split("\n")

this.version = {
number: lines[0] ?? "",
commit: lines[1] ?? "",
}
} catch {
// ignore errors here for now
}
return this
}

private validate(): this {
try {
const result = cp.execSync(`${this.compilerPath} -v`)
const rawVersion = result.toString()
const lines = rawVersion.split("\n")

this.version = {
number: lines[0] ?? "",
commit: lines[1] ?? "",
}
} catch (error_: unknown) {
const error = error_ as SpawnSyncReturns<Buffer>

console.log(error.stdout.toString())
console.log(error.stderr.toString())

const tip = `Please recheck path or leave it empty to LS find toolchain automatically`

if (error.stderr.includes("not found")) {
throw new InvalidToolchainError(
`Cannot find valid Tact executable in "${this.compilerPath}"! ${tip}`,
)
}

throw new InvalidToolchainError(
`Path ${this.compilerPath} is invalid! ${tip}: ${error.stderr.toString()}`,
)
}

return this
}

public toString(): string {
return `Toolchain(path=${this.compilerPath}, version=${this.version.number}:${this.version.commit})`
}
}

export const fallbackToolchain = new Toolchain("fallback-toolchain")
15 changes: 15 additions & 0 deletions server/src/utils/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export interface TactSettings {
stdlib: {
path: string | null
}
toolchain: {
compilerPath: string
showShortCommitInStatusBar: boolean
}
findUsages: {
scope: FindUsagesScope
}
Expand Down Expand Up @@ -54,6 +58,10 @@ const defaultSettings: TactSettings = {
stdlib: {
path: null,
},
toolchain: {
compilerPath: "",
showShortCommitInStatusBar: false,
},
findUsages: {
scope: "workspace",
},
Expand Down Expand Up @@ -105,6 +113,13 @@ function mergeSettings(vsSettings: Partial<TactSettings>): TactSettings {
stdlib: {
path: vsSettings.stdlib?.path ?? defaultSettings.stdlib.path,
},
toolchain: {
compilerPath:
vsSettings.toolchain?.compilerPath ?? defaultSettings.toolchain.compilerPath,
showShortCommitInStatusBar:
vsSettings.toolchain?.showShortCommitInStatusBar ??
defaultSettings.toolchain.showShortCommitInStatusBar,
},
findUsages: {
scope: vsSettings.findUsages?.scope ?? defaultSettings.findUsages.scope,
},
Expand Down
8 changes: 8 additions & 0 deletions shared/src/shared-msgtypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type * as lsp from "vscode-languageserver"

export const GetTypeAtPositionRequest = "tact/getTypeAtPosition"
export const GetDocumentationAtPositionRequest = "tact/executeHoverProvider"
export const SetToolchainVersionNotification = "tact/setToolchainVersion"

export interface GetTypeAtPositionParams {
textDocument: {
Expand All @@ -13,6 +14,13 @@ export interface GetTypeAtPositionParams {
}
}

export interface SetToolchainVersionParams {
version: {
number: string
commit: string
}
}

export interface GetTypeAtPositionResponse {
type: string | null
}
Expand Down