diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 79fef95..0c1fb4c 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -209,9 +209,7 @@ "npmx-language-core": "workspace:*", "npmx-language-server": "workspace:*", "npmx-shared": "workspace:*", - "ocache": "catalog:inline", "reactive-vscode": "catalog:inline", - "semver": "catalog:inline", "vscode-ext-gen": "catalog:dev", "vscode-find-up": "catalog:inline" } diff --git a/extensions/vscode/src/client.ts b/extensions/vscode/src/client.ts index 1fd84cd..80c1971 100644 --- a/extensions/vscode/src/client.ts +++ b/extensions/vscode/src/client.ts @@ -1,11 +1,10 @@ import type { DocumentFilter } from '@volar/vscode' -import type { GetPackageManagerRequest } from 'npmx-shared/protocol' import { SUPPORTED_DOCUMENT_PATTERN } from '#utils/constants' import { middleware } from '@volar/vscode' import { LanguageClient, TransportKind } from '@volar/vscode/node' import { displayName, extensionId } from 'npmx-shared/meta' -import { GET_PACKAGE_MANAGER_METHOD } from 'npmx-shared/protocol' -import { commands, Hover, MarkdownString, Uri } from 'vscode' +import { Hover, MarkdownString } from 'vscode' +import { registerRequests } from './request' const SUPPORTED_LANGUAGES = [ 'javascript', @@ -74,17 +73,7 @@ export function launch(serverPath: string) { }, ) - client.onRequest( - GET_PACKAGE_MANAGER_METHOD, - async (params: GetPackageManagerRequest.ParamsType): Promise => { - try { - const result = await commands.executeCommand('npm.packageManager', Uri.parse(params.uri)) - return result || 'npm' - } catch { - return 'npm' - } - }, - ) + registerRequests(client) return { client, ready: client.start() } } diff --git a/extensions/vscode/src/composables/workspace-context.ts b/extensions/vscode/src/composables/workspace-context.ts deleted file mode 100644 index 3b57e53..0000000 --- a/extensions/vscode/src/composables/workspace-context.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { Uri } from 'vscode' -import { getWorkspaceContext } from '#core/workspace' -import { logger } from '#state' -import { SUPPORTED_DOCUMENT_PATTERN } from '#utils/constants' -import { PACKAGE_JSON_BASENAME } from 'npmx-language-core/constants' -import { isDependencyFile, isWorkspaceFile } from 'npmx-language-core/utils' -import { useDisposable, useFileSystemWatcher } from 'reactive-vscode' -import { window, workspace } from 'vscode' - -export function useWorkspaceContext() { - useDisposable(workspace.onDidChangeWorkspaceFolders(({ removed }) => { - removed.forEach((folder) => { - getWorkspaceContext.invalidate(folder.uri) - }) - })) - - async function deleteCacheByUri(uri: Uri, reload = true) { - if (!isDependencyFile(uri.path)) - return - - const ctx = await getWorkspaceContext(uri) - if (!ctx) - return - - await ctx.invalidateDependencyInfo(uri.path) - logger.info(`[workspace-context] invalidate dependencies cache: ${uri.path}`) - if (reload) { - const isRoot = uri.path === `${ctx.rootPath}/${PACKAGE_JSON_BASENAME}` - if (isRoot || isWorkspaceFile(uri.path)) - await ctx.loadWorkspace() - } - } - - useDisposable(workspace.onDidChangeTextDocument(({ document }) => { - const activeEditor = window.activeTextEditor - if ( - !activeEditor - || document !== activeEditor.document - || document.version === activeEditor.document.version - ) { - return - } - - deleteCacheByUri(document.uri, false) - })) - - const { onDidCreate, onDidChange, onDidDelete } = useFileSystemWatcher(SUPPORTED_DOCUMENT_PATTERN) - - onDidCreate(deleteCacheByUri) - onDidChange(deleteCacheByUri) - onDidDelete(deleteCacheByUri) -} diff --git a/extensions/vscode/src/core/workspace.ts b/extensions/vscode/src/core/workspace.ts deleted file mode 100644 index 07c9bc9..0000000 --- a/extensions/vscode/src/core/workspace.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { DependencyInfo, WorkspaceAdapter } from 'npmx-language-core/workspace' -import type { Uri } from 'vscode' -import { logger } from '#state' -import { isOffsetInRange } from '#utils/ast' -import { getDocumentText } from '#utils/file' -import { isPackageManifest } from 'npmx-language-core/utils' -import { WorkspaceContext } from 'npmx-language-core/workspace' -import { defineCachedFunction } from 'ocache' -import { commands, window, workspace } from 'vscode' -import { accessOk } from 'vscode-find-up' - -function createVscodeAdapter(baseUri: Uri): WorkspaceAdapter { - const toUri = (path: string) => baseUri.with({ path }) - - return { - async readFile(path: string): Promise { - return getDocumentText(toUri(path)) - }, - - async fileExists(path: string): Promise { - return accessOk(toUri(path)) - }, - - async detectPackageManager(rootPath: string): Promise<'npm' | 'pnpm' | 'yarn'> { - try { - const result = await commands.executeCommand<'npm' | 'pnpm' | 'yarn'>('npm.packageManager', toUri(rootPath)) - return result || 'npm' - } catch (error) { - logger.error('Error getting package manager:', error) - window.showErrorMessage('Failed to detect package manager. Defaulting to npm.') - return 'npm' - } - }, - } -} - -export const getWorkspaceContext = defineCachedFunction< - WorkspaceContext | undefined, - [Uri] ->(async (uri) => { - const folder = workspace.getWorkspaceFolder(uri) - if (!folder) - return - - logger.info(`[workspace-context] built ${folder.uri.path}`) - return await WorkspaceContext.create(folder.uri.path, createVscodeAdapter(folder.uri)) -}, { - name: 'workspace-context', - getKey: (uri) => workspace.getWorkspaceFolder(uri)?.uri.path ?? '', - swr: false, - maxAge: 0, - staleMaxAge: 0, -}) - -export async function getResolvedDependencies(uri: Uri): Promise { - const ctx = await getWorkspaceContext(uri) - if (!ctx) - return - - return ( - isPackageManifest(uri.path) - ? await ctx.loadPackageManifestInfo(uri.path) - : await ctx.loadWorkspaceFileInfo(uri.path) - )?.dependencies -} - -export async function getResolvedDependencyByOffset(uri: Uri, offset: number): Promise { - const dependencies = await getResolvedDependencies(uri) - - return dependencies?.find((dependency) => isOffsetInRange(offset, dependency.nameRange) || isOffsetInRange(offset, dependency.specRange)) -} diff --git a/extensions/vscode/src/index.ts b/extensions/vscode/src/index.ts index 9f3bc01..ab4273f 100644 --- a/extensions/vscode/src/index.ts +++ b/extensions/vscode/src/index.ts @@ -1,4 +1,3 @@ -import { useWorkspaceContext } from '#composables/workspace-context' import { createLabsInfo } from '@volar/vscode' import { ADD_TO_IGNORE_COMMAND } from 'npmx-shared/commands' import { commands, displayName, version } from 'npmx-shared/meta' @@ -18,9 +17,7 @@ export const { activate, deactivate } = defineExtension((ctx) => { const { client } = launch(serverPath) volarLabs.addLanguageClient(client) - useWorkspaceContext() - - useDecorators() + useDecorators(client) useCommand(ADD_TO_IGNORE_COMMAND, addToIgnore) diff --git a/extensions/vscode/src/providers/decorators.ts b/extensions/vscode/src/providers/decorators.ts index 4390f03..94ce10a 100644 --- a/extensions/vscode/src/providers/decorators.ts +++ b/extensions/vscode/src/providers/decorators.ts @@ -1,12 +1,13 @@ +import type { BaseLanguageClient } from '@volar/vscode' import type { DecorationOptions } from 'vscode' -import { getResolvedDependencies } from '#core/workspace' import { logger } from '#state' import { offsetRangeToRange } from '#utils/ast' +import { getResolvedDependencies } from '#utils/request' import { isPackageManifest } from 'npmx-language-core/utils' import { useActiveTextEditor, useEditorDecorations, watch } from 'reactive-vscode' import { Range } from 'vscode' -export function useDecorators() { +export function useDecorators(client: BaseLanguageClient) { const activeEditor = useActiveTextEditor() const { update } = useEditorDecorations( @@ -20,7 +21,7 @@ export function useDecorators() { return [] logger.info(`[decorators] updating ${document.uri.path}`) - const dependencies = await getResolvedDependencies(document.uri) + const dependencies = await getResolvedDependencies(client, document.uri) if (!dependencies) return [] diff --git a/extensions/vscode/src/request.ts b/extensions/vscode/src/request.ts new file mode 100644 index 0000000..561828a --- /dev/null +++ b/extensions/vscode/src/request.ts @@ -0,0 +1,25 @@ +import type { BaseLanguageClient } from '@volar/vscode' +import type { GetPackageManagerRequest } from 'npmx-shared/protocol' +import { RequestType } from '@volar/vscode' +import { GET_PACKAGE_MANAGER_METHOD } from 'npmx-shared/protocol' +import { commands, Uri } from 'vscode' + +const getPackageManagerRequestType = new RequestType< + GetPackageManagerRequest.ParamsType, + GetPackageManagerRequest.ResponseType, + GetPackageManagerRequest.ErrorType +>(GET_PACKAGE_MANAGER_METHOD) + +export function registerRequests(client: BaseLanguageClient) { + client.onRequest( + getPackageManagerRequestType, + async (params): Promise => { + try { + const result = await commands.executeCommand('npm.packageManager', Uri.parse(params.uri)) + return result || 'npm' + } catch { + return 'npm' + } + }, + ) +} diff --git a/extensions/vscode/src/utils/request.ts b/extensions/vscode/src/utils/request.ts new file mode 100644 index 0000000..67eefc4 --- /dev/null +++ b/extensions/vscode/src/utils/request.ts @@ -0,0 +1,21 @@ +import type { BaseLanguageClient } from '@volar/vscode' +import type { GetResolvedDependenciesRequest } from 'npmx-shared/protocol' +import type { Uri } from 'vscode' +import { logger } from '#state' +import { RequestType } from '@volar/vscode' +import { GET_RESOLVED_DEPENDENCIES_METHOD } from 'npmx-shared/protocol' + +const getResolvedDependenciesRequestType = new RequestType< + GetResolvedDependenciesRequest.ParamsType, + GetResolvedDependenciesRequest.ResponseType, + GetResolvedDependenciesRequest.ErrorType +>(GET_RESOLVED_DEPENDENCIES_METHOD) + +export async function getResolvedDependencies(client: BaseLanguageClient, uri: Uri) { + try { + return client.sendRequest(getResolvedDependenciesRequestType, { uri: uri.toString() }) + } catch (err) { + logger.error(`Failed to resolve dependencies for ${uri.toString()}: ${String(err)}`) + return [] + } +} diff --git a/extensions/vscode/tsconfig.json b/extensions/vscode/tsconfig.json index 210d4da..9940e36 100644 --- a/extensions/vscode/tsconfig.json +++ b/extensions/vscode/tsconfig.json @@ -6,9 +6,7 @@ "#state": ["./src/state.ts"], "#api/*": ["./src/api/*"], "#types/*": ["./src/types/*"], - "#utils/*": ["./src/utils/*"], - "#core/*": ["./src/core/*"], - "#composables/*": ["./src/composables/*"] + "#utils/*": ["./src/utils/*"] } }, "include": [ diff --git a/extensions/vscode/tsdown.config.ts b/extensions/vscode/tsdown.config.ts index 0ef9aa3..a34674d 100644 --- a/extensions/vscode/tsdown.config.ts +++ b/extensions/vscode/tsdown.config.ts @@ -15,8 +15,6 @@ export default defineConfig({ 'balanced-match', 'brace-expansion', 'minimatch', - 'ocache', - 'ohash', 'path-browserify', 'semver', 'typescript', diff --git a/packages/language-server/src/request.ts b/packages/language-server/src/request.ts new file mode 100644 index 0000000..f7f3af9 --- /dev/null +++ b/packages/language-server/src/request.ts @@ -0,0 +1,18 @@ +import type { Connection } from '@volar/language-server' +import type { GetResolvedDependenciesRequest } from 'npmx-shared/protocol' +import type { WorkspaceState } from './workspace' +import { RequestType } from '@volar/language-server' +import { GET_RESOLVED_DEPENDENCIES_METHOD } from 'npmx-shared/protocol' + +const getResolvedDependenciesRequestType = new RequestType< + GetResolvedDependenciesRequest.ParamsType, + GetResolvedDependenciesRequest.ResponseType, + GetResolvedDependenciesRequest.ErrorType +>(GET_RESOLVED_DEPENDENCIES_METHOD) + +export function registerRequests(connection: Connection, workspaceState: WorkspaceState) { + connection.onRequest( + getResolvedDependenciesRequestType, + async (params): Promise => workspaceState.getResolvedDependencies(params.uri), + ) +} diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 5e8d636..f04fc77 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -1,6 +1,7 @@ import { createConnection, createServer, createSimpleProject } from '@volar/language-server/node' import { createNpmxLanguageServicePlugins } from 'npmx-language-service' import { name, version } from '../package.json' with { type: 'json' } +import { registerRequests } from './request' import { createWorkspaceState } from './workspace' export function startServer() { @@ -28,4 +29,6 @@ export function startServer() { server.initialized() }) connection.onShutdown(server.shutdown) + + registerRequests(connection, workspaceState) } diff --git a/packages/shared/src/protocol.ts b/packages/shared/src/protocol.ts index 59861fa..8aa224f 100644 --- a/packages/shared/src/protocol.ts +++ b/packages/shared/src/protocol.ts @@ -1,11 +1,23 @@ /* eslint-disable ts/no-namespace */ +import type { DependencyInfo, PackageManager } from 'npmx-language-core/workspace' + export const GET_PACKAGE_MANAGER_METHOD = 'npmx/getPackageManager' export namespace GetPackageManagerRequest { export interface ParamsType { uri: string } - export type ResponseType = 'npm' | 'pnpm' | 'yarn' + export type ResponseType = PackageManager + export type ErrorType = never +} + +export const GET_RESOLVED_DEPENDENCIES_METHOD = 'npmx/getResolvedDependencies' + +export namespace GetResolvedDependenciesRequest { + export interface ParamsType { + uri: string + } + export type ResponseType = DependencyInfo[] | undefined export type ErrorType = never } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cae559..dda93dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,15 +158,9 @@ importers: npmx-shared: specifier: workspace:* version: link:../../packages/shared - ocache: - specifier: catalog:inline - version: 0.1.4 reactive-vscode: specifier: catalog:inline version: 1.0.0-beta.2(@types/vscode@1.101.0) - semver: - specifier: catalog:inline - version: 7.7.4 vscode-ext-gen: specifier: catalog:dev version: 1.6.0