coc-vscode-loader 1.1.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/converter/src/cli.ts +33 -2
- package/converter/src/convert.ts +294 -361
- package/converter/src/scanner.ts +5 -89
- package/converter/src/steps/bridge.ts +142 -0
- package/converter/src/steps/index.ts +29 -0
- package/converter/src/steps/language-client.ts +150 -0
- package/converter/src/steps/mark-unsupported.ts +81 -0
- package/converter/src/steps/source.ts +159 -0
- package/converter/src/transforms/class-to-factory.ts +1 -2
- package/converter/src/transforms/enum-offset.ts +0 -17
- package/converter/src/transforms/provider-register.ts +7 -1
- package/converter/src/transforms/strip-volar.ts +29 -0
- package/converter/src/types.ts +117 -0
- package/lib/index.js +49 -60
- package/package.json +4 -3
- package/converter/README.md +0 -134
- package/converter/package-lock.json +0 -692
- package/converter/pnpm-lock.yaml +0 -419
|
@@ -39,8 +39,7 @@ export const transformClassToFactory: Transform = (ctx) => {
|
|
|
39
39
|
(match, type) => `${type}.create(`
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
// CompletionItem.create(label)
|
|
43
|
-
// Convert `CompletionItem.create(label, kind)` to `item = CompletionItem.create(label); item.kind = kind`
|
|
42
|
+
// CompletionItem.create(label, kind) → item = CompletionItem.create(label); item.kind = kind
|
|
44
43
|
text = text.replace(
|
|
45
44
|
/const\s+(\w+)\s*=\s*CompletionItem\.create\(([^,]+),\s*([^)]+)\)/g,
|
|
46
45
|
(_, varName, label, kind) => {
|
|
@@ -19,23 +19,6 @@ export const transformEnumOffset: Transform = (ctx) => {
|
|
|
19
19
|
const { file } = ctx
|
|
20
20
|
let content = file.getText()
|
|
21
21
|
|
|
22
|
-
// Detect hardcoded numbers used in enum position (e.g., CompletionItemKind.Xxx).
|
|
23
|
-
// This is hard to detect perfectly, so we log a note when numeric literals
|
|
24
|
-
// appear near enum-type names.
|
|
25
|
-
const enumPatterns = [
|
|
26
|
-
'CompletionItemKind', 'SymbolKind', 'DocumentHighlightKind', 'DiagnosticSeverity',
|
|
27
|
-
'CompletionTriggerKind', 'InlineCompletionTriggerKind',
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
for (const enumName of enumPatterns) {
|
|
31
|
-
// Check if the enum is imported/used with a hardcoded number nearby
|
|
32
|
-
const enumRefs = content.match(new RegExp(`${enumName}\\.\\w+`, 'g'))
|
|
33
|
-
if (enumRefs) {
|
|
34
|
-
// Symbol references are fine - they resolve at runtime
|
|
35
|
-
// Only note if there are raw numbers being compared
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
22
|
// Replace any numeric enum comparisons with comments
|
|
40
23
|
// e.g., `severity === 0` → `severity === 0 /* DiagnosticSeverity.Error = 1 in coc */`
|
|
41
24
|
content = content.replace(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from 'path'
|
|
1
2
|
import { Transform } from '../types.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -32,10 +33,15 @@ export const transformProviderRegister: Transform = (ctx) => {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
// 2. registerCompletionItemProvider: insert name + shortcut at beginning
|
|
36
|
+
// Shortcut derived from source file name (first 2 uppercase letters)
|
|
37
|
+
const fileName = file.getFilePath() || 'plugin'
|
|
38
|
+
const baseName = path.basename(fileName, '.ts')
|
|
39
|
+
const shortcut = baseName.replace(/[^a-zA-Z]/g, '').substring(0, 2).toUpperCase() || 'PL'
|
|
35
40
|
if (content.includes('registerCompletionItemProvider')) {
|
|
41
|
+
const pluginName = path.basename(path.dirname(path.dirname(fileName))) || 'plugin'
|
|
36
42
|
content = content.replace(
|
|
37
43
|
/registerCompletionItemProvider\(/g,
|
|
38
|
-
`registerCompletionItemProvider('
|
|
44
|
+
`registerCompletionItemProvider('${pluginName}', '${shortcut}', `
|
|
39
45
|
)
|
|
40
46
|
// Wrap the last argument in an array if it's a string (trigger chars)
|
|
41
47
|
content = content.replace(
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Transform } from '../types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Remove Volar-specific framework imports that are not compatible with coc.nvim.
|
|
5
|
+
* The Volar extension uses @volar/vscode and reactive-vscode meta-framework,
|
|
6
|
+
* which need to be stripped since the bridge step provides alternative setup.
|
|
7
|
+
*/
|
|
8
|
+
export const transformStripVolar: Transform = (ctx) => {
|
|
9
|
+
const { file } = ctx
|
|
10
|
+
let content = file.getText()
|
|
11
|
+
let changed = false
|
|
12
|
+
|
|
13
|
+
const patterns = [
|
|
14
|
+
/import .* from ['"]@volar\/vscode['"];?\n?/g,
|
|
15
|
+
/import .* from ['"]reactive-vscode['"];?\n?/g,
|
|
16
|
+
/import \* as lsp from ['"]@volar\/vscode\/node['"];?\n?/g,
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
for (const re of patterns) {
|
|
20
|
+
if (re.test(content)) {
|
|
21
|
+
content = content.replace(re, '')
|
|
22
|
+
changed = true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (changed) {
|
|
27
|
+
file.replaceWithText(content)
|
|
28
|
+
}
|
|
29
|
+
}
|
package/converter/src/types.ts
CHANGED
|
@@ -6,3 +6,120 @@ export interface TransformContext {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export type Transform = (ctx: TransformContext) => void
|
|
9
|
+
|
|
10
|
+
// ---- Step type definitions ----
|
|
11
|
+
|
|
12
|
+
export interface ServerModuleConfig {
|
|
13
|
+
kind: 'module'
|
|
14
|
+
package: string
|
|
15
|
+
entry?: 'main' | 'bin'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ServerBinaryConfig {
|
|
19
|
+
kind: 'binary'
|
|
20
|
+
package: string
|
|
21
|
+
binary: {
|
|
22
|
+
repo: string
|
|
23
|
+
asset: string
|
|
24
|
+
binaryPath?: string
|
|
25
|
+
}
|
|
26
|
+
args?: string[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ServerConfig = ServerModuleConfig | ServerBinaryConfig
|
|
30
|
+
|
|
31
|
+
export interface LanguageClientStep {
|
|
32
|
+
type: 'language-client'
|
|
33
|
+
id?: string
|
|
34
|
+
server: ServerConfig
|
|
35
|
+
transport?: 'ipc' | 'stdio'
|
|
36
|
+
languages: string[]
|
|
37
|
+
multiRoot?: boolean
|
|
38
|
+
/** Enable debug logging in generated code */
|
|
39
|
+
verbose?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SourceStep {
|
|
43
|
+
type: 'source'
|
|
44
|
+
transforms: string[]
|
|
45
|
+
entry?: string
|
|
46
|
+
keepDeps?: string[] | Record<string, string>
|
|
47
|
+
activationEvents?: string[]
|
|
48
|
+
/** Enable debug logging in generated/transformed code */
|
|
49
|
+
verbose?: boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface BridgeStep {
|
|
53
|
+
type: 'bridge'
|
|
54
|
+
preset?: string
|
|
55
|
+
/** Override preset options (extensions, services, etc.) */
|
|
56
|
+
options?: {
|
|
57
|
+
extensions?: string[]
|
|
58
|
+
services?: string[]
|
|
59
|
+
}
|
|
60
|
+
/** Enable debug logging in generated bridge code */
|
|
61
|
+
verbose?: boolean
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface MarkUnsupportedStep {
|
|
65
|
+
type: 'mark-unsupported'
|
|
66
|
+
features: string[]
|
|
67
|
+
/** Enable detailed output during conversion */
|
|
68
|
+
verbose?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type ConvertStep = LanguageClientStep | SourceStep | BridgeStep | MarkUnsupportedStep
|
|
72
|
+
|
|
73
|
+
// ---- Step execution ----
|
|
74
|
+
|
|
75
|
+
export interface StepResult {
|
|
76
|
+
generatedFiles: Array<{ path: string; content: string }>
|
|
77
|
+
entryPoint?: string
|
|
78
|
+
keepDeps: Record<string, string>
|
|
79
|
+
activationEvents: string[]
|
|
80
|
+
serverBinary?: {
|
|
81
|
+
repo: string
|
|
82
|
+
asset: string
|
|
83
|
+
binaryPath?: string
|
|
84
|
+
args?: string[]
|
|
85
|
+
}
|
|
86
|
+
/** Code to inject into previously generated files (target path, code to insert, insertion point) */
|
|
87
|
+
codeInjections?: Array<{
|
|
88
|
+
target: string // file to modify (e.g. 'src/index.ts')
|
|
89
|
+
importCode?: string // import line to add at top
|
|
90
|
+
insertBefore?: string // regex pattern to insert code before
|
|
91
|
+
insertAfter?: string // regex pattern to insert code after
|
|
92
|
+
code: string // code to insert
|
|
93
|
+
}>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface StepContext {
|
|
97
|
+
input: string
|
|
98
|
+
output: string
|
|
99
|
+
project: Project
|
|
100
|
+
origPkg: Record<string, any>
|
|
101
|
+
verbose?: boolean
|
|
102
|
+
/** Preset definitions from registry (e.g. bridge presets) */
|
|
103
|
+
presets?: Record<string, any>
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface StepGenerator {
|
|
107
|
+
type: string
|
|
108
|
+
generate(ctx: StepContext, step: ConvertStep): StepResult
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isLanguageClientStep(s: ConvertStep): s is LanguageClientStep {
|
|
112
|
+
return s.type === 'language-client'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isSourceStep(s: ConvertStep): s is SourceStep {
|
|
116
|
+
return s.type === 'source'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function isBridgeStep(s: ConvertStep): s is BridgeStep {
|
|
120
|
+
return s.type === 'bridge'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function isMarkUnsupportedStep(s: ConvertStep): s is MarkUnsupportedStep {
|
|
124
|
+
return s.type === 'mark-unsupported'
|
|
125
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -39,7 +39,7 @@ var require_package = __commonJS({
|
|
|
39
39
|
"package.json"(exports2, module2) {
|
|
40
40
|
module2.exports = {
|
|
41
41
|
name: "coc-vscode-loader",
|
|
42
|
-
version: "1.
|
|
42
|
+
version: "1.2.0",
|
|
43
43
|
description: "Run VS Code extensions seamlessly in coc.nvim",
|
|
44
44
|
main: "lib/index.js",
|
|
45
45
|
keywords: [
|
|
@@ -60,7 +60,8 @@ var require_package = __commonJS({
|
|
|
60
60
|
homepage: "https://www.npmjs.com/package/coc-vscode-loader",
|
|
61
61
|
files: [
|
|
62
62
|
"lib/",
|
|
63
|
-
"converter/",
|
|
63
|
+
"converter/src/",
|
|
64
|
+
"converter/package.json",
|
|
64
65
|
"assets/"
|
|
65
66
|
],
|
|
66
67
|
license: "MIT",
|
|
@@ -68,7 +69,7 @@ var require_package = __commonJS({
|
|
|
68
69
|
coc: ">= 0.0.80"
|
|
69
70
|
},
|
|
70
71
|
scripts: {
|
|
71
|
-
"bundle-converter": "if [ -d ../converter ]; then rm -rf converter && cp -r ../converter ./converter && cd converter && npm install --legacy-peer-deps 2>/dev/null
|
|
72
|
+
"bundle-converter": "if [ -d ../converter ]; then rm -rf converter && cp -r ../converter ./converter && cd converter && npm install --legacy-peer-deps 2>/dev/null; fi",
|
|
72
73
|
build: "npm run bundle-converter && node esbuild.mjs",
|
|
73
74
|
prepare: "npm run bundle-converter && node esbuild.mjs"
|
|
74
75
|
},
|
|
@@ -158,9 +159,13 @@ async function fetchRegistryJSON(url) {
|
|
|
158
159
|
(0, import_child_process.execFile)("curl", ["-sL", url], { encoding: "utf-8", maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
|
|
159
160
|
if (err) reject(new Error(`curl failed: ${err.message}`));
|
|
160
161
|
else {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
try {
|
|
163
|
+
const data = JSON.parse(stdout);
|
|
164
|
+
if (!Array.isArray(data)) reject(new Error("Invalid registry format"));
|
|
165
|
+
else resolve2(data);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
reject(new Error(`Invalid JSON from registry: ${e.message}`));
|
|
168
|
+
}
|
|
164
169
|
}
|
|
165
170
|
});
|
|
166
171
|
});
|
|
@@ -436,8 +441,10 @@ function pluginDir(name) {
|
|
|
436
441
|
function converterCliPath() {
|
|
437
442
|
const base = path3.resolve(__dirname, "..");
|
|
438
443
|
const candidates = [
|
|
439
|
-
|
|
440
|
-
path3.join(base, "..", "converter", "src", "cli.ts")
|
|
444
|
+
// In dev mode (symlink), the converter is at repo root
|
|
445
|
+
path3.join(base, "..", "converter", "src", "cli.ts"),
|
|
446
|
+
// In npm install, the converter is bundled inside the package
|
|
447
|
+
path3.join(base, "converter", "src", "cli.ts")
|
|
441
448
|
];
|
|
442
449
|
for (const p of candidates) {
|
|
443
450
|
if (fs3.existsSync(p)) return p;
|
|
@@ -449,7 +456,7 @@ function converterCliPath() {
|
|
|
449
456
|
var CMD_TIMEOUT = 3e5;
|
|
450
457
|
async function run(cmd, args, cwd, onLine) {
|
|
451
458
|
return new Promise((resolve2, reject) => {
|
|
452
|
-
const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell:
|
|
459
|
+
const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
453
460
|
const timer = setTimeout(() => {
|
|
454
461
|
child.kill("SIGTERM");
|
|
455
462
|
reject(new Error(`Timed out after ${CMD_TIMEOUT / 1e3}s: ${cmd} ${args.join(" ")}`));
|
|
@@ -491,13 +498,33 @@ async function downloadSource(info, name, onProgress) {
|
|
|
491
498
|
}
|
|
492
499
|
return info.source.subdir ? path3.join(srcDir, info.source.subdir) : srcDir;
|
|
493
500
|
}
|
|
494
|
-
async function convertSource(inputDir, name, onProgress) {
|
|
501
|
+
async function convertSource(inputDir, name, info, onProgress) {
|
|
495
502
|
const build = buildDir(name);
|
|
496
503
|
if (fs3.existsSync(build)) fs3.rmSync(build, { recursive: true });
|
|
497
504
|
const cli = converterCliPath();
|
|
505
|
+
const converterDir = path3.resolve(path3.dirname(path3.dirname(cli)));
|
|
506
|
+
if (!fs3.existsSync(path3.join(converterDir, "node_modules", "commander"))) {
|
|
507
|
+
onProgress(2, 5, "Installing converter dependencies...", "");
|
|
508
|
+
const log2 = (chunk) => onProgress(2, 5, chunk.trim(), "");
|
|
509
|
+
await run("npm", ["install", "--legacy-peer-deps", "--production"], converterDir, log2);
|
|
510
|
+
}
|
|
511
|
+
if (!info.convert || !Array.isArray(info.convert) || info.convert.length === 0) {
|
|
512
|
+
throw new Error(`Registry entry "${name}" has no "convert" config. Please update the registry.`);
|
|
513
|
+
}
|
|
514
|
+
const convertFile = path3.join(cacheDir(name), "convert-config.json");
|
|
515
|
+
fs3.mkdirSync(path3.dirname(convertFile), { recursive: true });
|
|
516
|
+
fs3.writeFileSync(convertFile, JSON.stringify(info.convert));
|
|
517
|
+
const presetsFile = path3.join(cacheDir(name), "presets-config.json");
|
|
518
|
+
const registryDir = path3.resolve(__dirname, "..", "..", "coc-vscode-registry");
|
|
519
|
+
const presetsPath = path3.join(registryDir, "presets.json");
|
|
520
|
+
if (fs3.existsSync(presetsPath)) {
|
|
521
|
+
fs3.writeFileSync(presetsFile, fs3.readFileSync(presetsPath));
|
|
522
|
+
}
|
|
523
|
+
const args = ["tsx", cli, "convert", inputDir, "-o", build, "--convert-file", convertFile];
|
|
524
|
+
if (fs3.existsSync(presetsFile)) args.push("--presets-file", presetsFile);
|
|
498
525
|
onProgress(2, 5, "Converting...", `converter convert ${inputDir} -o ${build}`);
|
|
499
526
|
const log = (chunk) => onProgress(2, 5, chunk.trim(), "");
|
|
500
|
-
await run("npx",
|
|
527
|
+
await run("npx", args, cacheDir(name), log);
|
|
501
528
|
}
|
|
502
529
|
async function buildPackage(name, inputDir, info, onProgress) {
|
|
503
530
|
const build = buildDir(name);
|
|
@@ -607,56 +634,18 @@ async function buildPackage(name, inputDir, info, onProgress) {
|
|
|
607
634
|
}
|
|
608
635
|
const indexPath = path3.join(build, "lib", "index.js");
|
|
609
636
|
if (fs3.existsSync(indexPath)) {
|
|
610
|
-
const binPath = (sb.binaryPath || sb.asset.split(/-?\{\{/)[0]).replace(/\{\{version}}/g, version).replace(/\{\{platform}}/g, platform).replace(/\{\{arch}}/g, arch2).replace(/\{\{raw-arch}}/g, rawArch).replace(/\{\{rust-target}}/g, rustTarget);
|
|
611
637
|
let code = fs3.readFileSync(indexPath, "utf-8");
|
|
612
|
-
|
|
613
|
-
code = code.replace(
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
);
|
|
617
|
-
const serverPath = `require('path').join(__dirname, '..', 'server', '${binPath}')`;
|
|
618
|
-
code = code.replace(
|
|
619
|
-
/try\s*\{[^}]*?require\.resolve\([^)]+\)\s*;?\s*\}\s*catch\s*\{\s*\}/g,
|
|
620
|
-
`try { serverModule = ${serverPath} } catch {}`
|
|
621
|
-
);
|
|
622
|
-
code = code.replace(
|
|
623
|
-
/let\s+serverModule\s*=\s*config\.get\([^)]+\)\s*;?\s*/g,
|
|
624
|
-
`let serverModule = ${serverPath};`
|
|
625
|
-
);
|
|
638
|
+
code = code.replace(/\{\{version}}/g, version);
|
|
639
|
+
code = code.replace(/\{\{platform}}/g, platform);
|
|
640
|
+
code = code.replace(/\{\{arch}}/g, arch2);
|
|
641
|
+
code = code.replace(/\{\{raw-arch}}/g, rawArch);
|
|
642
|
+
code = code.replace(/\{\{rust-target}}/g, rustTarget);
|
|
626
643
|
fs3.writeFileSync(indexPath, code);
|
|
627
644
|
}
|
|
628
645
|
} catch (e) {
|
|
629
646
|
onProgress(4, 5, `Warning: serverBinary setup failed (${e.message})`, "install server binary manually");
|
|
630
647
|
}
|
|
631
648
|
}
|
|
632
|
-
const docSelPath = path3.join(build, "lib", "index.js");
|
|
633
|
-
if (fs3.existsSync(docSelPath)) {
|
|
634
|
-
let code = fs3.readFileSync(docSelPath, "utf-8");
|
|
635
|
-
const langSelector = info.languages.map((l) => `{ scheme: "file", language: "${l}" }`).join(", ");
|
|
636
|
-
code = code.replace(
|
|
637
|
-
/documentSelector:\s*\[\s*\{[^}]*?language:\s*['"][^'"]*['"][^}]*\}\s*\]/,
|
|
638
|
-
`documentSelector: [${langSelector}]`
|
|
639
|
-
);
|
|
640
|
-
code = code.replace(
|
|
641
|
-
/client\.start\(\);/g,
|
|
642
|
-
"client.start().catch(() => {/* init may complete async */});"
|
|
643
|
-
);
|
|
644
|
-
fs3.writeFileSync(docSelPath, code);
|
|
645
|
-
}
|
|
646
|
-
const pkgPath = path3.join(build, "package.json");
|
|
647
|
-
if (fs3.existsSync(pkgPath)) {
|
|
648
|
-
try {
|
|
649
|
-
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
650
|
-
const events = pkg.activationEvents || [];
|
|
651
|
-
const langEvents = info.languages.map((l) => `onLanguage:${l}`);
|
|
652
|
-
const newEvents = events.filter((e) => !e.startsWith("onLanguage:")).concat(langEvents);
|
|
653
|
-
if (newEvents.length > 0 && JSON.stringify(newEvents) !== JSON.stringify(events)) {
|
|
654
|
-
pkg.activationEvents = newEvents;
|
|
655
|
-
fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
656
|
-
}
|
|
657
|
-
} catch {
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
649
|
}
|
|
661
650
|
function extensionsPkgPath() {
|
|
662
651
|
return path3.join(os3.homedir(), ".config", "coc", "extensions", "package.json");
|
|
@@ -669,7 +658,7 @@ async function installToCoc(name, onProgress) {
|
|
|
669
658
|
fs3.mkdirSync(path3.dirname(dest), { recursive: true });
|
|
670
659
|
fs3.cpSync(src, dest, { recursive: true });
|
|
671
660
|
const pkgPath = extensionsPkgPath();
|
|
672
|
-
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
661
|
+
const pkg = fs3.existsSync(pkgPath) ? JSON.parse(fs3.readFileSync(pkgPath, "utf-8")) : { dependencies: {} };
|
|
673
662
|
pkg.dependencies = pkg.dependencies || {};
|
|
674
663
|
const depName = `coc-${name}`;
|
|
675
664
|
if (!pkg.dependencies[depName]) {
|
|
@@ -707,7 +696,7 @@ async function installPackage(state, name) {
|
|
|
707
696
|
state.setPackageStatus(name, "installing", { progress: "Starting..." });
|
|
708
697
|
try {
|
|
709
698
|
const input = await downloadSource(info, name, prog);
|
|
710
|
-
await convertSource(input, name, prog);
|
|
699
|
+
await convertSource(input, name, info, prog);
|
|
711
700
|
await buildPackage(name, input, info, prog);
|
|
712
701
|
await installToCoc(name, prog);
|
|
713
702
|
await saveMeta(name);
|
|
@@ -779,13 +768,13 @@ async function updatePackage(state, name) {
|
|
|
779
768
|
state.setPackageStatus(name, "updating", { progress: "Starting..." });
|
|
780
769
|
try {
|
|
781
770
|
const input = await downloadSource(info, name, prog);
|
|
782
|
-
await convertSource(input, name, prog);
|
|
771
|
+
await convertSource(input, name, info, prog);
|
|
783
772
|
await buildPackage(name, input, info, prog);
|
|
784
773
|
await installToCoc(name, prog);
|
|
785
774
|
await saveMeta(name);
|
|
786
775
|
state.setDirty();
|
|
787
776
|
state.setPackageStatus(name, "installed");
|
|
788
|
-
import_coc.window.showInformationMessage(`coc-${name}
|
|
777
|
+
import_coc.window.showInformationMessage(`coc-${name} updated`);
|
|
789
778
|
try {
|
|
790
779
|
const meta = JSON.parse(fs3.readFileSync(metaPath(name), "utf-8"));
|
|
791
780
|
if (meta.commit) {
|
|
@@ -807,7 +796,7 @@ async function updatePackage(state, name) {
|
|
|
807
796
|
}
|
|
808
797
|
async function runWithOutput(cmd, args, cwd) {
|
|
809
798
|
return new Promise((resolve2, reject) => {
|
|
810
|
-
const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell:
|
|
799
|
+
const child = (0, import_child_process2.spawn)(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
811
800
|
const timer = setTimeout(() => {
|
|
812
801
|
child.kill("SIGTERM");
|
|
813
802
|
reject(new Error(`Timed out after ${CMD_TIMEOUT / 1e3}s: ${cmd} ${args.join(" ")}`));
|
|
@@ -1220,7 +1209,7 @@ var TUI = class {
|
|
|
1220
1209
|
return;
|
|
1221
1210
|
}
|
|
1222
1211
|
if (id === "X" && entry.status === "installed") {
|
|
1223
|
-
uninstallPackage(this.state, pkgName);
|
|
1212
|
+
await uninstallPackage(this.state, pkgName);
|
|
1224
1213
|
return;
|
|
1225
1214
|
}
|
|
1226
1215
|
if (id === "R" && entry.status === "installed") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coc-vscode-loader",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Run VS Code extensions seamlessly in coc.nvim",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"homepage": "https://www.npmjs.com/package/coc-vscode-loader",
|
|
22
22
|
"files": [
|
|
23
23
|
"lib/",
|
|
24
|
-
"converter/",
|
|
24
|
+
"converter/src/",
|
|
25
|
+
"converter/package.json",
|
|
25
26
|
"assets/"
|
|
26
27
|
],
|
|
27
28
|
"license": "MIT",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"coc": ">= 0.0.80"
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
|
32
|
-
"bundle-converter": "if [ -d ../converter ]; then rm -rf converter && cp -r ../converter ./converter && cd converter && npm install --legacy-peer-deps 2>/dev/null
|
|
33
|
+
"bundle-converter": "if [ -d ../converter ]; then rm -rf converter && cp -r ../converter ./converter && cd converter && npm install --legacy-peer-deps 2>/dev/null; fi",
|
|
33
34
|
"build": "npm run bundle-converter && node esbuild.mjs",
|
|
34
35
|
"prepare": "npm run bundle-converter && node esbuild.mjs"
|
|
35
36
|
},
|
package/converter/README.md
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# converter — vscode → coc converter prototype
|
|
2
|
-
|
|
3
|
-
CLI tool that automatically converts VS Code extensions to coc.nvim plugins.
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Convert a VS Code extension directory
|
|
9
|
-
npx tsx src/cli.ts convert ./vscode-ext/ -o ./coc-ext/
|
|
10
|
-
|
|
11
|
-
# Build and install to coc
|
|
12
|
-
cd ./coc-ext && npm install && npm run build
|
|
13
|
-
cd ~/.config/coc/extensions && npm install /path/to/coc-ext
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Verified conversions
|
|
17
|
-
|
|
18
|
-
| Plugin | Type | Auto-detected | Build | Working | Notes |
|
|
19
|
-
|--------|------|---------------|-------|---------|-------|
|
|
20
|
-
| Volar (Vue) | TS bridge | `@vue/language-server` + `typescript` | ✅ | ✅ | Requires modified coc-tsserver |
|
|
21
|
-
| Prisma | Pure LSP | `@prisma/language-server` | ✅ | ✅ | Auto-detects bin entry |
|
|
22
|
-
| HTML CSS Support | Direct API | — | ✅ | ✅ | Handles API differences |
|
|
23
|
-
|
|
24
|
-
### Plugin types
|
|
25
|
-
|
|
26
|
-
| Type | Description | Approach | Example |
|
|
27
|
-
|------|-------------|----------|---------|
|
|
28
|
-
| **TS bridge** | Language plugins depending on TypeScript LSP | Generate `tsserver/request` bridge + `typescriptServerPlugins` | Volar |
|
|
29
|
-
| **Pure LSP** | Standard LSP using LanguageClient | Generate LanguageClient entry + server dependency injection | Prisma |
|
|
30
|
-
| **Direct API** | Direct coc.nvim API calls (no LanguageClient) | Keep original `extension.ts` as entry, no bridge | HTML CSS Support |
|
|
31
|
-
|
|
32
|
-
TS bridge plugins require a modified coc-tsserver ([PR #493](https://github.com/neoclide/coc-tsserver/pull/493)):
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
cd ~/.config/coc/extensions
|
|
36
|
-
npm install ChuYanLon/coc-tsserver --legacy-peer-deps
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Architecture
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
Input: VS Code extension directory
|
|
43
|
-
│
|
|
44
|
-
├─ scanner Analyze API → detect plugin type
|
|
45
|
-
├─ transforms/ AST transforms
|
|
46
|
-
│ ├─ import-mapping from 'vscode' → from 'coc.nvim'
|
|
47
|
-
│ ├─ class-to-factory new Xxx() → Xxx.create()
|
|
48
|
-
│ ├─ provider-register Adapt provider registration signatures
|
|
49
|
-
│ ├─ language-client Adapt LanguageClient signatures
|
|
50
|
-
│ └─ enum-offset Comment on enum value offsets
|
|
51
|
-
├─ mark-unsupported Replace/mark missing APIs (getWordRangeAtPosition, fileName, etc.)
|
|
52
|
-
├─ generate src/index.ts Main entry (bridge / LanguageClient / direct templates)
|
|
53
|
-
├─ generate package.json Dependencies / esbuild external config
|
|
54
|
-
└─ generate esbuild.mjs Build config
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Bridge preset system
|
|
58
|
-
|
|
59
|
-
Bridge logic is preset-driven rather than hardcoded:
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
// presets.ts - all bridge presets defined here
|
|
63
|
-
const PRESETS = {
|
|
64
|
-
'ts-bridge': {
|
|
65
|
-
notification: 'tsserver/request',
|
|
66
|
-
responseNotification: 'tsserver/response',
|
|
67
|
-
handler: { type: 'command', command: 'typescript.tsserverRequest' },
|
|
68
|
-
extraDeps: ['typescript'],
|
|
69
|
-
},
|
|
70
|
-
// future: python-bridge, rust-bridge, etc.
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
`convert.ts` only calls `getActivePresets()` + `generateBridgeCode()`, it never touches bridge logic directly.
|
|
75
|
-
Adding a new bridge type = add a new preset in `presets.ts`, no changes to main flow.
|
|
76
|
-
|
|
77
|
-
See [coc-vscode-registry/docs/converter-design-v2.md](https://github.com/coc-plugin/coc-vscode-registry/blob/main/docs/converter-design-v2.md).
|
|
78
|
-
|
|
79
|
-
## File structure
|
|
80
|
-
|
|
81
|
-
| File | Lines | Description |
|
|
82
|
-
|------|-------|-------------|
|
|
83
|
-
| `src/cli.ts` | 28 | CLI entry |
|
|
84
|
-
| `src/convert.ts` | 484 | Main flow + template generation + API replacement |
|
|
85
|
-
| `src/scanner.ts` | 136 | API scanner + plugin classification |
|
|
86
|
-
| `src/transforms/import-mapping.ts` | 47 | Import replacement |
|
|
87
|
-
| `src/transforms/language-client.ts` | 48 | LanguageClient adaptation |
|
|
88
|
-
| `src/transforms/class-to-factory.ts` | 54 | new Xxx() → Xxx.create() |
|
|
89
|
-
| `src/transforms/provider-register.ts` | 55 | Provider registration signature fixes |
|
|
90
|
-
| `src/transforms/enum-offset.ts` | 49 | Enum value offset annotations |
|
|
91
|
-
| **Total** | **~870** | |
|
|
92
|
-
|
|
93
|
-
## Handled API differences
|
|
94
|
-
|
|
95
|
-
| API | VS Code | coc.nvim | Handling |
|
|
96
|
-
|-----|---------|----------|----------|
|
|
97
|
-
| import | `from 'vscode'` | `from 'coc.nvim'` | Direct replace |
|
|
98
|
-
| Position/Range/Location etc. | `new Xxx()` | `Xxx.create()` | AST replace |
|
|
99
|
-
| EventEmitter | `EventEmitter<T>` | `Emitter<T>` | Direct replace |
|
|
100
|
-
| registerCompletionItemProvider | `(sel, p, ...t)` | `(name, shortcut, sel, p, t?)` | Pad arguments |
|
|
101
|
-
| registerCodeActionsProvider | `registerCodeActionsProvider` | `registerCodeActionProvider` | Rename |
|
|
102
|
-
| registerReferenceProvider | `registerReferenceProvider` | `registerReferencesProvider` | Rename |
|
|
103
|
-
| CompletionItem.create | `new CompletionItem(label, kind)` | `CompletionItem.create(label)` + `item.kind = kind` | kind set separately |
|
|
104
|
-
| Trigger characters | `" "` (string) | `[" "]` (array) | Rest param → array |
|
|
105
|
-
| CompletionItemKind enum | `Value = 11`, `Enum = 12` | `Value = 12`, `Enum = 13` | Offset by 1, symbols auto-adapt |
|
|
106
|
-
| documentSelector | `[{ language: 'xxx' }]` | Same | Auto-infer from package.json |
|
|
107
|
-
| getWordRangeAtPosition | `document.getWordRangeAtPosition()` | Not available | Inline word boundary calculation |
|
|
108
|
-
| fileName | `document.fileName` | Not available | Replace with `document.uri` |
|
|
109
|
-
| createTextEditorDecorationType | `window.createTextEditorDecorationType()` | Not available | Mark TODO |
|
|
110
|
-
| createWebviewPanel | `window.createWebviewPanel()` | Not available | Mark TODO |
|
|
111
|
-
|
|
112
|
-
### Missing API strategy
|
|
113
|
-
|
|
114
|
-
When a VS Code API has no coc.nvim equivalent, the approach is:
|
|
115
|
-
|
|
116
|
-
1. Find the [VS Code source](https://github.com/microsoft/vscode) implementation
|
|
117
|
-
2. Evaluate complexity:
|
|
118
|
-
- **Simple** (e.g. `getWordRangeAtPosition`) → inline polyfill
|
|
119
|
-
- **Complex** (e.g. decoration, webview) → mark TODO with explanation
|
|
120
|
-
3. Polyfill using existing coc APIs where possible, avoid new dependencies
|
|
121
|
-
|
|
122
|
-
Known VS Code API source locations:
|
|
123
|
-
- `getWordRangeAtPosition` → `src/vs/editor/common/core/wordHelper.ts`
|
|
124
|
-
- `TextDocument.fileName` → coc uses `document.uri` instead (`DocumentUri = string`)
|
|
125
|
-
- Decoration system → `src/vs/editor/common/viewModel/viewDecorations.ts`
|
|
126
|
-
|
|
127
|
-
## Key design decisions
|
|
128
|
-
|
|
129
|
-
- **Zero hardcoding** — server package names auto-detected from source
|
|
130
|
-
- **Bin entry fallback** — auto-detect and prefer `package.json` bin entry
|
|
131
|
-
- **Auto esbuild external injection** — detected server packages marked as external
|
|
132
|
-
- **Auto TS bridge injection** — `typescriptServerPlugins` + `tsserver/request` forwarding
|
|
133
|
-
- **Plugin classification** — auto-detect TS bridge / pure LSP / direct API
|
|
134
|
-
- **Missing API handling** — polyfill where possible, mark TODO otherwise
|