Skip to content

Commit 2d3f45e

Browse files
authored
VFS (#720)
Towards #530
1 parent aa7b553 commit 2d3f45e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+568
-311
lines changed

client/src/build-system.ts

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
// SPDX-License-Identifier: MIT
22
// Copyright © 2025 TON Studio
33
import * as vscode from "vscode"
4-
import * as fs from "node:fs"
5-
import * as path from "node:path"
6-
import {TaskDefinition} from "vscode"
74

85
interface TaskProviderBase extends vscode.TaskProvider {
96
createTask(): vscode.Task
107

11-
isAvailable(): boolean
8+
isAvailable(): Promise<boolean>
129

1310
readonly taskType: string
1411
}
@@ -26,13 +23,13 @@ export class BlueprintTaskProvider implements TaskProviderBase {
2623
this.group = group
2724
}
2825

29-
public provideTasks(): vscode.Task[] {
30-
const isAvailable = this.isAvailable()
26+
public async provideTasks(): Promise<vscode.Task[]> {
27+
const isAvailable = await this.isAvailable()
3128
if (!isAvailable) return []
3229
return [this.createTask()]
3330
}
3431

35-
public isAvailable(): boolean {
32+
public async isAvailable(): Promise<boolean> {
3633
return projectUsesBlueprint()
3734
}
3835

@@ -45,7 +42,7 @@ export class BlueprintTaskProvider implements TaskProviderBase {
4542
}
4643

4744
public createTask(): vscode.Task {
48-
const definition: TaskDefinition = {
45+
const definition: vscode.TaskDefinition = {
4946
type: this.taskType,
5047
}
5148

@@ -88,12 +85,12 @@ export class TactTemplateTaskProvider implements TaskProviderBase {
8885
this.group = group
8986
}
9087

91-
public isAvailable(): boolean {
92-
return !projectUsesBlueprint()
88+
public async isAvailable(): Promise<boolean> {
89+
return !(await projectUsesBlueprint())
9390
}
9491

95-
public provideTasks(): vscode.Task[] {
96-
const isAvailable = this.isAvailable()
92+
public async provideTasks(): Promise<vscode.Task[]> {
93+
const isAvailable = await this.isAvailable()
9794
if (!isAvailable) return []
9895
return [this.createTask()]
9996
}
@@ -107,7 +104,7 @@ export class TactTemplateTaskProvider implements TaskProviderBase {
107104
}
108105

109106
public createTask(): vscode.Task {
110-
const definition: TaskDefinition = {
107+
const definition: vscode.TaskDefinition = {
111108
type: this.taskType,
112109
}
113110

@@ -137,19 +134,22 @@ export class TactTemplateTaskProvider implements TaskProviderBase {
137134
}
138135
}
139136

140-
function registerTaskProvider(context: vscode.ExtensionContext, provider: TaskProviderBase): void {
141-
if (!provider.isAvailable()) return
137+
async function registerTaskProvider(
138+
context: vscode.ExtensionContext,
139+
provider: TaskProviderBase,
140+
): Promise<void> {
141+
if (!(await provider.isAvailable())) return
142142

143143
const taskProviderDisposable = vscode.tasks.registerTaskProvider(provider.taskType, provider)
144144
context.subscriptions.push(taskProviderDisposable)
145145
}
146146

147-
export function registerBuildTasks(context: vscode.ExtensionContext): void {
148-
registerTaskProvider(
147+
export async function registerBuildTasks(context: vscode.ExtensionContext): Promise<void> {
148+
await registerTaskProvider(
149149
context,
150150
new BlueprintTaskProvider("build", "build", "npx blueprint build", vscode.TaskGroup.Build),
151151
)
152-
registerTaskProvider(
152+
await registerTaskProvider(
153153
context,
154154
new BlueprintTaskProvider(
155155
"build-all",
@@ -158,11 +158,11 @@ export function registerBuildTasks(context: vscode.ExtensionContext): void {
158158
vscode.TaskGroup.Build,
159159
),
160160
)
161-
registerTaskProvider(
161+
await registerTaskProvider(
162162
context,
163163
new BlueprintTaskProvider("test", "test", "npx blueprint test", vscode.TaskGroup.Test),
164164
)
165-
registerTaskProvider(
165+
await registerTaskProvider(
166166
context,
167167
new BlueprintTaskProvider(
168168
"build-and-test-all",
@@ -171,15 +171,15 @@ export function registerBuildTasks(context: vscode.ExtensionContext): void {
171171
vscode.TaskGroup.Build,
172172
),
173173
)
174-
registerTaskProvider(
174+
await registerTaskProvider(
175175
context,
176176
new TactTemplateTaskProvider("build", "build", "yarn build", vscode.TaskGroup.Build),
177177
)
178-
registerTaskProvider(
178+
await registerTaskProvider(
179179
context,
180180
new TactTemplateTaskProvider("test", "test", "yarn test", vscode.TaskGroup.Test),
181181
)
182-
registerTaskProvider(
182+
await registerTaskProvider(
183183
context,
184184
new TactTemplateTaskProvider(
185185
"build-and-test",
@@ -208,14 +208,15 @@ export function registerBuildTasks(context: vscode.ExtensionContext): void {
208208
)
209209
}
210210

211-
function projectUsesBlueprint(): boolean {
211+
async function projectUsesBlueprint(): Promise<boolean> {
212212
const workspaceFolders = vscode.workspace.workspaceFolders
213213
if (!workspaceFolders || workspaceFolders.length === 0) return false
214214

215-
const packageJsonPath = path.join(workspaceFolders[0].uri.fsPath, "package.json")
216-
217215
try {
218-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
216+
const packageJsonContent = await vscode.workspace.fs.readFile(
217+
vscode.Uri.joinPath(workspaceFolders[0].uri, "package.json"),
218+
)
219+
const packageJson = JSON.parse(new TextDecoder().decode(packageJsonContent)) as {
219220
dependencies?: Record<string, unknown>
220221
devDependencies?: Record<string, unknown>
221222
}

client/src/commands/saveBocDecompiledCommand.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
// SPDX-License-Identifier: MIT
22
// Copyright © 2025 TON Studio
33
import * as vscode from "vscode"
4-
import * as path from "node:path"
5-
import * as fs from "node:fs"
64
import {BocDecompilerProvider} from "../providers/BocDecompilerProvider"
7-
import {Disposable} from "vscode"
85

9-
export function registerSaveBocDecompiledCommand(_context: vscode.ExtensionContext): Disposable {
6+
export function registerSaveBocDecompiledCommand(
7+
_context: vscode.ExtensionContext,
8+
): vscode.Disposable {
109
return vscode.commands.registerCommand(
1110
"tact.saveBocDecompiled",
1211
async (fileUri: vscode.Uri | undefined) => {
@@ -46,16 +45,14 @@ async function saveBoc(fileUri: vscode.Uri | undefined): Promise<void> {
4645
scheme: BocDecompilerProvider.scheme,
4746
path: actualFileUri.path + ".decompiled.fif",
4847
})
49-
const content = decompiler.provideTextDocumentContent(decompileUri)
48+
const content = await decompiler.provideTextDocumentContent(decompileUri)
5049

5150
const outputPath = actualFileUri.fsPath + ".decompiled.fif"
5251

53-
fs.writeFileSync(outputPath, content)
52+
const bytes = new TextEncoder().encode(content)
53+
vscode.workspace.fs.writeFile(vscode.Uri.file(outputPath), bytes)
5454

55-
const relativePath = path.relative(
56-
vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? "",
57-
outputPath,
58-
)
55+
const relativePath = vscode.workspace.asRelativePath(outputPath)
5956
vscode.window.showInformationMessage(`Decompiled BOC saved to: ${relativePath}`)
6057

6158
const savedFileUri = vscode.Uri.file(outputPath)

client/src/extension.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// SPDX-License-Identifier: MIT
22
// Copyright © 2025 TON Studio
33
import * as vscode from "vscode"
4-
import * as fs from "node:fs"
54
import * as path from "node:path"
65
import {Utils as vscode_uri} from "vscode-uri"
76
import {
@@ -44,9 +43,9 @@ let client: LanguageClient | null = null
4443
let gasStatusBarItem: vscode.StatusBarItem | null = null
4544
let cachedToolchainInfo: SetToolchainVersionParams | null = null
4645

47-
export function activate(context: vscode.ExtensionContext): void {
46+
export async function activate(context: vscode.ExtensionContext): Promise<void> {
4847
startServer(context).catch(consoleError)
49-
registerBuildTasks(context)
48+
await registerBuildTasks(context)
5049
registerOpenBocCommand(context)
5150
registerSaveBocDecompiledCommand(context)
5251
registerMistiCommand(context)
@@ -817,14 +816,15 @@ function getInstallCommandForMisti(packageManager: PackageManager): string {
817816
}
818817
}
819818

820-
function projectUsesMisti(): boolean {
819+
async function projectUsesMisti(): Promise<boolean> {
821820
const workspaceFolders = vscode.workspace.workspaceFolders
822821
if (!workspaceFolders || workspaceFolders.length === 0) return false
823822

824-
const packageJsonPath = path.join(workspaceFolders[0].uri.fsPath, "package.json")
825-
826823
try {
827-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
824+
const packageJsonContent = await vscode.workspace.fs.readFile(
825+
vscode.Uri.joinPath(workspaceFolders[0].uri, "package.json"),
826+
)
827+
const packageJson = JSON.parse(new TextDecoder().decode(packageJsonContent)) as {
828828
dependencies?: Record<string, unknown>
829829
devDependencies?: Record<string, unknown>
830830
}
@@ -842,8 +842,8 @@ function projectUsesMisti(): boolean {
842842
function registerMistiCommand(context: vscode.ExtensionContext): void {
843843
context.subscriptions.push(
844844
vscode.commands.registerCommand("tact.runMisti", async () => {
845-
if (!projectUsesMisti()) {
846-
const packageManager = detectPackageManager()
845+
if (!(await projectUsesMisti())) {
846+
const packageManager = await detectPackageManager()
847847
const installCommand = getInstallCommandForMisti(packageManager)
848848

849849
const result = await vscode.window.showErrorMessage(

client/src/providers/BocDecompilerProvider.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,37 @@
22
// Copyright © 2025 TON Studio
33
import * as vscode from "vscode"
44
import {AssemblyWriter, Cell, debugSymbols, disassembleRoot} from "@tact-lang/opcode"
5-
import {readFileSync, existsSync} from "node:fs"
65

76
export class BocDecompilerProvider implements vscode.TextDocumentContentProvider {
87
private readonly _onDidChange: vscode.EventEmitter<vscode.Uri> = new vscode.EventEmitter()
98
public readonly onDidChange: vscode.Event<vscode.Uri> = this._onDidChange.event
109

11-
private readonly lastModified: Map<string, Date> = new Map()
10+
private readonly lastModified: Map<vscode.Uri, Date> = new Map()
1211

1312
public static scheme: string = "boc-decompiled"
1413

15-
public provideTextDocumentContent(uri: vscode.Uri): string {
16-
const bocPath = this.getBocPath(uri)
14+
public async provideTextDocumentContent(uri: vscode.Uri): Promise<string> {
15+
const bocUri = this.getBocPath(uri)
1716

1817
try {
19-
return this.decompileBoc(bocPath)
18+
return await this.decompileBoc(bocUri)
2019
} catch (error) {
2120
const errorMessage = error instanceof Error ? error.message : String(error)
2221
return this.formatError(errorMessage)
2322
}
2423
}
2524

26-
private getBocPath(uri: vscode.Uri): string {
25+
private getBocPath(uri: vscode.Uri): vscode.Uri {
2726
console.log("Original URI:", uri.toString())
2827
const bocPath = uri.fsPath.replace(".decompiled.fif", "")
2928
console.log("BOC path:", bocPath)
30-
return bocPath
29+
return vscode.Uri.file(bocPath)
3130
}
3231

33-
private decompileBoc(bocPath: string): string {
32+
private async decompileBoc(bocUri: vscode.Uri): Promise<string> {
3433
try {
35-
if (!existsSync(bocPath)) {
36-
throw new Error(`BoC file not found: ${bocPath}`)
37-
}
38-
39-
const content = readFileSync(bocPath).toString("base64")
34+
const rawContent = await vscode.workspace.fs.readFile(bocUri)
35+
const content = Buffer.from(rawContent).toString("base64")
4036
const cell = Cell.fromBase64(content)
4137
const program = disassembleRoot(cell, {
4238
computeRefs: true,
@@ -47,19 +43,19 @@ export class BocDecompilerProvider implements vscode.TextDocumentContentProvider
4743
debugSymbols: debugSymbols,
4844
})
4945

50-
return this.formatDecompiledOutput(output, bocPath)
46+
return this.formatDecompiledOutput(output, bocUri)
5147
} catch (error: unknown) {
5248
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
5349
throw new Error(`Decompilation failed: ${error}`)
5450
}
5551
}
5652

57-
private formatDecompiledOutput(output: string, bocPath?: string): string {
53+
private formatDecompiledOutput(output: string, bocUri: vscode.Uri): string {
5854
const header = [
5955
"// Decompiled BOC file",
6056
"// Note: This is auto-generated code",
6157
`// Time: ${new Date().toISOString()}`,
62-
...(bocPath ? [`// Source: ${bocPath}`] : []),
58+
`// Source: ${bocUri.fsPath}`,
6359
"",
6460
"",
6561
].join("\n")
@@ -76,8 +72,8 @@ export class BocDecompilerProvider implements vscode.TextDocumentContentProvider
7672
}
7773

7874
public update(uri: vscode.Uri): void {
79-
const bocPath = this.getBocPath(uri)
80-
this.lastModified.set(bocPath, new Date())
75+
const bocUri = this.getBocPath(uri)
76+
this.lastModified.set(bocUri, new Date())
8177
this._onDidChange.fire(uri)
8278
}
8379
}

client/src/providers/BocFileSystemProvider.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Copyright © 2025 TON Studio
33
import * as vscode from "vscode"
44
import {BocDecompilerProvider} from "./BocDecompilerProvider"
5-
import {readFileSync} from "node:fs"
65

76
export class BocFileSystemProvider implements vscode.FileSystemProvider {
87
private readonly _emitter: vscode.EventEmitter<vscode.FileChangeEvent[]> =
@@ -31,7 +30,7 @@ export class BocFileSystemProvider implements vscode.FileSystemProvider {
3130
public async readFile(uri: vscode.Uri): Promise<Uint8Array> {
3231
console.log("Reading BOC file:", uri.fsPath)
3332
try {
34-
const fileContent = readFileSync(uri.fsPath)
33+
const fileContent = await vscode.workspace.fs.readFile(uri)
3534
console.log("File content length:", fileContent.length)
3635

3736
const decompileUri = uri.with({

client/src/utils/package-manager.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
// SPDX-License-Identifier: MIT
22
// Copyright © 2025 TON Studio
33
import * as vscode from "vscode"
4-
import * as fs from "node:fs"
5-
import * as path from "node:path"
64

75
export type PackageManager = "yarn" | "npm" | "pnpm" | "bun"
86

9-
export function detectPackageManager(): PackageManager {
7+
export async function detectPackageManager(): Promise<PackageManager> {
108
const workspaceFolders = vscode.workspace.workspaceFolders
119
if (!workspaceFolders || workspaceFolders.length === 0) return "npm"
1210

13-
const workspaceRoot = workspaceFolders[0].uri.fsPath
11+
const workspaceRoot = workspaceFolders[0].uri
1412

1513
// Check for lock files
16-
if (fs.existsSync(path.join(workspaceRoot, "bun.lockb"))) {
14+
if (await pathExits(workspaceRoot, "bun.lockb")) {
1715
return "bun"
1816
}
19-
if (fs.existsSync(path.join(workspaceRoot, "yarn.lock"))) {
17+
if (await pathExits(workspaceRoot, "yarn.lock")) {
2018
return "yarn"
2119
}
22-
if (fs.existsSync(path.join(workspaceRoot, "pnpm-lock.yaml"))) {
20+
if (await pathExits(workspaceRoot, "pnpm-lock.yaml")) {
2321
return "pnpm"
2422
}
25-
if (fs.existsSync(path.join(workspaceRoot, "package-lock.json"))) {
23+
if (await pathExits(workspaceRoot, "package-lock.json")) {
2624
return "npm"
2725
}
2826

2927
try {
30-
const packageJsonPath = path.join(workspaceRoot, "package.json")
31-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
28+
const packageJsonContent = await vscode.workspace.fs.readFile(
29+
vscode.Uri.joinPath(workspaceFolders[0].uri, "package.json"),
30+
)
31+
const packageJson = JSON.parse(new TextDecoder().decode(packageJsonContent)) as {
3232
packageManager?: string
3333
}
3434

@@ -49,3 +49,12 @@ export function detectPackageManager(): PackageManager {
4949

5050
return "npm"
5151
}
52+
53+
async function pathExits(path: vscode.Uri, file: string): Promise<boolean> {
54+
try {
55+
await vscode.workspace.fs.stat(vscode.Uri.joinPath(path, file))
56+
return true
57+
} catch {
58+
return false
59+
}
60+
}

0 commit comments

Comments
 (0)