Skip to content

Commit 4a3f5c4

Browse files
authored
Add globalState to the standalone vscode extension context replacement. (RooCodeInc#3987)
* Add globalState to the standalone vscode extension context replacement. Add a generic key-value store that can be used by different storages, and move this into vscode-context-utils file. Move the stubs/mocks into a separate stubs file, (they have type checking turned off). Keep the implementations in vscode-context and turn on typechecking for this file. * Add type parameter for the values in the JsonKeyValueStore * Optimize imports * Add implementations for the vscode ExtensionContext in the standalone app (RooCodeInc#4000) * Formatting * add package-lock.json
1 parent 9f85105 commit 4a3f5c4

File tree

8 files changed

+236
-194
lines changed

8 files changed

+236
-194
lines changed

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@
402402
"tree-sitter-wasms": "^0.1.11",
403403
"ts-morph": "^25.0.1",
404404
"turndown": "^7.2.0",
405+
"vscode-uri": "^3.1.0",
405406
"web-tree-sitter": "^0.22.6",
406407
"zod": "^3.24.2"
407408
}

scripts/package-standalone.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const output = fs.createWriteStream(zipPath)
3939
const archive = archiver("zip", { zlib: { level: 9 } })
4040

4141
output.on("close", () => {
42-
console.log(`Created ${zipPath} (${archive.pointer()} bytes)`)
42+
console.log(`Created ${zipPath} (${(archive.pointer() / 1024 / 1024).toFixed(1)} MB)`)
4343
})
4444

4545
archive.on("error", (err) => {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @ts-nocheck
2+
import * as vscode from "vscode"
3+
4+
import { log } from "./utils"
5+
6+
const outputChannel: vscode.OutputChannel = {
7+
append: (text) => process.stdout.write(text),
8+
appendLine: (line) => console.log(`OUTPUT_CHANNEL: ${line}`),
9+
clear: () => {},
10+
show: () => {},
11+
hide: () => {},
12+
dispose: () => {},
13+
name: "",
14+
replace: function (value: string): void {},
15+
}
16+
17+
function postMessage(message: ExtensionMessage): Promise<boolean> {
18+
log("postMessage stub called:", message)
19+
return Promise.resolve(true)
20+
}
21+
22+
export { outputChannel, postMessage }
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import * as fs from "fs"
2+
import * as vscode from "vscode"
3+
import type { EnvironmentVariableMutatorOptions, EnvironmentVariableMutator, EnvironmentVariableScope } from "vscode"
4+
export class SecretStore implements vscode.SecretStorage {
5+
private data: JsonKeyValueStore<string>
6+
private readonly _onDidChange = new EventEmitter<vscode.SecretStorageChangeEvent>()
7+
8+
constructor(filepath: string) {
9+
this.data = new JsonKeyValueStore(filepath)
10+
}
11+
12+
readonly onDidChange: vscode.Event<vscode.SecretStorageChangeEvent> = this._onDidChange.event
13+
14+
get(key: string): Thenable<string | undefined> {
15+
return Promise.resolve(this.data.get(key))
16+
}
17+
18+
store(key: string, value: string): Thenable<void> {
19+
this.data.put(key, value)
20+
this._onDidChange.fire({ key })
21+
return Promise.resolve()
22+
}
23+
24+
delete(key: string): Thenable<void> {
25+
this.data.delete(key)
26+
this._onDidChange.fire({ key })
27+
return Promise.resolve()
28+
}
29+
}
30+
31+
// Create a class that implements Memento interface with the required setKeysForSync method
32+
export class MementoStore implements vscode.Memento {
33+
private data: JsonKeyValueStore<any>
34+
35+
constructor(filepath: string) {
36+
this.data = new JsonKeyValueStore(filepath)
37+
}
38+
keys(): readonly string[] {
39+
return Array.from(this.data.keys())
40+
}
41+
get<T>(key: string): T | undefined {
42+
return this.data.get(key) as T
43+
}
44+
update(key: string, value: any): Thenable<void> {
45+
this.data.put(key, value)
46+
return Promise.resolve()
47+
}
48+
setKeysForSync(_keys: readonly string[]): void {
49+
throw new Error("Method not implemented.")
50+
}
51+
}
52+
53+
// Simple implementation of VSCode's EventEmitter
54+
type EventCallback<T> = (e: T) => any
55+
export class EventEmitter<T> {
56+
private listeners: EventCallback<T>[] = []
57+
58+
event: vscode.Event<T> = (listener: EventCallback<T>) => {
59+
this.listeners.push(listener)
60+
return {
61+
dispose: () => {
62+
const index = this.listeners.indexOf(listener)
63+
if (index !== -1) {
64+
this.listeners.splice(index, 1)
65+
}
66+
},
67+
}
68+
}
69+
70+
fire(data: T): void {
71+
this.listeners.forEach((listener) => listener(data))
72+
}
73+
}
74+
75+
/** A simple key-value store for secrets backed by a JSON file. This is not secure, and it is not thread-safe. */
76+
export class JsonKeyValueStore<T> {
77+
private data = new Map<string, T>()
78+
private filePath: string
79+
80+
constructor(filePath: string) {
81+
this.filePath = filePath
82+
this.load()
83+
}
84+
85+
get(key: string): T | undefined {
86+
return this.data.get(key)
87+
}
88+
89+
put(key: string, value: T): void {
90+
this.data.set(key, value)
91+
this.save()
92+
}
93+
94+
delete(key: string): void {
95+
this.data.delete(key)
96+
this.save()
97+
}
98+
keys(): Iterable<string> | ArrayLike<string> {
99+
return this.data.keys()
100+
}
101+
private load(): void {
102+
if (fs.existsSync(this.filePath)) {
103+
const data = JSON.parse(fs.readFileSync(this.filePath, "utf-8"))
104+
Object.entries(data).forEach(([k, v]) => {
105+
this.data.set(k, v as T)
106+
})
107+
}
108+
}
109+
private save(): void {
110+
fs.writeFileSync(this.filePath, JSON.stringify(Object.fromEntries(this.data), null, 2))
111+
}
112+
}
113+
114+
/** This is not used in cline, none of the methods are implemented. */
115+
export class EnvironmentVariableCollection implements EnvironmentVariableCollection {
116+
persistent: boolean = false
117+
description: string | undefined = undefined
118+
replace(_variable: string, _value: string, _options?: EnvironmentVariableMutatorOptions): void {
119+
throw new Error("Method not implemented.")
120+
}
121+
append(_variable: string, _value: string, _options?: EnvironmentVariableMutatorOptions): void {
122+
throw new Error("Method not implemented.")
123+
}
124+
prepend(_variable: string, _value: string, _options?: EnvironmentVariableMutatorOptions): void {
125+
throw new Error("Method not implemented.")
126+
}
127+
get(_variable: string): EnvironmentVariableMutator | undefined {
128+
throw new Error("Method not implemented.")
129+
}
130+
forEach(
131+
_callback: (variable: string, mutator: EnvironmentVariableMutator, collection: EnvironmentVariableCollection) => any,
132+
_thisArg?: any,
133+
): void {
134+
throw new Error("Method not implemented.")
135+
}
136+
delete(_variable: string): void {
137+
throw new Error("Method not implemented.")
138+
}
139+
clear(): void {
140+
throw new Error("Method not implemented.")
141+
}
142+
[Symbol.iterator](): Iterator<[variable: string, mutator: EnvironmentVariableMutator], any, any> {
143+
throw new Error("Method not implemented.")
144+
}
145+
getScoped(_scope: EnvironmentVariableScope): EnvironmentVariableCollection {
146+
throw new Error("Method not implemented.")
147+
}
148+
}
149+
150+
export function readJson(filePath: string): any {
151+
return JSON.parse(fs.readFileSync(filePath, "utf8"))
152+
}

0 commit comments

Comments
 (0)