;
36 |
37 | protected printJson(obj: JSONSchema7Type, format = false) {
38 | return JSON.stringify(obj, undefined, format ? ' ' : undefined);
39 | }
40 |
41 | protected printAsDetails(rows: Row[]) {
42 | const lines: string[] = [];
43 | rows.forEach((row) => {
44 | let hideLine = '';
45 | if (row.type) {
46 | hideLine += `Type: ${row.type}
`;
47 | }
48 | if (row.default !== undefined) {
49 | hideLine += 'Default: ';
50 | hideLine +=
51 | '' + this.printJson(row.default, true) + '
';
52 | }
53 | if (hideLine) {
54 | lines.push(``);
55 | }
56 | lines.push(
57 | `${row.name}
: ${row.description}.
`,
58 | );
59 | if (hideLine) {
60 | lines.push(hideLine);
61 | lines.push(' ');
62 | }
63 | });
64 | return lines;
65 | }
66 |
67 | /**
68 | * @deprecated
69 | */
70 | protected printAsList(rows: Row[]) {
71 | const lines: string[] = [];
72 | rows.forEach((row) => {
73 | let line = `- \`${row.name}\``;
74 | const descriptions: string[] = [];
75 | if (row.description) {
76 | descriptions.push(row.description);
77 | }
78 | if (row.type) {
79 | descriptions.push(`type: \`${this.printJson(row.type)}\``);
80 | }
81 | if (row.default !== undefined) {
82 | descriptions.push(`default: \`${this.printJson(row.default)}\``);
83 | }
84 | if (descriptions.length) {
85 | line += ': ' + descriptions.join(', ');
86 | }
87 | lines.push(line);
88 | });
89 | return lines;
90 | }
91 |
92 | async attach(headLevel: number, attachTitle: string, markdownPath: string) {
93 | const markdown = await fsp.readFile(markdownPath, 'utf8');
94 | const markdownLines = markdown.split('\n');
95 | let startIndex = markdownLines.findIndex((line) =>
96 | new RegExp('#'.repeat(headLevel) + '\\s*' + attachTitle + '\\s*').test(
97 | line,
98 | ),
99 | );
100 | if (startIndex < 0) {
101 | return;
102 | }
103 | startIndex += 1;
104 | const endIndex = markdownLines
105 | .slice(startIndex)
106 | .findIndex((line) => new RegExp(`#{1,${headLevel}}[^#]`).test(line));
107 | const removeCount = endIndex < 0 ? 0 : endIndex;
108 |
109 | const sections = await this.generate();
110 | const lines: string[] = ['', this.hint, this.ignorePrettierStart];
111 | for (const section of sections) {
112 | if (section.title) {
113 | lines.push(`${section.title}`);
114 | }
115 | lines.push(...this.printAsDetails(section.rows));
116 | }
117 | lines.push('');
118 | lines.push(this.ignorePrettierEnd);
119 | lines.push('');
120 | markdownLines.splice(startIndex, removeCount, ...lines);
121 | console.log(markdownLines.join('\n'))
122 | await fsp.writeFile(markdownPath, markdownLines.join('\n'));
123 | console.log(`Attached to ${attachTitle} header`);
124 | }
125 | }
126 |
127 | class ConfigurationDocGenerator extends DocGenerator {
128 | constructor(
129 | generateCommand: string,
130 | public packageDeclarationFilepath: string,
131 | ) {
132 | super(generateCommand);
133 | }
134 |
135 | isNodeExported(node: ts.Node) {
136 | return (
137 | (ts.getCombinedModifierFlags(node as ts.Declaration) &
138 | ts.ModifierFlags.Export) !==
139 | 0 ||
140 | (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
141 | );
142 | }
143 |
144 | async generate() {
145 | const defRows: Row[] = [];
146 | const propRows: Row[] = [];
147 |
148 | const conf = Pkg.contributes.configuration;
149 | const title = conf.title;
150 | const filename = pathLib.basename(this.packageDeclarationFilepath);
151 |
152 | const Kind = ts.SyntaxKind;
153 | const prog = ts.createProgram([this.packageDeclarationFilepath], {
154 | strict: true,
155 | });
156 | const sourceFile = prog.getSourceFile(this.packageDeclarationFilepath)!;
157 | const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
158 | const checker = prog.getTypeChecker();
159 |
160 | function print(node: ts.Node): string {
161 | return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
162 | }
163 |
164 | function debug(node: ts.Node) {
165 | console.log(Kind[node.kind]);
166 | console.log(print(node));
167 | }
168 |
169 | sourceFile.forEachChild((node) => {
170 | if (!this.isNodeExported(node)) {
171 | return;
172 | }
173 |
174 | if (ts.isTypeAliasDeclaration(node)) {
175 | defRows.push({
176 | name: node.name.text,
177 | description: node.name.text,
178 | type: print(node.type),
179 | });
180 | } else if (ts.isInterfaceDeclaration(node)) {
181 | if (node.name.text === title) {
182 | node.forEachChild((prop) => {
183 | if (!ts.isPropertySignature(prop)) {
184 | return;
185 | }
186 | const symbol = checker.getSymbolAtLocation(prop.name);
187 | if (!symbol) {
188 | return;
189 | }
190 |
191 | const name = symbol.getName();
192 | // @ts-ignore
193 | const jsonProp = conf.properties[name as any] as Definition & {
194 | default_doc?: string;
195 | };
196 | propRows.push({
197 | name,
198 | description: ts.displayPartsToString(
199 | symbol.getDocumentationComment(checker),
200 | ),
201 | type: prop.type ? print(prop.type) : undefined,
202 | default: jsonProp.default_doc
203 | ? jsonProp.default_doc
204 | : jsonProp.default,
205 | });
206 | });
207 | }
208 | } else {
209 | console.error(`[gen_doc] ${filename} not support ${print(node)}`);
210 | }
211 | });
212 |
213 | return [
214 | { title: 'Properties', rows: propRows },
215 | ];
216 | }
217 | }
218 |
219 | // class CommandDocGenerator extends DocGenerator {
220 | // async generate() {
221 | // const cmds = Pkg.contributes.commands as Cmd[];
222 | // const rows: Row[] = [];
223 | // cmds.forEach((cmd) => {
224 | // rows.push({
225 | // name: cmd.command,
226 | // description: cmd.title,
227 | // });
228 | // });
229 | // return [{ rows }];
230 | // }
231 | // }
232 |
233 | async function main() {
234 | const cmd = 'yarn run bulid:doc';
235 | const markdownPath = `${__dirname}/../README.md`
236 | const packageDeclarationFilepath = `${__dirname}/../src/types/pkg-config.d.ts`
237 | // await new CommandDocGenerator(cmd).attach(2, 'Commands', markdownPath);
238 | await new ConfigurationDocGenerator(cmd, packageDeclarationFilepath).attach(
239 | 2,
240 | 'Configuration',
241 | markdownPath
242 | );
243 | }
244 |
245 | main().catch(console.error);
246 |
--------------------------------------------------------------------------------
/scripts/readme.md:
--------------------------------------------------------------------------------
1 | Reference: https://github.com/weirongxu/coc-explorer/tree/master/scripts
2 |
--------------------------------------------------------------------------------
/src/client.ts:
--------------------------------------------------------------------------------
1 | import { LanguageClient, LanguageClientOptions, ServerOptions, window } from 'coc.nvim'
2 | import getConfig from './config'
3 | import { checkCommand } from './util'
4 |
5 | const serverPath = getConfig('lsp.serverPath')
6 |
7 | const serverOptions: ServerOptions = {
8 | command: serverPath,
9 | }
10 |
11 | const clientOptions: LanguageClientOptions = {
12 | documentSelector: ['cmake'],
13 | initializationOptions: {
14 | buildDirectory: getConfig('lsp.buildDirectory'),
15 | },
16 | }
17 |
18 | export default class CMakeLanguageClient extends LanguageClient {
19 | constructor() {
20 | super('cmake', 'cmake language server', serverOptions, clientOptions)
21 | checkServerBin()
22 | }
23 | }
24 |
25 | async function checkServerBin(): Promise {
26 | const serverExists = await checkCommand(serverPath)
27 | if (!serverExists) {
28 | const install = await window.showPrompt(
29 | '`cmake.lsp.enable` is set to `true` but ' +
30 | 'cmake-language-server is not installed, install it?'
31 | )
32 | if (install) {
33 | await window.openTerminal('pip install cmake-language-server')
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import { workspace } from 'coc.nvim'
2 |
3 | export default function getConfig(key: string, defaultValue?: any): T {
4 | const cmake_conf = workspace.getConfiguration('cmake')
5 | return cmake_conf.get(key, defaultValue)
6 | }
7 |
--------------------------------------------------------------------------------
/src/core.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CompletionItem,
3 | CompletionItemKind,
4 | InsertTextFormat,
5 | Thenable,
6 | } from 'coc.nvim'
7 | import getConfig from './config'
8 | import { parseCmdArgs, strContains, strEquals } from './util'
9 | import child_process = require('child_process')
10 |
11 | function cmakeType2complKind(kind: string): CompletionItemKind {
12 | switch (kind) {
13 | case 'function':
14 | return CompletionItemKind.Function
15 | case 'variable':
16 | return CompletionItemKind.Variable
17 | case 'module':
18 | return CompletionItemKind.Module
19 | }
20 | return CompletionItemKind.Property
21 | }
22 |
23 | export function complKind2cmakeType(kind: CompletionItemKind): string {
24 | switch (kind) {
25 | case CompletionItemKind.Function:
26 | return 'function'
27 | case CompletionItemKind.Variable:
28 | return 'variable'
29 | case CompletionItemKind.Module:
30 | return 'module'
31 | }
32 | return 'property'
33 | }
34 |
35 | // Simple helper function that invoke the CMAKE executable and return a promise
36 | // with stdout
37 | export async function cmake(args: string[]): Promise {
38 | return new Promise((resolve, reject) => {
39 | const cmake_path = getConfig('cmakePath')
40 | const cmake_args = parseCmdArgs(cmake_path)
41 | const cmd = child_process.spawn(
42 | cmake_args[0],
43 | cmake_args
44 | .slice(1, cmake_args.length)
45 | .concat(args.map((arg) => arg.replace(/\r/gm, '')))
46 | )
47 | let stdout = ''
48 | cmd.stdout.on('data', (data) => {
49 | const txt: string = data.toString()
50 | stdout += txt.replace(/\r/gm, '')
51 | })
52 | cmd.on('error', () => {
53 | reject()
54 | })
55 | cmd.on('exit', () => resolve(stdout))
56 | })
57 | }
58 |
59 | // return the cmake command list
60 | function cmake_help_command_list(): Promise {
61 | return cmake(['--help-command-list'])
62 | }
63 |
64 | function cmake_help_command(name: string): Thenable {
65 | return cmake_help_command_list()
66 | .then(
67 | (result: string) => {
68 | const contains = result.indexOf(name) > -1
69 | return new Promise((resolve, reject) => {
70 | if (contains) {
71 | resolve(name)
72 | } else {
73 | reject('not found')
74 | }
75 | })
76 | },
77 | () => {}
78 | )
79 | .then((n: string) => {
80 | return cmake(['--help-command', n])
81 | }, null)
82 | }
83 |
84 | function cmake_help_variable_list(): Promise {
85 | return cmake(['--help-variable-list'])
86 | }
87 |
88 | function cmake_help_variable(name: string): Promise {
89 | return cmake_help_variable_list()
90 | .then(
91 | (result: string) => {
92 | const contains = result.indexOf(name) > -1
93 | return new Promise((resolve, reject) => {
94 | if (contains) {
95 | resolve(name)
96 | } else {
97 | reject('not found')
98 | }
99 | })
100 | },
101 | () => {}
102 | )
103 | .then((name: string) => cmake(['--help-variable', name]), null)
104 | }
105 |
106 | function cmake_help_property_list(): Promise {
107 | return cmake(['--help-property-list'])
108 | }
109 |
110 | function cmake_help_property(name: string): Promise {
111 | return cmake_help_property_list()
112 | .then(
113 | (result: string) => {
114 | const contains = result.indexOf(name) > -1
115 | return new Promise((resolve, reject) => {
116 | if (contains) {
117 | resolve(name)
118 | } else {
119 | reject('not found')
120 | }
121 | })
122 | },
123 | () => {}
124 | )
125 | .then((name: string) => cmake(['--help-property', name]), null)
126 | }
127 |
128 | function cmake_help_module_list(): Promise {
129 | return cmake(['--help-module-list'])
130 | }
131 |
132 | function cmake_help_module(name: string): Promise {
133 | return cmake_help_module_list()
134 | .then(
135 | (result: string) => {
136 | const contains = result.indexOf(name) > -1
137 | return new Promise((resolve, reject) => {
138 | if (contains) {
139 | resolve(name)
140 | } else {
141 | reject('not found')
142 | }
143 | })
144 | },
145 | () => {}
146 | )
147 | .then((name: string) => cmake(['--help-module', name]), null)
148 | }
149 |
150 | export function cmake_help_all(): any {
151 | const promises = {
152 | function: (name: string) => {
153 | return cmake_help_command(name)
154 | },
155 | module: (name: string) => {
156 | return cmake_help_module(name)
157 | },
158 | variable: (name: string) => {
159 | return cmake_help_variable(name)
160 | },
161 | property: (name: string) => {
162 | return cmake_help_property(name)
163 | },
164 | }
165 | return promises
166 | }
167 |
168 | function suggestionsHelper(
169 | cmake_cmd: Promise,
170 | currentWord: string,
171 | type: string,
172 | insertText,
173 | matchPredicate
174 | ): Thenable {
175 | return new Promise((resolve, reject) => {
176 | cmake_cmd
177 | .then((stdout: string) => {
178 | const commands = stdout
179 | .split('\n')
180 | .filter((v) => matchPredicate(v, currentWord))
181 | if (commands.length > 0) {
182 | const suggestions = commands.map((command_name) => {
183 | const item: CompletionItem = { label: command_name }
184 | item.kind = cmakeType2complKind(type)
185 | if (insertText == null || insertText == '') {
186 | item.insertText = command_name
187 | } else {
188 | item.insertTextFormat = InsertTextFormat.Snippet
189 | item.insertText = insertText(command_name)
190 | }
191 | return item
192 | })
193 | resolve(suggestions)
194 | } else {
195 | resolve([])
196 | }
197 | })
198 | .catch((err) => reject(err))
199 | })
200 | }
201 |
202 | function cmModuleInsertText(module: string): string {
203 | if (module.indexOf('Find') == 0) {
204 | return 'find_package(' + module.replace('Find', '') + '${1: REQUIRED})'
205 | } else {
206 | return 'include(' + module + ')'
207 | }
208 | }
209 |
210 | function cmFunctionInsertText(func: string): string {
211 | const scoped_func = ['if', 'function', 'while', 'macro', 'foreach']
212 | const is_scoped = scoped_func.reduceRight(
213 | (prev, name) => prev || func == name,
214 | false
215 | )
216 | if (is_scoped) {
217 | return func + '(${1})\n\t\nend' + func + '(${1})\n'
218 | } else {
219 | return func + '(${1})'
220 | }
221 | }
222 |
223 | function cmVariableInsertText(variable: string): string {
224 | return variable.replace(/<(.*)>/g, '${1:<$1>}')
225 | }
226 |
227 | function cmPropetryInsertText(variable: string): string {
228 | return variable.replace(/<(.*)>/g, '${1:<$1>}')
229 | }
230 |
231 | export function cmCommandsSuggestions(
232 | currentWord: string
233 | ): Thenable {
234 | const cmd = cmake_help_command_list()
235 | return suggestionsHelper(
236 | cmd,
237 | currentWord,
238 | 'function',
239 | cmFunctionInsertText,
240 | strContains
241 | )
242 | }
243 |
244 | export function cmVariablesSuggestions(
245 | currentWord: string
246 | ): Thenable {
247 | const cmd = cmake_help_variable_list()
248 | return suggestionsHelper(
249 | cmd,
250 | currentWord,
251 | 'variable',
252 | cmVariableInsertText,
253 | strContains
254 | )
255 | }
256 |
257 | export function cmPropertiesSuggestions(
258 | currentWord: string
259 | ): Thenable {
260 | const cmd = cmake_help_property_list()
261 | return suggestionsHelper(
262 | cmd,
263 | currentWord,
264 | 'property',
265 | cmPropetryInsertText,
266 | strContains
267 | )
268 | }
269 |
270 | export function cmModulesSuggestions(
271 | currentWord: string
272 | ): Thenable {
273 | const cmd = cmake_help_module_list()
274 | return suggestionsHelper(
275 | cmd,
276 | currentWord,
277 | 'module',
278 | cmModuleInsertText,
279 | strContains
280 | )
281 | }
282 |
283 | export function cmCommandsSuggestionsExact(
284 | currentWord: string
285 | ): Thenable {
286 | const cmd = cmake_help_command_list()
287 | return suggestionsHelper(
288 | cmd,
289 | currentWord,
290 | 'function',
291 | cmFunctionInsertText,
292 | strEquals
293 | )
294 | }
295 |
296 | export function cmVariablesSuggestionsExact(
297 | currentWord: string
298 | ): Thenable {
299 | const cmd = cmake_help_variable_list()
300 | return suggestionsHelper(
301 | cmd,
302 | currentWord,
303 | 'variable',
304 | cmVariableInsertText,
305 | strEquals
306 | )
307 | }
308 |
309 | export function cmPropertiesSuggestionsExact(
310 | currentWord: string
311 | ): Thenable {
312 | const cmd = cmake_help_property_list()
313 | return suggestionsHelper(
314 | cmd,
315 | currentWord,
316 | 'property',
317 | cmPropetryInsertText,
318 | strEquals
319 | )
320 | }
321 |
322 | export function cmModulesSuggestionsExact(
323 | currentWord: string
324 | ): Thenable {
325 | const cmd = cmake_help_module_list()
326 | return suggestionsHelper(cmd, currentWord, 'module', cmModuleInsertText, strEquals)
327 | }
328 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { commands, languages, services, window } from 'coc.nvim'
2 | import { checkCommand } from './util'
3 | import getConfig from './config'
4 | import onLineHelp from './provider/command'
5 | import CMakeExtraInfoProvider from './provider/hover'
6 | import CMakeCompletionProvider from './provider/completion'
7 | import CMakeFormattingEditProvider from './provider/format'
8 | import CMakeLanguageClient from './client'
9 |
10 | export async function activate(): Promise {
11 | if (!(await checkCommand(getConfig('cmakePath')))) {
12 | window.showMessage(
13 | 'Install cmake or specify its path using `cmake.cmakePath`.',
14 | 'error'
15 | )
16 | return
17 | }
18 |
19 | commands.registerCommand(
20 | 'cmake.onlineHelp',
21 | async () => await onLineHelp()
22 | )
23 |
24 | if (getConfig('lsp.enable')) {
25 | services.registLanguageClient(
26 | new CMakeLanguageClient()
27 | )
28 | return
29 | }
30 |
31 | languages.registerHoverProvider(
32 | ['cmake'],
33 | new CMakeExtraInfoProvider()
34 | )
35 |
36 | languages.registerDocumentFormatProvider(
37 | ['cmake'],
38 | new CMakeFormattingEditProvider()
39 | )
40 |
41 | languages.registerCompletionItemProvider(
42 | 'coc-cmake',
43 | 'CMAKE',
44 | 'cmake',
45 | new CMakeCompletionProvider()
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/src/provider/command.ts:
--------------------------------------------------------------------------------
1 | import { window, workspace } from 'coc.nvim'
2 | import {
3 | complKind2cmakeType,
4 | cmCommandsSuggestionsExact,
5 | cmModulesSuggestionsExact,
6 | cmPropertiesSuggestionsExact,
7 | cmVariablesSuggestionsExact,
8 | cmake,
9 | } from '../core'
10 | import opener from 'opener'
11 |
12 | // Show Tooltip on over
13 | export default async function onLineHelp(): Promise {
14 | const document = await workspace.document
15 | const position = await window.getCursorPosition()
16 | const range = document.getWordRangeAtPosition(position)
17 | let currentWord = document.textDocument.getText(range)
18 |
19 | if (range && range.start.character < position.character) {
20 | const word = document.textDocument.getText(range)
21 | currentWord = word
22 | }
23 |
24 | let result = await window.requestInput(
25 | 'Search on Cmake online documentation',
26 | currentWord
27 | )
28 | if (result != null) {
29 | if (result.length === 0) {
30 | result = currentWord
31 | }
32 | if (result != '') {
33 | await cmake_online_help(result)
34 | }
35 | }
36 | }
37 |
38 | export async function cmake_online_help(search: string): Promise {
39 | const url = await cmake_help_url()
40 | const v2x = url.endsWith('html') // cmake < 3.0
41 | return Promise.all([
42 | cmCommandsSuggestionsExact(search),
43 | cmVariablesSuggestionsExact(search),
44 | cmModulesSuggestionsExact(search),
45 | cmPropertiesSuggestionsExact(search),
46 | ]).then((results) => {
47 | const suggestions = Array.prototype.concat.apply([], results)
48 |
49 | if (suggestions.length == 0) {
50 | search = search.replace(/[<>]/g, '')
51 | if (v2x || search.length == 0) {
52 | opener(url)
53 | } else {
54 | opener(`${url}search.html?q=${search}&check_keywords=yes&area=default`)
55 | }
56 | } else {
57 | const suggestion = suggestions[0]
58 | let type = complKind2cmakeType(suggestion.kind)
59 | if (type == 'property') {
60 | if (v2x) {
61 | opener(url)
62 | } else {
63 | // TODO : needs to filter properties per scope to detect the right URL
64 | opener(`${url}search.html?q=${search}&check_keywords=yes&area=default`)
65 | }
66 | } else {
67 | if (type == 'function') {
68 | type = 'command'
69 | }
70 | search = search.replace(/[<>]/g, '')
71 | if (v2x) {
72 | opener(`${url}#${type}:${search}`)
73 | } else {
74 | opener(`${url}${type}/${search}.html`)
75 | }
76 | }
77 | }
78 | })
79 | }
80 |
81 | // Return the url for the online help based on the cmake executable binary used
82 | export async function cmake_help_url(): Promise {
83 | const base_url = 'https://cmake.org/cmake/help'
84 | let version = await cmake_version()
85 | if (version.length > 0) {
86 | if (version >= '3.0') {
87 | const re = /(\d+.\d+).\d+/
88 | version = version.replace(re, '$1/')
89 | } else {
90 | const older_versions = [
91 | '2.8.12',
92 | '2.8.11',
93 | '2.8.10',
94 | '2.8.9',
95 | '2.8.8',
96 | '2.8.7',
97 | '2.8.6',
98 | '2.8.5',
99 | '2.8.4',
100 | '2.8.3',
101 | '2.8.2',
102 | '2.8.1',
103 | '2.8.0',
104 | '2.6',
105 | ]
106 | if (older_versions.indexOf(version) == -1) {
107 | version = 'latest/'
108 | } else {
109 | version = version + '/cmake.html'
110 | }
111 | }
112 | } else {
113 | version = 'latest/'
114 | }
115 | return base_url + '/v' + version
116 | }
117 |
118 | async function cmake_version(): Promise {
119 | const cmd_output = await cmake(['--version'])
120 | const re = /cmake\s+version\s+(\d+.\d+.\d+)/
121 | if (re.test(cmd_output)) {
122 | const result = re.exec(cmd_output)
123 | return result[1]
124 | }
125 | return ''
126 | }
127 |
--------------------------------------------------------------------------------
/src/provider/completion.ts:
--------------------------------------------------------------------------------
1 | import {
2 | workspace,
3 | CompletionItemProvider,
4 | Thenable,
5 | CompletionItem,
6 | TextDocument,
7 | Position,
8 | ProviderResult,
9 | } from 'coc.nvim'
10 | import {
11 | complKind2cmakeType,
12 | cmake_help_all,
13 | cmCommandsSuggestions,
14 | cmModulesSuggestions,
15 | cmPropertiesSuggestions,
16 | cmVariablesSuggestions,
17 | } from '../core'
18 |
19 | export default class CMakeCompletionProvider implements CompletionItemProvider {
20 | provideCompletionItems(
21 | document: TextDocument,
22 | position: Position
23 | ): ProviderResult {
24 | const doc = workspace.getDocument(document.uri)
25 | if (!doc) return []
26 | const wordRange = doc.getWordRangeAtPosition(
27 | Position.create(position.line, position.character - 1)
28 | )
29 | if (!wordRange) return []
30 | const text = document.getText(wordRange)
31 |
32 | return new Promise((resolve, reject) => {
33 | Promise.all([
34 | cmCommandsSuggestions(text),
35 | cmVariablesSuggestions(text),
36 | cmPropertiesSuggestions(text),
37 | cmModulesSuggestions(text),
38 | ])
39 | .then((results) => {
40 | const suggestions = Array.prototype.concat.apply([], results)
41 | resolve(suggestions)
42 | })
43 | .catch((err) => {
44 | reject(err)
45 | })
46 | })
47 | }
48 |
49 | public resolveCompletionItem(item: CompletionItem): Thenable {
50 | const promises = cmake_help_all()
51 | const type = complKind2cmakeType(item.kind)
52 | return promises[type](item.label).then((result: string) => {
53 | item.documentation = result.split('\n')[3]
54 | return item
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/provider/format.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Uri,
3 | Range,
4 | ProviderResult,
5 | TextDocument,
6 | TextEdit,
7 | workspace,
8 | DocumentFormattingEditProvider,
9 | DocumentRangeFormattingEditProvider,
10 | window,
11 | } from 'coc.nvim'
12 | import getConfig from '../config'
13 | import { checkCommand, fsCreateTmpfile, fsWriteFile, runCommand } from '../util'
14 |
15 | export default class CMakeFormattingEditProvider
16 | implements DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider {
17 | provideDocumentFormattingEdits(
18 | document: TextDocument
19 | ): ProviderResult {
20 | return this._providerEdits(document)
21 | }
22 |
23 | provideDocumentRangeFormattingEdits(
24 | document: TextDocument,
25 | range: Range
26 | ): ProviderResult {
27 | return this._providerEdits(document, range)
28 | }
29 |
30 | async _providerEdits(document: TextDocument, range?: Range): Promise {
31 | workspace.nvim.command('update')
32 | const replacementText = await format(document, range)
33 | if (replacementText?.length == 0) return []
34 |
35 | if (!range) range = wholeRange(document)
36 |
37 | return [TextEdit.replace(range, replacementText)]
38 | }
39 | }
40 |
41 | async function format(document: TextDocument, range?: Range): Promise {
42 | const formatter = getConfig('formatter')
43 | const args = Array.from(getConfig>('formatter_args'))
44 | if (!range) {
45 | args.push(Uri.parse(document.uri).fsPath)
46 | } else {
47 | // write the selected code into a tmp file and invoke formatter
48 | const tmpfile = await fsCreateTmpfile()
49 | const text = document.getText(range)
50 | await fsWriteFile(tmpfile, text)
51 | args.push(tmpfile)
52 | }
53 |
54 | try {
55 | return await runCommand(formatter, args)
56 | } catch {
57 | const formatterExists = await checkCommand(formatter)
58 | if (!formatterExists) {
59 | const install = await window.showPrompt(
60 | 'cmake-format is not installed, install it?'
61 | )
62 | if (install) {
63 | await window.openTerminal('pip3 install cmake-format')
64 | }
65 | return ''
66 | }
67 | }
68 | }
69 |
70 | function wholeRange(document: TextDocument): Range {
71 | const doc = workspace.getDocument(document.uri)
72 | return Range.create(
73 | {
74 | line: 0,
75 | character: 0,
76 | },
77 | {
78 | line: document.lineCount - 1,
79 | character: doc.getline(doc.lineCount - 1).length,
80 | }
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/src/provider/hover.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CompletionItem,
3 | Hover,
4 | HoverProvider,
5 | Position,
6 | TextDocument,
7 | workspace
8 | } from "coc.nvim"
9 | import {
10 | complKind2cmakeType,
11 | cmake_help_all,
12 | cmCommandsSuggestionsExact,
13 | cmModulesSuggestionsExact,
14 | cmPropertiesSuggestionsExact,
15 | cmVariablesSuggestionsExact
16 | } from "../core"
17 |
18 | export default class CMakeExtraInfoProvider implements HoverProvider {
19 |
20 | public async provideHover(document: TextDocument, position: Position): Promise {
21 | const doc = workspace.getDocument(document.uri)
22 | if (!doc) return null
23 | const wordRange = doc.getWordRangeAtPosition(position)
24 | if (!wordRange) return null
25 | const text = document.getText(wordRange) || ''
26 | if (!text) { return null }
27 | const promises = cmake_help_all()
28 |
29 | return Promise.all([
30 | cmCommandsSuggestionsExact(text),
31 | cmVariablesSuggestionsExact(text),
32 | cmModulesSuggestionsExact(text),
33 | cmPropertiesSuggestionsExact(text),
34 | ]).then(results => {
35 | const suggestions = Array.prototype.concat.apply([], results)
36 | if (suggestions.length == 0) {
37 | return null
38 | }
39 | const suggestion: CompletionItem = suggestions[0]
40 |
41 | return promises[complKind2cmakeType(suggestion.kind)](suggestion.label).then((result: string) => {
42 | let lines = result.split('\n')
43 | lines = lines.slice(2, lines.length)
44 | const hover: Hover = {
45 | contents: {
46 | kind: 'markdown',
47 | value: lines.join('\n')
48 | }
49 | }
50 | return hover
51 | })
52 | })
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/types/pkg-config.d.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /**
3 | * This file was automatically generated by json-schema-to-typescript.
4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
5 | * and run json-schema-to-typescript to regenerate this file.
6 | */
7 |
8 | export interface CMake {
9 | /**
10 | * Path to CMake generator executable
11 | */
12 | 'cmake.cmakePath'?: string;
13 | /**
14 | * Path to [cmake-format](https://github.com/cheshirekow/cmake_format)
15 | */
16 | 'cmake.formatter'?: string;
17 | /**
18 | * Additional arguments to be passed down to the formatter
19 | */
20 | 'cmake.formatter_args'?: string[];
21 | /**
22 | * Enable language server(https://github.com/regen100/cmake-language-server), Notice that the functionality(completion, formatting, etc.) of lsp and extension builtin can not coexist
23 | */
24 | 'cmake.lsp.enable'?: boolean;
25 | /**
26 | * Path to [cmake-language-server](https://github.com/regen100/cmake-language-server)
27 | */
28 | 'cmake.lsp.serverPath'?: string;
29 | /**
30 | * See https://github.com/regen100/cmake-language-server#configuration
31 | */
32 | 'cmake.lsp.buildDirectory'?: string;
33 | [k: string]: unknown;
34 | }
35 |
--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import child_process = require('child_process')
3 | import tmp from 'tmp'
4 | import commandExists from 'command-exists'
5 |
6 | export function strContains(word: string, pattern: string): boolean {
7 | return word.indexOf(pattern) > -1
8 | }
9 |
10 | export function strEquals(word: string, pattern: string): boolean {
11 | return word.toLowerCase() == pattern.toLowerCase()
12 | }
13 |
14 | // https://stackoverflow.com/questions/13796594/how-to-split-string-into-arguments-and-options-in-javascript
15 | export function parseCmdArgs(text: string): string[] {
16 | const re = /^"[^"]*"$/ // Check if argument is surrounded with double-quotes
17 | const re2 = /^([^"]|[^"].*?[^"])$/ // Check if argument is NOT surrounded with double-quotes
18 |
19 | const arr = []
20 | let argPart = null
21 |
22 | // tslint:disable-next-line: no-unused-expression
23 | text &&
24 | text.split(' ').forEach((arg) => {
25 | if ((re.test(arg) || re2.test(arg)) && !argPart) {
26 | arr.push(arg)
27 | } else {
28 | argPart = argPart ? argPart + ' ' + arg : arg
29 | // If part is complete (ends with a double quote), we can add it to the array
30 | if (/"$/.test(argPart)) {
31 | arr.push(argPart)
32 | argPart = null
33 | }
34 | }
35 | })
36 | return arr
37 | }
38 |
39 | export async function fsCreateTmpfile(): Promise {
40 | return new Promise((resolve, reject) => {
41 | tmp.file((err, path) => {
42 | if (err) reject(new Error('Failed to create a tmp file'))
43 | resolve(path)
44 | })
45 | })
46 | }
47 |
48 | export async function fsWriteFile(fullpath: string, content: string): Promise {
49 | return new Promise((resolve, reject) => {
50 | fs.writeFile(fullpath, content, 'utf8', (err) => {
51 | if (err) reject()
52 | resolve()
53 | })
54 | })
55 | }
56 |
57 | export async function runCommand(command: string, args?: string[]): Promise {
58 | return new Promise((resolve, reject) => {
59 | child_process.execFile(command, args, (error, stdout) => {
60 | if (error) reject(error)
61 | resolve(stdout)
62 | })
63 | })
64 | }
65 |
66 | export async function checkCommand(command: string): Promise {
67 | return new Promise((resolve) => {
68 | commandExists(command, (_err, exists) => {
69 | resolve(exists)
70 | })
71 | })
72 | }
73 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/@voldikss/tsconfig/tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": false,
5 | "outDir": "lib",
6 | "target": "es2015",
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "lib": ["es2018"],
10 | "plugins": []
11 | },
12 | "include": ["src"],
13 | "exclude": []
14 | }
15 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './src/index.ts',
5 | target: 'node',
6 | mode: 'none',
7 | resolve: {
8 | mainFields: ['module', 'main'],
9 | extensions: ['.js', '.ts']
10 | },
11 | externals: {
12 | 'coc.nvim': 'commonjs coc.nvim'
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.ts$/,
18 | exclude: /node_modules/,
19 | use: [
20 | {
21 | loader: 'ts-loader',
22 | options: {
23 | compilerOptions: {
24 | sourceMap: true
25 | }
26 | }
27 | }
28 | ]
29 | }
30 | ]
31 | },
32 | output: {
33 | path: path.join(__dirname, 'lib'),
34 | filename: 'index.js',
35 | libraryTarget: 'commonjs'
36 | },
37 | plugins: [],
38 | node: {
39 | __dirname: false,
40 | __filename: false
41 | }
42 | };
43 |
--------------------------------------------------------------------------------