Skip to content

Commit 16f5515

Browse files
committed
wip
1 parent 1910f85 commit 16f5515

5 files changed

Lines changed: 132 additions & 59 deletions

File tree

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"args": [
7474
"--extensionDevelopmentPath=${workspaceFolder}/packages/vscode",
7575
"--profile-temp",
76-
"--disable-extension=vscode.css-language-features",
76+
// "--disable-extension=vscode.css-language-features",
7777
"--skip-welcome",
7878
"--folder-uri=${workspaceFolder}/examples/1-basic",
7979
"${workspaceFolder}/examples/1-basic/src/a.tsx"

packages/ts-plugin/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@
2323
"access": "public",
2424
"registry": "https://registry.npmjs.org/"
2525
},
26+
"exports": {
27+
".": {
28+
"types": "./dist/index.d.ts",
29+
"default": "./dist/index.js"
30+
},
31+
"./type": {
32+
"types": "./dist/type.d.ts",
33+
"default": "./dist/type.js"
34+
}
35+
},
2636
"keywords": [
2737
"css-modules",
2838
"typescript",

packages/ts-plugin/src/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ const plugin = createLanguageServicePlugin((ts, info) => {
1111
return { languagePlugins: [] };
1212
}
1313

14+
if (!info.session) {
15+
info.project.projectService.logger.info('[@css-modules-kit/ts-plugin] info: Session is not available');
16+
return { languagePlugins: [] };
17+
}
18+
1419
let config: CMKConfig;
1520
try {
1621
config = readConfigFile(info.project.getProjectName());
@@ -50,6 +55,17 @@ const plugin = createLanguageServicePlugin((ts, info) => {
5055
const resolver = createResolver(config.compilerOptions, moduleResolutionCache);
5156
const matchesPattern = createMatchesPattern(config);
5257

58+
info.session.addProtocolHandler('_css-modules-kit:rename', (request) => {
59+
const { fileName, position } = request.arguments;
60+
const result = info.languageService.findRenameLocations(fileName, position, false, false, {});
61+
return { response: { result } };
62+
});
63+
info.session.addProtocolHandler('_css-modules-kit:renameInfo', (request) => {
64+
const { fileName, position } = request.arguments;
65+
const result = info.languageService.getRenameInfo(fileName, position, {});
66+
return { response: { result } };
67+
});
68+
5369
return {
5470
languagePlugins: [createCSSLanguagePlugin(resolver, matchesPattern, config)],
5571
setup: (language) => {

packages/ts-plugin/src/type.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type ts from 'typescript';
2+
3+
export interface CSSModulesKitRenameRequest extends ts.server.protocol.Request {
4+
command: '_css-modules-kit:rename';
5+
arguments: {
6+
fileName: string;
7+
position: number;
8+
};
9+
}
10+
export interface CSSModulesKitRenameHandlerResponse extends ts.server.HandlerResponse {
11+
response: { result: ReturnType<ts.LanguageService['findRenameLocations']> };
12+
}
13+
export interface CSSModulesKitRenameResponse extends ts.server.protocol.Response {
14+
command: '_css-modules-kit:rename';
15+
readonly body: CSSModulesKitRenameHandlerResponse['response'];
16+
}
17+
18+
export interface CSSModulesKitRenameInfoRequest extends ts.server.protocol.Request {
19+
command: '_css-modules-kit:renameInfo';
20+
arguments: {
21+
fileName: string;
22+
position: number;
23+
};
24+
}
25+
export interface CSSModulesKitRenameInfoHandlerResponse extends ts.server.HandlerResponse {
26+
response: { result: ReturnType<ts.LanguageService['getRenameInfo']> };
27+
}
28+
export interface CSSModulesKitRenameInfoResponse extends ts.server.protocol.Response {
29+
command: '_css-modules-kit:renameInfo';
30+
readonly body: CSSModulesKitRenameInfoHandlerResponse['response'];
31+
}
32+
33+
declare module 'typescript' {
34+
// eslint-disable-next-line @typescript-eslint/no-namespace
35+
namespace server {
36+
export interface Session {
37+
addProtocolHandler(
38+
command: '_css-modules-kit:rename',
39+
handler: (request: CSSModulesKitRenameRequest) => CSSModulesKitRenameHandlerResponse,
40+
): void;
41+
addProtocolHandler(
42+
command: '_css-modules-kit:renameInfo',
43+
handler: (request: CSSModulesKitRenameInfoRequest) => CSSModulesKitRenameInfoHandlerResponse,
44+
): void;
45+
}
46+
}
47+
}

packages/vscode/src/index.ts

Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,73 @@
11
/* eslint-disable no-console */
22

3+
import type {
4+
CSSModulesKitRenameInfoRequest,
5+
CSSModulesKitRenameInfoResponse,
6+
CSSModulesKitRenameRequest,
7+
CSSModulesKitRenameResponse,
8+
} from '@css-modules-kit/ts-plugin/type';
39
import * as vscode from 'vscode';
4-
import * as lsp from 'vscode-languageclient/node';
510

6-
let client: lsp.BaseLanguageClient;
7-
8-
export async function activate(_context: vscode.ExtensionContext) {
11+
export async function activate(context: vscode.ExtensionContext) {
912
console.log('[css-modules-kit-vscode] Activated');
1013

1114
// By default, `vscode.typescript-language-features` is not activated when a user opens *.css in VS Code.
1215
// So, activate it manually.
1316
const tsExtension = vscode.extensions.getExtension('vscode.typescript-language-features');
14-
if (tsExtension) {
17+
if (tsExtension && !tsExtension.isActive) {
1518
console.log('[css-modules-kit-vscode] Activating `vscode.typescript-language-features`');
16-
tsExtension.activate();
19+
await tsExtension.activate();
1720
}
1821

19-
// Both vscode.css-language-features extension and tsserver receive "rename" requests for *.css.
20-
// If more than one Provider receives a "rename" request, VS Code will use one of them.
21-
// In this case, the extension is used to rename. However, we do not want this.
22-
// Without rename in tsserver, we cannot rename class selectors across *.css and *.ts.
23-
//
24-
// Also, VS Code seems to send "references" requests to both vscode.css-language-features extension
25-
// and tsserver and merge the results of both. Thus, when a user executes "Find all references"
26-
// on a class selector, the same class selector appears twice.
27-
//
28-
// To avoid this, we recommend disabling vscode.css-language-features extension. Disabling extensions is optional.
29-
// If not disabled, "rename" and "references" will behave in a way the user does not want.
30-
const cssExtension = vscode.extensions.getExtension('vscode.css-language-features');
31-
if (cssExtension) {
32-
// Temporarily commented out
33-
// vscode.window
34-
// .showInformationMessage(
35-
// '"Rename Symbol" and "Find All References" do not work in some cases because the "CSS Language Features" extension is enabled. Disabling the extension will make them work.',
36-
// 'Show "CSS Language Features" extension',
37-
// )
38-
// .then((selected) => {
39-
// if (selected) {
40-
// vscode.commands.executeCommand('workbench.extensions.search', '@builtin css-language-features');
41-
// }
42-
// });
43-
} else {
44-
// If vscode.css-language-features extension is disabled, start the customized language server for *.css, *.scss, and *.less.
45-
// The language server is based on the vscode-css-languageservice, but "rename" and "references" features are disabled.
46-
47-
// TODO: Do not use Node.js API
48-
const serverModulePath = require.resolve('@css-modules-kit/language-server');
49-
50-
const serverOptions: lsp.ServerOptions = {
51-
run: {
52-
module: serverModulePath,
53-
transport: lsp.TransportKind.ipc,
54-
options: { execArgv: [] },
22+
context.subscriptions.push(
23+
vscode.languages.registerRenameProvider(
24+
{ scheme: 'file', language: 'css' },
25+
{
26+
async provideRenameEdits(document, position, newName, _token) {
27+
const res = await vscode.commands.executeCommand<CSSModulesKitRenameResponse>(
28+
'typescript.tsserverRequest',
29+
'_css-modules-kit:rename',
30+
{
31+
fileName: document.fileName,
32+
position: document.offsetAt(position),
33+
} satisfies CSSModulesKitRenameRequest['arguments'],
34+
);
35+
if (!res.success || !res.body.result) return;
36+
const edit = new vscode.WorkspaceEdit();
37+
for (const location of res.body.result) {
38+
// eslint-disable-next-line no-await-in-loop
39+
const document = await vscode.workspace.openTextDocument(location.fileName);
40+
const start = document.positionAt(location.textSpan.start);
41+
const end = document.positionAt(location.textSpan.start + location.textSpan.length);
42+
edit.replace(vscode.Uri.file(location.fileName), new vscode.Range(start, end), newName);
43+
}
44+
return edit;
45+
},
46+
async prepareRename(document, position, _token) {
47+
const res = await vscode.commands.executeCommand<CSSModulesKitRenameInfoResponse>(
48+
'typescript.tsserverRequest',
49+
'_css-modules-kit:renameInfo',
50+
{
51+
fileName: document.fileName,
52+
position: document.offsetAt(position),
53+
} satisfies CSSModulesKitRenameInfoRequest['arguments'],
54+
);
55+
if (!res.success || !res.body.result.canRename) return;
56+
return new vscode.Range(
57+
document.positionAt(res.body.result.triggerSpan.start),
58+
document.positionAt(res.body.result.triggerSpan.start + res.body.result.triggerSpan.length),
59+
);
60+
},
5561
},
56-
debug: {
57-
module: serverModulePath,
58-
transport: lsp.TransportKind.ipc,
59-
options: { execArgv: ['--nolazy', `--inspect=${6009}`] },
62+
),
63+
vscode.languages.registerDocumentLinkProvider(
64+
{ scheme: 'file', language: 'css' },
65+
{
66+
provideDocumentLinks(document, _token) {
67+
// TODO
68+
return [];
69+
},
6070
},
61-
};
62-
const clientOptions: lsp.LanguageClientOptions = {
63-
documentSelector: [{ language: 'css' }, { language: 'scss' }, { language: 'less' }],
64-
initializationOptions: {},
65-
};
66-
client = new lsp.LanguageClient('css-modules-kit-vscode', 'css-modules-kit-vscode', serverOptions, clientOptions);
67-
await client.start();
68-
}
69-
}
70-
71-
export function deactivate(): Thenable<unknown> | undefined {
72-
return client?.stop();
71+
),
72+
);
7373
}

0 commit comments

Comments
 (0)