coc-vscode-loader 1.2.1 → 1.2.3
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/assets/tui-preview.png +0 -0
- package/converter/package.json +1 -1
- package/converter/src/convert.ts +33 -6
- package/converter/src/steps/bridge.ts +26 -4
- package/converter/src/steps/language-client.ts +15 -5
- package/converter/src/steps/source.ts +39 -6
- package/converter/src/transforms/import-mapping.ts +164 -18
- package/converter/src/types.ts +5 -0
- package/lib/index.js +98 -49
- package/package.json +29 -8
package/assets/tui-preview.png
CHANGED
|
Binary file
|
package/converter/package.json
CHANGED
package/converter/src/convert.ts
CHANGED
|
@@ -68,7 +68,9 @@ export async function convert(opts: ConvertOptions): Promise<void> {
|
|
|
68
68
|
}
|
|
69
69
|
console.log(result.summary)
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
// Source-only steps need source files; bridge/language-client steps can generate code
|
|
72
|
+
const hasNonSourceStep = steps.some(s => s.type !== 'source' && s.type !== 'mark-unsupported')
|
|
73
|
+
if (result.files.length === 0 && !hasNonSourceStep) {
|
|
72
74
|
console.log('No VS Code API usage found, nothing to convert.')
|
|
73
75
|
return
|
|
74
76
|
}
|
|
@@ -174,7 +176,7 @@ export async function convert(opts: ConvertOptions): Promise<void> {
|
|
|
174
176
|
if (fs.existsSync(outputSrc)) {
|
|
175
177
|
for (const f of fs.readdirSync(outputSrc, { recursive: true })) {
|
|
176
178
|
const fp = typeof f === 'string' ? path.join(outputSrc, f) : f
|
|
177
|
-
if (!fp.endsWith('.ts')) continue
|
|
179
|
+
if (!fp.endsWith('.ts') && !fp.endsWith('.js')) continue
|
|
178
180
|
let content = fs.readFileSync(fp, 'utf-8')
|
|
179
181
|
let changed = false
|
|
180
182
|
|
|
@@ -198,13 +200,38 @@ export async function convert(opts: ConvertOptions): Promise<void> {
|
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
if (content.includes('.fileName') || content.includes('.uri.fsPath')) {
|
|
201
|
-
// .fileName → .uri (coc's TextDocument#uri returns a
|
|
202
|
-
content = content.replace(/(document|this\.document|textDocument|scope)\.fileName/g, '$1.uri')
|
|
203
|
-
//
|
|
204
|
-
content = content.replace(
|
|
203
|
+
// .fileName → Uri.parse($1.uri).fsPath (coc's TextDocument#uri returns a file:// URI string)
|
|
204
|
+
content = content.replace(/(document|this\.document|textDocument|scope|doc)\.fileName/g, 'Uri.parse($1.uri).fsPath')
|
|
205
|
+
// Handle destructuring: const { fileName, ...rest } = document/doc/textDocument
|
|
206
|
+
content = content.replace(
|
|
207
|
+
/^(\s*)(const|let|var)\s*\{([^}]*)\}\s*=\s*(document|doc|textDocument)\s*;?\s*$/gm,
|
|
208
|
+
(m: string, indent: string, kw: string, props: string, varName: string) => {
|
|
209
|
+
const parts = props.split(',').map((p: string) => p.trim())
|
|
210
|
+
if (!parts.some((p: string) => p === 'fileName')) return m
|
|
211
|
+
const rest = parts.filter((p: string) => p !== 'fileName' && p !== '')
|
|
212
|
+
return rest.length > 0
|
|
213
|
+
? `${indent}${kw} {${rest.join(', ')}} = ${varName};\n${indent}const fileName = Uri.parse(${varName}.uri).fsPath`
|
|
214
|
+
: `${indent}const fileName = Uri.parse(${varName}.uri).fsPath`
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
// .uri.fsPath → Uri.parse(...).fsPath (coc's uri is a file:// URI string, not a path)
|
|
218
|
+
content = content.replace(/(\w+(?:\.\w+)*?)\.uri\.fsPath/g, 'Uri.parse($1.uri).fsPath')
|
|
205
219
|
changed = true
|
|
206
220
|
}
|
|
207
221
|
|
|
222
|
+
// Ensure Uri is imported when introduced by Uri.parse() replacements
|
|
223
|
+
if (content.includes('Uri.parse(') && content.match(/from\s+['"]coc\.nvim['"]/)) {
|
|
224
|
+
content = content.replace(
|
|
225
|
+
/(import\s*\{\s*)([^}]*?)(\s*\}\s*from\s*['"]coc\.nvim['"])/g,
|
|
226
|
+
(_m: string, prefix: string, existing: string, suffix: string) => {
|
|
227
|
+
if (!existing.includes('Uri')) {
|
|
228
|
+
const sep = existing.trim() ? ', ' : ''
|
|
229
|
+
return `${prefix}${existing.trim()}${sep}Uri${suffix}`
|
|
230
|
+
}
|
|
231
|
+
return _m
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
}
|
|
208
235
|
if (changed) fs.writeFileSync(fp, content)
|
|
209
236
|
}
|
|
210
237
|
}
|
|
@@ -72,7 +72,32 @@ export const bridgeGenerator: StepGenerator = {
|
|
|
72
72
|
${code}`
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
const extIds = generated.injectExts || []
|
|
76
|
+
const svcIds = generated.injectSvcs || []
|
|
77
|
+
const callAfter = generated.callAfter
|
|
78
|
+
const isStandalone = !callAfter && extIds.length === 0 && svcIds.length === 0
|
|
79
|
+
|
|
80
|
+
if (isStandalone) {
|
|
81
|
+
// Standalone preset (e.g. prettier): generate entry point directly
|
|
82
|
+
const moduleContent = `\
|
|
83
|
+
import { ExtensionContext, languages, Range, TextEdit, Uri, window, workspace } from 'coc.nvim'
|
|
84
|
+
|
|
85
|
+
export async function activate(context: ExtensionContext): Promise<void> {
|
|
86
|
+
${code}
|
|
87
|
+
}
|
|
88
|
+
`
|
|
89
|
+
return {
|
|
90
|
+
generatedFiles: [{ path: 'src/index.ts', content: moduleContent }],
|
|
91
|
+
entryPoint: 'src/index.ts',
|
|
92
|
+
keepDeps: Object.fromEntries((generated.extraDeps || []).map((d: string) => {
|
|
93
|
+
const ver = ctx.origPkg.dependencies?.[d] || ctx.origPkg.devDependencies?.[d]
|
|
94
|
+
return [d, ver || '*']
|
|
95
|
+
})),
|
|
96
|
+
activationEvents: ['*'],
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Generate the bridge module (for tsserver-forward etc)
|
|
76
101
|
const moduleContent = `\
|
|
77
102
|
import { commands, ExtensionContext } from 'coc.nvim'
|
|
78
103
|
|
|
@@ -83,9 +108,6 @@ ${code}
|
|
|
83
108
|
|
|
84
109
|
// Build code injections
|
|
85
110
|
const codeInjections: StepResult['codeInjections'] = []
|
|
86
|
-
const extIds = generated.injectExts || []
|
|
87
|
-
const svcIds = generated.injectSvcs || []
|
|
88
|
-
const callAfter = generated.callAfter
|
|
89
111
|
|
|
90
112
|
if (callAfter) {
|
|
91
113
|
codeInjections.push({
|
|
@@ -42,21 +42,30 @@ export const languageClientGenerator: StepGenerator = {
|
|
|
42
42
|
const entry = ls.server.entry || 'main'
|
|
43
43
|
|
|
44
44
|
// Same resolution as old converter: resolve main entry first, then walk for bin
|
|
45
|
+
// Use require.resolve('pkg/package.json') as fallback for packages without main entry
|
|
46
|
+
const binName = ls.server.binName || ''
|
|
47
|
+
const binLookupCode = binName
|
|
48
|
+
? `(_pkg.bin && _pkg.bin['${escapeStr(binName)}'] ? _pkg.bin['${escapeStr(binName)}'] : Object.values(_pkg.bin)[0])`
|
|
49
|
+
: `(typeof _pkg.bin === 'string' ? _pkg.bin : Object.values(_pkg.bin)[0])`
|
|
45
50
|
serverPathCode = `\
|
|
46
51
|
let serverPath: string | undefined
|
|
52
|
+
let _mainEntry: string | undefined
|
|
47
53
|
try {
|
|
48
|
-
|
|
54
|
+
_mainEntry = require.resolve('${escapeStr(pkg)}')
|
|
49
55
|
} catch {}
|
|
56
|
+
if (!_mainEntry) {
|
|
57
|
+
try { _mainEntry = require.resolve('${escapeStr(pkg)}/package.json') } catch {}
|
|
58
|
+
}
|
|
50
59
|
try {
|
|
51
60
|
// Walk up from the resolved main entry to find the package's package.json
|
|
52
61
|
// We can't use require.resolve('pkg/package.json') because exports field may block it
|
|
53
|
-
let _dir = require('path').dirname(
|
|
54
|
-
while (_dir !== require('path').dirname(_dir)) {
|
|
62
|
+
let _dir = _mainEntry ? require('path').dirname(_mainEntry) : undefined;
|
|
63
|
+
while (_dir && _dir !== require('path').dirname(_dir)) {
|
|
55
64
|
const _pkgPath = require('path').join(_dir, 'package.json');
|
|
56
65
|
if (require('fs').existsSync(_pkgPath)) {
|
|
57
66
|
const _pkg = JSON.parse(require('fs').readFileSync(_pkgPath, 'utf-8'));
|
|
58
67
|
if (_pkg.bin) {
|
|
59
|
-
const _entry =
|
|
68
|
+
const _entry = ${binLookupCode};
|
|
60
69
|
serverPath = require('path').join(_dir, _entry);
|
|
61
70
|
}
|
|
62
71
|
break;
|
|
@@ -65,7 +74,7 @@ export const languageClientGenerator: StepGenerator = {
|
|
|
65
74
|
}
|
|
66
75
|
} catch {}`
|
|
67
76
|
// Use full require.resolve path (including bin walking) if available, else fallback to simple main entry
|
|
68
|
-
serverOptionsCode = `{ module: serverPath || require.resolve('${escapeStr(pkg)}'), transport: ${transportExpr} }`
|
|
77
|
+
serverOptionsCode = `{ module: serverPath || _mainEntry || require.resolve('${escapeStr(pkg)}/package.json'), transport: ${transportExpr} }`
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
const docSelectorCode = `[${languages.map(l => `{ scheme: 'file', language: '${l}' }`).join(', ')}]`
|
|
@@ -99,6 +108,7 @@ ${ls.verbose ? ` console.log('[${escapeStr(id)}] creating LanguageClient')\n`
|
|
|
99
108
|
{
|
|
100
109
|
documentSelector: ${docSelectorCode},
|
|
101
110
|
outputChannelName: '${escapeStr(description)}',
|
|
111
|
+
${ls.initializationOptions ? `initializationOptions: ${ls.initializationOptions},` : ''}
|
|
102
112
|
},
|
|
103
113
|
)
|
|
104
114
|
context.subscriptions.push({ dispose: () => c.stop() })
|
|
@@ -39,18 +39,19 @@ export const sourceGenerator: StepGenerator = {
|
|
|
39
39
|
const outputsDir = path.join(output, 'src')
|
|
40
40
|
fs.mkdirSync(outputsDir, { recursive: true })
|
|
41
41
|
|
|
42
|
-
// Copy ALL .ts/.tsx files from source directory (try src/ first, fall back to input root)
|
|
42
|
+
// Copy ALL .ts/.tsx/.js files from source directory (try src/ first, fall back to input root)
|
|
43
43
|
let srcDir = path.join(input, 'src')
|
|
44
44
|
if (!fs.existsSync(srcDir)) {
|
|
45
45
|
srcDir = input
|
|
46
46
|
}
|
|
47
47
|
const hasStripVolar = ss.transforms.includes('strip-volar')
|
|
48
48
|
const allFiles: Array<{ src: string; rel: string }> = []
|
|
49
|
+
const jsFiles: string[] = []
|
|
49
50
|
const vscodeFiles: string[] = []
|
|
50
51
|
|
|
51
52
|
for (const f of walkFiles(srcDir)) {
|
|
52
53
|
const rel = path.relative(srcDir, f)
|
|
53
|
-
if (!rel.endsWith('.ts') && !rel.endsWith('.tsx')) continue
|
|
54
|
+
if (!rel.endsWith('.ts') && !rel.endsWith('.tsx') && !rel.endsWith('.js')) continue
|
|
54
55
|
|
|
55
56
|
// Skip framework files that are replaced by generated code
|
|
56
57
|
if (hasStripVolar) {
|
|
@@ -61,8 +62,10 @@ export const sourceGenerator: StepGenerator = {
|
|
|
61
62
|
allFiles.push({ src: f, rel })
|
|
62
63
|
|
|
63
64
|
const content = fs.readFileSync(f, 'utf-8')
|
|
64
|
-
|
|
65
|
+
const hasVscode = content.includes("from 'vscode'") || content.includes('from "vscode"') || content.includes('require("vscode")')
|
|
66
|
+
if (hasVscode) {
|
|
65
67
|
vscodeFiles.push(rel)
|
|
68
|
+
if (rel.endsWith('.js')) jsFiles.push(rel)
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -74,13 +77,13 @@ export const sourceGenerator: StepGenerator = {
|
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
if (verbose) {
|
|
77
|
-
console.log(` source: copied ${allFiles.length} files (${vscodeFiles.length} with vscode imports)`)
|
|
80
|
+
console.log(` source: copied ${allFiles.length} files (${vscodeFiles.length} with vscode imports, ${jsFiles.length} .js)`)
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
// Apply transforms via ts-morph (only to files with vscode imports)
|
|
83
|
+
// Apply transforms via ts-morph (only to TS files with vscode imports; .js files get text-level only)
|
|
81
84
|
for (const rel of vscodeFiles) {
|
|
82
85
|
const fp = path.join(outputsDir, rel)
|
|
83
|
-
if (!fs.existsSync(fp)) continue
|
|
86
|
+
if (!fs.existsSync(fp) || rel.endsWith('.js')) continue
|
|
84
87
|
try { project.addSourceFileAtPath(fp) } catch {}
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -104,6 +107,36 @@ export const sourceGenerator: StepGenerator = {
|
|
|
104
107
|
sf.saveSync()
|
|
105
108
|
}
|
|
106
109
|
|
|
110
|
+
// Apply text-level replacements to .js files (ts-morph can't handle JS)
|
|
111
|
+
for (const rel of jsFiles) {
|
|
112
|
+
const fp = path.join(outputsDir, rel)
|
|
113
|
+
if (!fs.existsSync(fp)) continue
|
|
114
|
+
let code = fs.readFileSync(fp, 'utf-8')
|
|
115
|
+
const orig = code
|
|
116
|
+
code = code.replace(/require\(['"]vscode['"]\)/g, "require('coc.nvim')")
|
|
117
|
+
code = code.replace(/(\w+)\.fileName\b/g, "Uri.parse($1.uri).fsPath")
|
|
118
|
+
code = code.replace(/(\w+(?:\.\w+)*?)\.uri\.fsPath/g, 'Uri.parse($1.uri).fsPath')
|
|
119
|
+
if (code.includes('window.activeTextEditor')) {
|
|
120
|
+
code = `\
|
|
121
|
+
if (typeof window !== 'undefined' && !('activeTextEditor' in window)) {
|
|
122
|
+
try {
|
|
123
|
+
Object.defineProperty(window, 'activeTextEditor', {
|
|
124
|
+
get() {
|
|
125
|
+
try {
|
|
126
|
+
var doc = typeof workspace !== 'undefined' ? workspace.getDocument() : undefined;
|
|
127
|
+
return doc ? { document: doc } : undefined;
|
|
128
|
+
} catch(e) { return undefined }
|
|
129
|
+
},
|
|
130
|
+
configurable: true,
|
|
131
|
+
});
|
|
132
|
+
} catch {}
|
|
133
|
+
}
|
|
134
|
+
` + code
|
|
135
|
+
}
|
|
136
|
+
code = code.replace(/window\.onDidChangeActiveTextEditor/g, 'workspace.onDidOpenTextDocument')
|
|
137
|
+
if (code !== orig) fs.writeFileSync(fp, code)
|
|
138
|
+
}
|
|
139
|
+
|
|
107
140
|
// Resolve keepDeps from origPkg (with workspace root fallback)
|
|
108
141
|
const keepDeps: Record<string, string> = {}
|
|
109
142
|
if (ss.keepDeps) {
|
|
@@ -5,12 +5,12 @@ import { Transform } from '../types.js'
|
|
|
5
5
|
* and apply name remapping for known API differences.
|
|
6
6
|
*/
|
|
7
7
|
const MAPPINGS: Record<string, string> = {
|
|
8
|
-
// namespace
|
|
9
|
-
'vscode': 'coc.nvim',
|
|
8
|
+
// namespace (module specifier is rewritten by AST, keep identifier as-is)
|
|
10
9
|
|
|
11
10
|
// naming differences
|
|
12
11
|
'EventEmitter': 'Emitter',
|
|
13
12
|
'Disposable': 'Disposable',
|
|
13
|
+
'StatusBarAlignment': '(void 0) as any',
|
|
14
14
|
|
|
15
15
|
// function/method renames
|
|
16
16
|
'getExtension': 'getExtensionById',
|
|
@@ -25,23 +25,169 @@ export const transformImportMapping: Transform = (ctx) => {
|
|
|
25
25
|
const { file } = ctx
|
|
26
26
|
|
|
27
27
|
// Rewrite import declarations
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
try {
|
|
29
|
+
file.getImportDeclarations().forEach(decl => {
|
|
30
|
+
const mod = decl.getModuleSpecifierValue()
|
|
31
|
+
if (mod === 'vscode') {
|
|
32
|
+
decl.setModuleSpecifier('coc.nvim')
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
} catch {}
|
|
34
36
|
|
|
35
37
|
// Rewrite named references
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
try {
|
|
39
|
+
file.getDescendantsOfKind(80 /* Identifier */).forEach(node => {
|
|
40
|
+
const text = node.getText()
|
|
41
|
+
if (Object.prototype.hasOwnProperty.call(MAPPINGS, text)) {
|
|
42
|
+
const mapped = MAPPINGS[text]
|
|
43
|
+
if (mapped !== text) {
|
|
44
|
+
const parent = node.getParent()
|
|
45
|
+
if (parent && parent.getKindName() !== 'StringLiteral') {
|
|
46
|
+
node.replaceWithText(mapped)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
} catch {}
|
|
52
|
+
|
|
53
|
+
// Replace CodeActionKind.SourceFixAll.append('xxx') with string literal
|
|
54
|
+
try {
|
|
55
|
+
file.getDescendantsOfKind(214 /* CallExpression */).forEach(node => {
|
|
56
|
+
const text = node.getText()
|
|
57
|
+
const match = text.match(/^CodeActionKind\.SourceFixAll\.append\(['"](.+)['"]\)$/)
|
|
58
|
+
if (match) {
|
|
59
|
+
node.replaceWithText(`'source.fixAll.${match[1]}'`)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
} catch {}
|
|
63
|
+
|
|
64
|
+
// Text-level replacements for coc.nvim API differences
|
|
65
|
+
|
|
66
|
+
// Convert require('vscode') to require('coc.nvim') (JS-style imports)
|
|
67
|
+
let content = file.getText()
|
|
68
|
+
let newContent = content.replace(
|
|
69
|
+
/require\(['"]vscode['"]\)/g,
|
|
70
|
+
"require('coc.nvim')",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// Convert dynamic import() to require()
|
|
74
|
+
newContent = newContent.replace(
|
|
75
|
+
/await\s+import\(/g,
|
|
76
|
+
'require(',
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// Convert createStatusBarItem(name, alignment, priority) → createStatusBarItem(priority)
|
|
80
|
+
newContent = newContent.replace(
|
|
81
|
+
/createStatusBarItem\([^,]+,\s*(?:\w+\.)?(?:Right|Left),\s*/g,
|
|
82
|
+
'createStatusBarItem(',
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// Replace LanguageStatusSeverity.xxx → 2
|
|
86
|
+
newContent = newContent.replace(
|
|
87
|
+
/LanguageStatusSeverity\.\w+/g,
|
|
88
|
+
'2',
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// Replace StatusBar with a no-op mock so formatting works without status UI
|
|
92
|
+
newContent = newContent.replace(
|
|
93
|
+
/new\s+StatusBar\(\)/g,
|
|
94
|
+
'new (class { update(){} hide(){} updateConfig(){} dispose(){} } as any)()',
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
// Treat all workspaces as trusted (coc.nvim doesn't have workspace.isTrusted)
|
|
98
|
+
newContent = newContent.replace(
|
|
99
|
+
/workspace\.isTrusted/g,
|
|
100
|
+
'true',
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// Wrap new CodeAction() in try-catch (coc.nvim may not have CodeAction)
|
|
104
|
+
newContent = newContent.replace(
|
|
105
|
+
/const action = new CodeAction\(/g,
|
|
106
|
+
'let action; try { action = new CodeAction(',
|
|
107
|
+
)
|
|
108
|
+
// Close the try-catch before return [action]
|
|
109
|
+
newContent = newContent.replace(
|
|
110
|
+
/return \[action\];/g,
|
|
111
|
+
'}catch(e){action={title:"",kind:""}};return [action];',
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
// window.createOutputChannel works in coc.nvim (workspace variant is deprecated)
|
|
115
|
+
// no replacement needed
|
|
116
|
+
|
|
117
|
+
// Polyfill window.activeTextEditor (VS Code API, not in coc.nvim)
|
|
118
|
+
if (newContent.includes('window.activeTextEditor')) {
|
|
119
|
+
newContent = `\
|
|
120
|
+
if (typeof window !== 'undefined' && !('activeTextEditor' in window)) {
|
|
121
|
+
try {
|
|
122
|
+
Object.defineProperty(window, 'activeTextEditor', {
|
|
123
|
+
get() {
|
|
124
|
+
try {
|
|
125
|
+
var doc = typeof workspace !== 'undefined' ? workspace.getDocument() : undefined;
|
|
126
|
+
return doc ? { document: doc } : undefined;
|
|
127
|
+
} catch(e) { return undefined }
|
|
128
|
+
},
|
|
129
|
+
configurable: true,
|
|
130
|
+
});
|
|
131
|
+
} catch {}
|
|
132
|
+
}
|
|
133
|
+
` + newContent
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// window.onDidChangeActiveTextEditor → workspace.onDidOpenTextDocument
|
|
137
|
+
newContent = newContent.replace(/window\.onDidChangeActiveTextEditor/g, 'workspace.onDidOpenTextDocument')
|
|
138
|
+
|
|
139
|
+
// languages.createLanguageStatusItem → no-op (coc.nvim doesn't have this)
|
|
140
|
+
newContent = newContent.replace(
|
|
141
|
+
/languages\.createLanguageStatusItem\([^)]+\)/g,
|
|
142
|
+
'({ dispose(){}, text: "", command: void 0, name: "", accessibilityInformation: void 0, severity: void 0 }) as any'
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
// window.showOpenDialog → not available in coc, return undefined
|
|
146
|
+
newContent = newContent.replace(
|
|
147
|
+
/window\.showOpenDialog\([^)]*\)/g,
|
|
148
|
+
'void 0 as any'
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
// Add priority 1 to document format providers (default 0 gets overridden by LanguageClient)
|
|
152
|
+
newContent = newContent.replace(
|
|
153
|
+
/registerDocumentFormatProvider\s*\(\s*(\w[\w.]*)\s*,\s*(\w[\w.]*)\s*,?\s*\)/g,
|
|
154
|
+
'registerDocumentFormatProvider($1, $2, 1)'
|
|
155
|
+
)
|
|
156
|
+
newContent = newContent.replace(
|
|
157
|
+
/registerDocumentRangeFormatProvider\s*\(\s*(\w[\w.]*)\s*,\s*(\w[\w.]*)\s*,?\s*\)/g,
|
|
158
|
+
'registerDocumentRangeFormatProvider($1, $2, 1)'
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
// authentication.getSession → undefined (coc.nvim has no auth API)
|
|
162
|
+
newContent = newContent.replace(
|
|
163
|
+
/authentication\.getSession\s*\([^)]*\)/g,
|
|
164
|
+
'undefined as any'
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
// editor.setDecorations → no-op (coc has different decoration API)
|
|
168
|
+
newContent = newContent.replace(/editor\.setDecorations\s*\([^)]+\)/g, '/* setDecorations */')
|
|
169
|
+
|
|
170
|
+
// Guard workspace.workspaceFolders when accessed via index (coc.nvim may return undefined)
|
|
171
|
+
newContent = newContent.replace(
|
|
172
|
+
/workspace\.workspaceFolders(?=\[)/g,
|
|
173
|
+
'(workspace.workspaceFolders || [])'
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
// Ensure workspace is imported from coc.nvim when we introduced workspace. references
|
|
177
|
+
if (newContent.includes('workspace.') && newContent.match(/from\s+['"]coc\.nvim['"]/)) {
|
|
178
|
+
newContent = newContent.replace(
|
|
179
|
+
/(import\s*\{\s*)([^}]*?)(\s*\}\s*from\s*['"]coc\.nvim['"])/g,
|
|
180
|
+
(match, prefix, existing, suffix) => {
|
|
181
|
+
if (!existing.includes('workspace')) {
|
|
182
|
+
const sep = existing.trim() ? ', ' : ''
|
|
183
|
+
return `${prefix}${existing.trim()}${sep}workspace${suffix}`
|
|
184
|
+
}
|
|
185
|
+
return match
|
|
44
186
|
}
|
|
45
|
-
|
|
46
|
-
}
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (newContent !== content) {
|
|
191
|
+
file.replaceWithText(newContent)
|
|
192
|
+
}
|
|
47
193
|
}
|
package/converter/src/types.ts
CHANGED
|
@@ -13,6 +13,9 @@ export interface ServerModuleConfig {
|
|
|
13
13
|
kind: 'module'
|
|
14
14
|
package: string
|
|
15
15
|
entry?: 'main' | 'bin'
|
|
16
|
+
/** When entry is 'bin', pick a specific bin entry by name (e.g. "tailwindcss-language-server").
|
|
17
|
+
* Defaults to the first entry in the bin object. */
|
|
18
|
+
binName?: string
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export interface ServerBinaryConfig {
|
|
@@ -37,6 +40,8 @@ export interface LanguageClientStep {
|
|
|
37
40
|
multiRoot?: boolean
|
|
38
41
|
/** Enable debug logging in generated code */
|
|
39
42
|
verbose?: boolean
|
|
43
|
+
/** Extra options passed as initializationOptions to LanguageClient (JS object expression, inserted as-is) */
|
|
44
|
+
initializationOptions?: string
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
export interface SourceStep {
|
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.2.
|
|
42
|
+
version: "1.2.3",
|
|
43
43
|
description: "Run VS Code extensions seamlessly in coc.nvim",
|
|
44
44
|
main: "lib/index.js",
|
|
45
45
|
keywords: [
|
|
@@ -89,13 +89,34 @@ var require_package = __commonJS({
|
|
|
89
89
|
],
|
|
90
90
|
contributes: {
|
|
91
91
|
commands: [
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
{
|
|
93
|
+
command: "loader.open",
|
|
94
|
+
title: "Open VS Code extension loader"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
command: "loader.install",
|
|
98
|
+
title: "Install a VS Code extension"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
command: "loader.uninstall",
|
|
102
|
+
title: "Uninstall a package"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
command: "loader.update",
|
|
106
|
+
title: "Update a package"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
command: "loader.uninstallAll",
|
|
110
|
+
title: "Uninstall all packages"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
command: "loader.updateRegistry",
|
|
114
|
+
title: "Update package registry"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
command: "loader._dispatch",
|
|
118
|
+
title: ""
|
|
119
|
+
}
|
|
99
120
|
]
|
|
100
121
|
}
|
|
101
122
|
};
|
|
@@ -500,7 +521,7 @@ async function downloadSource(info, name, onProgress) {
|
|
|
500
521
|
}
|
|
501
522
|
async function convertSource(inputDir, name, info, onProgress) {
|
|
502
523
|
const build = buildDir(name);
|
|
503
|
-
|
|
524
|
+
await rimraf(build);
|
|
504
525
|
const cli = converterCliPath();
|
|
505
526
|
const converterDir = path3.resolve(path3.dirname(path3.dirname(cli)));
|
|
506
527
|
if (!fs3.existsSync(path3.join(converterDir, "node_modules", "commander"))) {
|
|
@@ -593,34 +614,53 @@ async function buildPackage(name, inputDir, info, onProgress) {
|
|
|
593
614
|
onProgress(3, 5, "Installing server dependencies...", `npm install in ${serverDir}`);
|
|
594
615
|
await run("npm", ["install", "--legacy-peer-deps"], serverDir, npmLog);
|
|
595
616
|
const destServer = path3.join(build, "server");
|
|
596
|
-
|
|
597
|
-
|
|
617
|
+
await rimraf(destServer);
|
|
618
|
+
await cpdir(serverDir, destServer);
|
|
598
619
|
}
|
|
599
620
|
onProgress(4, 5, "Building...", "node esbuild.mjs");
|
|
600
621
|
const buildLog = (chunk) => onProgress(4, 5, chunk.trim(), "");
|
|
601
622
|
await run("node", ["esbuild.mjs"], build, buildLog);
|
|
623
|
+
const archMap = {
|
|
624
|
+
arm64: "aarch64",
|
|
625
|
+
x64: "x86_64"
|
|
626
|
+
};
|
|
627
|
+
const platformMap = {
|
|
628
|
+
darwin: "apple-darwin",
|
|
629
|
+
linux: "unknown-linux-gnu",
|
|
630
|
+
win32: "pc-windows-msvc"
|
|
631
|
+
};
|
|
632
|
+
const arch2 = os3.arch() === "arm64" ? "arm64" : "x64";
|
|
633
|
+
const platform = process.platform === "win32" ? "win32" : process.platform === "darwin" ? "darwin" : "linux";
|
|
634
|
+
const rawArch = archMap[arch2] || arch2;
|
|
635
|
+
const rustTarget = `${rawArch}-${platformMap[platform] || platform}`;
|
|
636
|
+
const indexPath = path3.join(build, "lib", "index.js");
|
|
637
|
+
if (fs3.existsSync(indexPath)) {
|
|
638
|
+
let code = fs3.readFileSync(indexPath, "utf-8");
|
|
639
|
+
for (const [key, val] of [["platform", platform], ["arch", arch2], ["raw-arch", rawArch], ["rust-target", rustTarget]]) {
|
|
640
|
+
code = code.replace(new RegExp(`\\{\\{${key}}}`, "g"), val);
|
|
641
|
+
}
|
|
642
|
+
if (code !== fs3.readFileSync(indexPath, "utf-8")) {
|
|
643
|
+
fs3.writeFileSync(indexPath, code);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
602
646
|
if (info.serverBinary) {
|
|
603
647
|
const sb = info.serverBinary;
|
|
604
648
|
onProgress(4, 5, "Downloading language server...", `fetching ${sb.repo}`);
|
|
605
649
|
try {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
650
|
+
let tagData;
|
|
651
|
+
try {
|
|
652
|
+
const tagRes = await fetch(`https://api.github.com/repos/${sb.repo}/releases/latest`);
|
|
653
|
+
if (!tagRes.ok) throw new Error(`HTTP ${tagRes.status}`);
|
|
654
|
+
tagData = await tagRes.json();
|
|
655
|
+
} catch {
|
|
656
|
+
const { execFile: execFile2 } = require("child_process");
|
|
657
|
+
const out = await new Promise((resolve2, reject) => {
|
|
658
|
+
execFile2("curl", ["-sL", `https://api.github.com/repos/${sb.repo}/releases/latest`], { encoding: "utf-8", maxBuffer: 1024 * 1024 }, (err, stdout) => err ? reject(err) : resolve2(stdout));
|
|
659
|
+
});
|
|
660
|
+
tagData = JSON.parse(out);
|
|
661
|
+
}
|
|
609
662
|
const tag = tagData.tag_name;
|
|
610
663
|
const version = tag.replace(/^v/, "");
|
|
611
|
-
const archMap = {
|
|
612
|
-
arm64: "aarch64",
|
|
613
|
-
x64: "x86_64"
|
|
614
|
-
};
|
|
615
|
-
const platformMap = {
|
|
616
|
-
darwin: "apple-darwin",
|
|
617
|
-
linux: "unknown-linux-gnu",
|
|
618
|
-
win32: "pc-windows-msvc"
|
|
619
|
-
};
|
|
620
|
-
const arch2 = os3.arch() === "arm64" ? "arm64" : "x64";
|
|
621
|
-
const platform = process.platform === "win32" ? "win32" : process.platform === "darwin" ? "darwin" : "linux";
|
|
622
|
-
const rawArch = archMap[arch2] || arch2;
|
|
623
|
-
const rustTarget = `${rawArch}-${platformMap[platform] || platform}`;
|
|
624
664
|
const filename = sb.asset.replace(/\{\{version}}/g, version).replace(/\{\{platform}}/g, platform).replace(/\{\{arch}}/g, arch2).replace(/\{\{raw-arch}}/g, rawArch).replace(/\{\{rust-target}}/g, rustTarget);
|
|
625
665
|
const url = `https://github.com/${sb.repo}/releases/download/${tag}/${filename}`;
|
|
626
666
|
onProgress(4, 5, "Downloading...", `curl ${filename}`);
|
|
@@ -630,12 +670,15 @@ async function buildPackage(name, inputDir, info, onProgress) {
|
|
|
630
670
|
fs3.mkdirSync(serverDir2, { recursive: true });
|
|
631
671
|
if (filename.endsWith(".zip")) {
|
|
632
672
|
await run("unzip", ["-o", filename, "-d", serverDir2], build);
|
|
673
|
+
} else if (filename.endsWith(".tar.gz") || filename.endsWith(".tgz")) {
|
|
674
|
+
await run("tar", ["xzf", filename, "-C", serverDir2], build);
|
|
633
675
|
} else if (filename.endsWith(".gz") && !filename.endsWith(".tar.gz")) {
|
|
634
676
|
const outName = filename.replace(/\.gz$/, "");
|
|
635
677
|
await run("gunzip", [filename], build);
|
|
636
678
|
fs3.renameSync(path3.join(build, outName), path3.join(serverDir2, outName));
|
|
637
679
|
} else {
|
|
638
|
-
|
|
680
|
+
const binName = sb.binaryPath || filename;
|
|
681
|
+
fs3.renameSync(path3.join(build, filename), path3.join(serverDir2, binName));
|
|
639
682
|
}
|
|
640
683
|
try {
|
|
641
684
|
fs3.readdirSync(serverDir2).forEach((f) => {
|
|
@@ -643,19 +686,18 @@ async function buildPackage(name, inputDir, info, onProgress) {
|
|
|
643
686
|
});
|
|
644
687
|
} catch {
|
|
645
688
|
}
|
|
689
|
+
if (version) {
|
|
690
|
+
const verPath = path3.join(build, "lib", "index.js");
|
|
691
|
+
if (fs3.existsSync(verPath)) {
|
|
692
|
+
let vc = fs3.readFileSync(verPath, "utf-8");
|
|
693
|
+
if (vc.includes("{{version}}")) {
|
|
694
|
+
fs3.writeFileSync(verPath, vc.replace(/\{\{version}}/g, version));
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
646
698
|
if (sb.binaryPath || filename.match(/\.(zip|gz)$/)) {
|
|
647
699
|
const archivePath = path3.join(build, filename);
|
|
648
|
-
if (fs3.existsSync(archivePath))
|
|
649
|
-
}
|
|
650
|
-
const indexPath = path3.join(build, "lib", "index.js");
|
|
651
|
-
if (fs3.existsSync(indexPath)) {
|
|
652
|
-
let code = fs3.readFileSync(indexPath, "utf-8");
|
|
653
|
-
code = code.replace(/\{\{version}}/g, version);
|
|
654
|
-
code = code.replace(/\{\{platform}}/g, platform);
|
|
655
|
-
code = code.replace(/\{\{arch}}/g, arch2);
|
|
656
|
-
code = code.replace(/\{\{raw-arch}}/g, rawArch);
|
|
657
|
-
code = code.replace(/\{\{rust-target}}/g, rustTarget);
|
|
658
|
-
fs3.writeFileSync(indexPath, code);
|
|
700
|
+
if (fs3.existsSync(archivePath)) await rimraf(archivePath);
|
|
659
701
|
}
|
|
660
702
|
} catch (e) {
|
|
661
703
|
onProgress(4, 5, `Warning: serverBinary setup failed (${e.message})`, "install server binary manually");
|
|
@@ -669,9 +711,9 @@ async function installToCoc(name, onProgress) {
|
|
|
669
711
|
const src = buildDir(name);
|
|
670
712
|
const dest = pluginDir(name);
|
|
671
713
|
onProgress(5, 5, "Installing to coc...", `copy to ${dest} + register in extensions/package.json`);
|
|
672
|
-
|
|
714
|
+
await rimraf(dest);
|
|
673
715
|
fs3.mkdirSync(path3.dirname(dest), { recursive: true });
|
|
674
|
-
|
|
716
|
+
await cpdir(src, dest);
|
|
675
717
|
const pkgPath = extensionsPkgPath();
|
|
676
718
|
const pkg = fs3.existsSync(pkgPath) ? JSON.parse(fs3.readFileSync(pkgPath, "utf-8")) : { dependencies: {} };
|
|
677
719
|
pkg.dependencies = pkg.dependencies || {};
|
|
@@ -737,13 +779,23 @@ async function installPackage(state, name) {
|
|
|
737
779
|
state.setPackageStatus(name, "failed", { error: e.message });
|
|
738
780
|
}
|
|
739
781
|
}
|
|
782
|
+
function rimraf(dir) {
|
|
783
|
+
return new Promise((resolve2, reject) => {
|
|
784
|
+
if (!fs3.existsSync(dir)) return resolve2();
|
|
785
|
+
(0, import_child_process2.spawn)("rm", ["-rf", dir], { stdio: "ignore" }).on("close", (code) => code === 0 ? resolve2() : reject(new Error(`rm -rf exited ${code}`)));
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
function cpdir(src, dest) {
|
|
789
|
+
return new Promise((resolve2, reject) => {
|
|
790
|
+
const parent = path3.dirname(dest);
|
|
791
|
+
if (!fs3.existsSync(parent)) fs3.mkdirSync(parent, { recursive: true });
|
|
792
|
+
(0, import_child_process2.spawn)("cp", ["-r", src, dest], { stdio: "ignore" }).on("close", (code) => code === 0 ? resolve2() : reject(new Error(`cp -r exited ${code}`)));
|
|
793
|
+
});
|
|
794
|
+
}
|
|
740
795
|
async function uninstallPackage(state, name) {
|
|
741
796
|
state.setPackageStatus(name, "uninstalling", { progress: "[1/3] Removing from coc..." });
|
|
742
797
|
try {
|
|
743
|
-
|
|
744
|
-
if (fs3.existsSync(dest)) {
|
|
745
|
-
fs3.rmSync(dest, { recursive: true });
|
|
746
|
-
}
|
|
798
|
+
await rimraf(pluginDir(name));
|
|
747
799
|
state.setPackageStatus(name, "uninstalling", { progress: "[2/3] Removing from package.json..." });
|
|
748
800
|
const pkgPath = extensionsPkgPath();
|
|
749
801
|
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
@@ -755,10 +807,7 @@ async function uninstallPackage(state, name) {
|
|
|
755
807
|
fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
756
808
|
}
|
|
757
809
|
state.setPackageStatus(name, "uninstalling", { progress: "[3/3] Removing cache..." });
|
|
758
|
-
|
|
759
|
-
if (fs3.existsSync(cache)) {
|
|
760
|
-
fs3.rmSync(cache, { recursive: true });
|
|
761
|
-
}
|
|
810
|
+
await rimraf(cacheDir(name));
|
|
762
811
|
state.setPackageStatus(name, "not-installed");
|
|
763
812
|
state.setDirty();
|
|
764
813
|
import_coc.window.showInformationMessage(`coc-${name} uninstalled`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coc-vscode-loader",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Run VS Code extensions seamlessly in coc.nvim",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -50,13 +50,34 @@
|
|
|
50
50
|
],
|
|
51
51
|
"contributes": {
|
|
52
52
|
"commands": [
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
{
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
{
|
|
54
|
+
"command": "loader.open",
|
|
55
|
+
"title": "Open VS Code extension loader"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"command": "loader.install",
|
|
59
|
+
"title": "Install a VS Code extension"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"command": "loader.uninstall",
|
|
63
|
+
"title": "Uninstall a package"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"command": "loader.update",
|
|
67
|
+
"title": "Update a package"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"command": "loader.uninstallAll",
|
|
71
|
+
"title": "Uninstall all packages"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"command": "loader.updateRegistry",
|
|
75
|
+
"title": "Update package registry"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"command": "loader._dispatch",
|
|
79
|
+
"title": ""
|
|
80
|
+
}
|
|
60
81
|
]
|
|
61
82
|
}
|
|
62
83
|
}
|