Skip to content

Commit c68a7f3

Browse files
authored
Try support vim (#46)
* wip: try support vim * refactor: enhance document handling in lsp-server - Removed middleware from client options in lsp-client for cleaner code. - Introduced DocumentStore class in lsp-server to manage document operations, including fallback methods for file reading and bean file listing. - Updated startServer function to utilize the new DocumentStore, improving document management in both browser and node environments. - Added support for reading files and listing bean files in the Node environment using fast-glob and fs/promises. * fix: custom capabilities * feat: trigger index without QueueInit message * fix: correct configuration property name in DocumentStore - Updated references from 'manBeanFile' to 'mainBeanFile' in the DocumentStore class to ensure proper configuration handling. - Adjusted warning message to reflect the correct property name for user guidance. * chore: bump lsp-client version to 0.0.62 * chore: update changelog
1 parent 01964f9 commit c68a7f3

File tree

9 files changed

+104
-19
lines changed

9 files changed

+104
-19
lines changed

packages/lsp-client/src/browser/extension.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ export function activate(context: vscode.ExtensionContext): void {
3232
statusBarItem.show();
3333
context.subscriptions.push(statusBarItem);
3434

35-
// Show notification that this is experimental
36-
vscode.window.showInformationMessage(
37-
'Beancount LSP browser extension is experimental and might have limited functionality compared to the desktop version.',
38-
);
39-
4035
try {
4136
// Resolve the web-tree-sitter.wasm path
4237
const webTreeSitterWasmPath = resolveWebTreeSitterWasmPath(context);

packages/lsp-client/src/common/client.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { CustomMessages, Logger, logLevelToString, mapTraceServerToLogLevel } from '@bean-lsp/shared';
22
import * as vscode from 'vscode';
33
// Import from base package for shared types between node and browser
4-
import { LanguageClientOptions, State } from 'vscode-languageclient';
4+
import type { InitializeParams, LanguageClientOptions, ProtocolConnection } from 'vscode-languageclient';
5+
import { State } from 'vscode-languageclient';
56
import { Utils as UriUtils } from 'vscode-uri';
67
import { tools } from './llm/tools';
78
import { ClientOptions, ExtensionContext } from './types';
@@ -223,6 +224,17 @@ export function setupStatusBar(ctx: ExtensionContext<'browser' | 'node'>): void
223224
* Sets up custom message handlers for the client
224225
*/
225226
export function setupCustomMessageHandlers(ctx: ExtensionContext<'browser' | 'node'>): void {
227+
const originalInitialize = ctx.client['doInitialize'].bind(ctx.client);
228+
ctx.client['doInitialize'] = async (connection: ProtocolConnection, params: InitializeParams) => {
229+
console.log('===== doInitialize', params);
230+
// @ts-expect-error customMessage is not part of the protocol
231+
params.capabilities['customMessage'] = {
232+
[CustomMessages.ListBeanFile]: true,
233+
[CustomMessages.FileRead]: true,
234+
};
235+
return originalInitialize(connection, params);
236+
};
237+
226238
ctx.client.onRequest(CustomMessages.ListBeanFile, async () => {
227239
const exclude = await generateExcludePattern();
228240
const files = await vscode.workspace.findFiles('**/*.{bean,beancount}', exclude);

packages/lsp-server/src/browser/server.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ProposedFeatures,
66
} from 'vscode-languageserver/browser';
77

8+
import { DocumentStore } from 'src/common/document-store';
89
import { ServerOptions, startServer } from '../common/startServer';
910
import { factory } from './storage';
1011

@@ -16,10 +17,13 @@ const messageWriter = new BrowserMessageWriter(self);
1617
const connection = createConnection(ProposedFeatures.all, messageReader, messageWriter);
1718

1819
// Server options - will be populated by the initialization options in startServer
19-
const serverOptions: ServerOptions = {};
20+
const serverOptions: ServerOptions = {
21+
isBrowser: true,
22+
};
2023

24+
const documents = new DocumentStore(connection);
2125
// Start the server with the options
22-
startServer(connection, factory, undefined, serverOptions);
26+
startServer(connection, factory, documents, undefined, serverOptions);
2327

2428
// Listen on the connection
2529
connection.listen();

packages/lsp-server/src/common/document-store.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import {
44
Connection,
55
Emitter,
66
Event,
7+
InitializeParams,
78
Range,
89
TextDocumentContentChangeEvent,
910
TextDocuments,
11+
WorkspaceFolder,
1012
} from 'vscode-languageserver';
1113
import { TextDocument } from 'vscode-languageserver-textdocument';
1214
import { URI, Utils as UriUtils } from 'vscode-uri';
@@ -29,10 +31,13 @@ export class DocumentStore extends TextDocuments<TextDocument> {
2931
readonly onDidChangeContent2: Event<TextDocumentChange2> = this._onDidChangeContent2.event;
3032

3133
private _beanFiles: string[] = [];
34+
private _initializeParams: InitializeParams | undefined;
3235

3336
private logger = new Logger('DocumentStore');
3437

35-
constructor(private readonly _connection: Connection) {
38+
constructor(
39+
private readonly _connection: Connection,
40+
) {
3641
super({
3742
create: TextDocument.create,
3843
update: (doc, changes, version) => {
@@ -61,13 +66,34 @@ export class DocumentStore extends TextDocuments<TextDocument> {
6166
});
6267
this.listen(_connection);
6368
}
69+
70+
public setInitializeParams(initializeParams: InitializeParams) {
71+
this._initializeParams = initializeParams;
72+
}
73+
6474
private readonly _documentsCache = new LRUMap<string, TextDocument>(200);
6575

6676
async refetchBeanFiles(): Promise<void> {
77+
// Check if client supports ListBeanFile capability
78+
// @ts-expect-error customMessage is not part of the protocol
79+
if (!this._initializeParams?.capabilities?.customMessage?.[CustomMessages.ListBeanFile]) {
80+
if (!this._initializeParams?.workspaceFolders?.[0]) {
81+
this._beanFiles = [];
82+
return;
83+
}
84+
this._beanFiles = await this.fallbackListBeanFiles(this._initializeParams.workspaceFolders[0]);
85+
return;
86+
}
87+
6788
const files = await this._connection.sendRequest<string[]>(CustomMessages.ListBeanFile);
6889
this._beanFiles = files;
6990
}
7091

92+
protected async fallbackListBeanFiles(_workspaceFolder: WorkspaceFolder): Promise<string[]> {
93+
this.logger.warn('Client does not support ListBeanFile capability');
94+
return this.all().map(doc => doc.uri);
95+
}
96+
7197
get beanFiles(): string[] {
7298
return this._beanFiles;
7399
}
@@ -92,11 +118,25 @@ export class DocumentStore extends TextDocuments<TextDocument> {
92118
}
93119

94120
private async _requestDocument(uri: string): Promise<TextDocument> {
95-
const reply = await this._connection.sendRequest<number[]>(CustomMessages.FileRead, uri);
121+
const reply = await this.fileRead(uri);
96122
const bytes = new Uint8Array(reply);
97123
return TextDocument.create(uri, LANGUAGE_ID, 1, this._decoder.decode(bytes));
98124
}
99125

126+
private async fileRead(uri: string): Promise<ArrayBuffer> {
127+
// Check if client supports FileRead capability
128+
// @ts-expect-error customMessage is not part of the protocol
129+
if (!this._initializeParams?.capabilities?.customMessage?.[CustomMessages.FileRead]) {
130+
return this.fallbackFileRead(uri);
131+
}
132+
return this._connection.sendRequest<ArrayBuffer>(CustomMessages.FileRead, uri);
133+
}
134+
135+
protected async fallbackFileRead(_uri: string): Promise<ArrayBuffer> {
136+
this.logger.warn('Client does not support FileRead capability');
137+
return new ArrayBuffer(0);
138+
}
139+
100140
removeFile(uri: string): boolean {
101141
return this._documentsCache.delete(uri);
102142
}

packages/lsp-server/src/common/features/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,8 @@ export interface RealBeancountManager {
3535
}
3636

3737
export type BeancountManagerFactory = (connection: Connection, extensionUri: string) => RealBeancountManager;
38+
39+
export interface PlatformMethods {
40+
findBeanFiles: () => Promise<string[]>;
41+
readFile: (uri: string) => Promise<string>;
42+
}

packages/lsp-server/src/common/startServer.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export interface IStorageFactory<T> {
4343
export interface ServerOptions {
4444
webTreeSitterWasmPath?: string;
4545
logLevel?: LogLevel;
46+
47+
isBrowser: boolean;
4648
}
4749

4850
// Create a server logger
@@ -51,8 +53,9 @@ const serverLogger = new Logger('Server');
5153
export function startServer(
5254
connection: Connection,
5355
factory: IStorageFactory<unknown>,
56+
documents: DocumentStore,
5457
beanMgrFactory: BeancountManagerFactory | undefined,
55-
options: ServerOptions = {},
58+
options: ServerOptions,
5659
): void {
5760
// Set initial log level from options
5861
if (options.logLevel !== undefined) {
@@ -72,11 +75,11 @@ export function startServer(
7275

7376
const features: Feature[] = [];
7477

75-
let documents: DocumentStore;
7678
let symbolIndex: SymbolIndex;
7779
let beanMgr: RealBeancountManager | undefined;
7880

7981
connection.onInitialize(async (params: InitializeParams): Promise<InitializeResult> => {
82+
documents.setInitializeParams(params);
8083
const result: InitializeResult = {
8184
capabilities: {
8285
textDocumentSync: TextDocumentSyncKind.Incremental,
@@ -130,7 +133,6 @@ export function startServer(
130133
await symbolStorage.autoloadPromise;
131134
connection.onExit(() => factory.destroy(symbolStorage));
132135

133-
documents = new DocumentStore(connection);
134136
const trees = new Trees(documents, options.webTreeSitterWasmPath!);
135137
const optionsManager = BeancountOptionsManager.getInstance();
136138
const historyContext = new HistoryContext(trees);
@@ -204,6 +206,8 @@ export function startServer(
204206
const mainBeanFile = await documents.getMainBeanFileUri();
205207
serverLogger.info(`mainBeanFile ${mainBeanFile}`);
206208
await documents.refetchBeanFiles();
209+
await symbolIndex.initFiles(documents.beanFiles);
210+
await symbolIndex.unleashFiles([]);
207211

208212
if (mainBeanFile) {
209213
await symbolIndex.initFiles([mainBeanFile]);

packages/lsp-server/src/node/beancount-manager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { Logger } from '@bean-lsp/shared';
22
import { $, execa } from 'execa';
3+
import { Connection, DidSaveTextDocumentParams } from 'vscode-languageserver';
4+
import { URI } from 'vscode-uri';
35
import {
46
Amount,
57
BeancountError,
68
BeancountFlag,
79
BeancountManagerFactory,
810
RealBeancountManager,
9-
} from 'src/common/features/types';
10-
import { Connection, DidSaveTextDocumentParams } from 'vscode-languageserver';
11-
import { URI } from 'vscode-uri';
11+
} from '../common/features/types';
1212
import { globalEventBus, GlobalEvents } from '../common/utils/event-bus';
1313

1414
interface AccountDetails {
Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,42 @@
1+
import { glob } from 'fast-glob';
2+
import { readFile } from 'fs/promises';
3+
import { DocumentStore } from 'src/common/document-store';
4+
import { pathToFileURL } from 'url';
15
import { createConnection, ProposedFeatures } from 'vscode-languageserver/node';
2-
6+
import { URI } from 'vscode-uri';
37
import { ServerOptions, startServer } from '../common/startServer';
48
import { beananagerFactory } from './beancount-manager';
59
import { factory } from './storage';
610

11+
class DocumentStoreInNode extends DocumentStore {
12+
protected override async fallbackListBeanFiles(workspaceFolder: { uri: string }): Promise<string[]> {
13+
const workspacePath = URI.parse(workspaceFolder.uri).fsPath;
14+
const files = await glob('**/*.{bean,beancount}', {
15+
cwd: workspacePath,
16+
ignore: ['.venv/**', '**/*.log', '**/*.tmp', '**/*.tmp.*', '**/*.tmp.*.*', '**/*.pyc'],
17+
absolute: true,
18+
});
19+
return files.map((p) => pathToFileURL(p).toString());
20+
}
21+
22+
protected override async fallbackFileRead(uri: string): Promise<ArrayBuffer> {
23+
const buffer = await readFile(new URL(uri));
24+
return new Uint8Array(buffer);
25+
}
26+
}
27+
728
// Create a connection for the server, using Node's IPC as a transport.
829
// Also include all preview / proposed LSP features.
930
const connection = createConnection(ProposedFeatures.all);
1031

1132
// Server options - will be populated by the initialization options in startServer
12-
const serverOptions: ServerOptions = {};
33+
const serverOptions: ServerOptions = {
34+
isBrowser: false,
35+
};
1336

37+
const documents = new DocumentStoreInNode(connection);
1438
// Start the server with the options
15-
startServer(connection, factory, beananagerFactory, serverOptions);
39+
startServer(connection, factory, documents, beananagerFactory, serverOptions);
1640

1741
// Listen on the connection
1842
connection.listen();

packages/lsp-server/tsup.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const nodeConfig = defineConfig({
3535
noExternal: [
3636
...commonConfig.noExternal,
3737
'execa',
38+
'fast-glob',
3839
],
3940
});
4041

0 commit comments

Comments
 (0)