2901 lines
92 KiB
JavaScript
2901 lines
92 KiB
JavaScript
var __create = Object.create;
|
|
var __defProp = Object.defineProperty;
|
|
var __getProtoOf = Object.getPrototypeOf;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
|
|
var __commonJS = (callback, module2) => () => {
|
|
if (!module2) {
|
|
module2 = {exports: {}};
|
|
callback(module2.exports, module2);
|
|
}
|
|
return module2.exports;
|
|
};
|
|
var __export = (target, all) => {
|
|
__markAsModule(target);
|
|
for (var name in all)
|
|
__defProp(target, name, {get: all[name], enumerable: true});
|
|
};
|
|
var __exportStar = (target, module2, desc) => {
|
|
__markAsModule(target);
|
|
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
for (let key of __getOwnPropNames(module2))
|
|
if (!__hasOwnProp.call(target, key) && key !== "default")
|
|
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
|
|
}
|
|
return target;
|
|
};
|
|
var __toModule = (module2) => {
|
|
if (module2 && module2.__esModule)
|
|
return module2;
|
|
return __exportStar(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", {value: module2, enumerable: true}), module2);
|
|
};
|
|
|
|
// node_modules/pify/index.js
|
|
var require_pify = __commonJS((exports2, module2) => {
|
|
"use strict";
|
|
var processFn = (fn, options, proxy, unwrapped) => function(...arguments_) {
|
|
const P = options.promiseModule;
|
|
return new P((resolve, reject) => {
|
|
if (options.multiArgs) {
|
|
arguments_.push((...result) => {
|
|
if (options.errorFirst) {
|
|
if (result[0]) {
|
|
reject(result);
|
|
} else {
|
|
result.shift();
|
|
resolve(result);
|
|
}
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
});
|
|
} else if (options.errorFirst) {
|
|
arguments_.push((error, result) => {
|
|
if (error) {
|
|
reject(error);
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
});
|
|
} else {
|
|
arguments_.push(resolve);
|
|
}
|
|
const self = this === proxy ? unwrapped : this;
|
|
Reflect.apply(fn, self, arguments_);
|
|
});
|
|
};
|
|
var filterCache = new WeakMap();
|
|
module2.exports = (input, options) => {
|
|
options = {
|
|
exclude: [/.+(?:Sync|Stream)$/],
|
|
errorFirst: true,
|
|
promiseModule: Promise,
|
|
...options
|
|
};
|
|
const objectType = typeof input;
|
|
if (!(input !== null && (objectType === "object" || objectType === "function"))) {
|
|
throw new TypeError(`Expected \`input\` to be a \`Function\` or \`Object\`, got \`${input === null ? "null" : objectType}\``);
|
|
}
|
|
const filter = (target, key) => {
|
|
let cached = filterCache.get(target);
|
|
if (!cached) {
|
|
cached = {};
|
|
filterCache.set(target, cached);
|
|
}
|
|
if (key in cached) {
|
|
return cached[key];
|
|
}
|
|
const match = (pattern) => typeof pattern === "string" || typeof key === "symbol" ? key === pattern : pattern.test(key);
|
|
const desc = Reflect.getOwnPropertyDescriptor(target, key);
|
|
const writableOrConfigurableOwn = desc === void 0 || desc.writable || desc.configurable;
|
|
const included = options.include ? options.include.some(match) : !options.exclude.some(match);
|
|
const shouldFilter = included && writableOrConfigurableOwn;
|
|
cached[key] = shouldFilter;
|
|
return shouldFilter;
|
|
};
|
|
const cache = new WeakMap();
|
|
const proxy = new Proxy(input, {
|
|
apply(target, thisArg, args) {
|
|
const cached = cache.get(target);
|
|
if (cached) {
|
|
return Reflect.apply(cached, thisArg, args);
|
|
}
|
|
const pified = options.excludeMain ? target : processFn(target, options, proxy, target);
|
|
cache.set(target, pified);
|
|
return Reflect.apply(pified, thisArg, args);
|
|
},
|
|
get(target, key) {
|
|
const property = target[key];
|
|
if (!filter(target, key) || property === Function.prototype[key]) {
|
|
return property;
|
|
}
|
|
const cached = cache.get(property);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
if (typeof property === "function") {
|
|
const pified = processFn(property, options, proxy, target);
|
|
cache.set(property, pified);
|
|
return pified;
|
|
}
|
|
return property;
|
|
}
|
|
});
|
|
return proxy;
|
|
};
|
|
});
|
|
|
|
// src/index.ts
|
|
__export(exports, {
|
|
activate: () => activate
|
|
});
|
|
var import_coc10 = __toModule(require("coc.nvim"));
|
|
var import_fs6 = __toModule(require("fs"));
|
|
var import_os4 = __toModule(require("os"));
|
|
var import_path5 = __toModule(require("path"));
|
|
var import_util9 = __toModule(require("util"));
|
|
|
|
// src/languages.ts
|
|
var import_coc = __toModule(require("coc.nvim"));
|
|
|
|
// src/util.ts
|
|
var import_pify = __toModule(require_pify());
|
|
var import_fs = __toModule(require("fs"));
|
|
var import_crypto = __toModule(require("crypto"));
|
|
var BASE64 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
|
|
function tostr(bytes) {
|
|
let r = [];
|
|
let i;
|
|
for (i = 0; i < bytes.length; i++) {
|
|
r.push(BASE64[bytes[i] % 64]);
|
|
}
|
|
return r.join("");
|
|
}
|
|
function uid() {
|
|
return tostr(import_crypto.default.randomBytes(10));
|
|
}
|
|
async function statAsync(filepath) {
|
|
try {
|
|
return await import_pify.default(import_fs.default.stat)(filepath);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
async function readFileAsync(fullpath, encoding = "utf8") {
|
|
return await import_pify.default(import_fs.default.readFile)(fullpath, encoding);
|
|
}
|
|
async function readdirAsync(filepath) {
|
|
try {
|
|
return await import_pify.default(import_fs.default.readdir)(filepath);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
function headTail(line) {
|
|
line = line.trim();
|
|
let ms = line.match(/^(\S+)\s+(.*)/);
|
|
if (!ms)
|
|
return [line, ""];
|
|
return [ms[1], ms[2]];
|
|
}
|
|
function distinct(array, keyFn) {
|
|
if (!keyFn) {
|
|
return array.filter((element, position) => {
|
|
return array.indexOf(element) === position;
|
|
});
|
|
}
|
|
const seen = Object.create(null);
|
|
return array.filter((elem) => {
|
|
const key = keyFn(elem);
|
|
if (seen[key]) {
|
|
return false;
|
|
}
|
|
seen[key] = true;
|
|
return true;
|
|
});
|
|
}
|
|
var conditionRe = /\(\?\(\?:\w+\).+\|/;
|
|
var bellRe = /\\a/;
|
|
var commentRe = /\(\?#.*?\)/;
|
|
var stringStartRe = /\\A/;
|
|
var namedCaptureRe = /\(\?P<\w+>.*?\)/;
|
|
var namedReferenceRe = /\(\?P=(\w+)\)/;
|
|
var braceRe = /\^\]/;
|
|
var regex = new RegExp(`${bellRe.source}|${commentRe.source}|${stringStartRe.source}|${namedCaptureRe.source}|${namedReferenceRe.source}|${braceRe}`, "g");
|
|
function convertRegex(str) {
|
|
if (str.indexOf("\\z") !== -1) {
|
|
throw new Error("pattern \\z not supported");
|
|
}
|
|
if (str.indexOf("(?s)") !== -1) {
|
|
throw new Error("pattern (?s) not supported");
|
|
}
|
|
if (str.indexOf("(?x)") !== -1) {
|
|
throw new Error("pattern (?x) not supported");
|
|
}
|
|
if (str.indexOf("\n") !== -1) {
|
|
throw new Error("multiple line pattern not supported");
|
|
}
|
|
if (conditionRe.test(str)) {
|
|
throw new Error("condition pattern not supported");
|
|
}
|
|
return str.replace(regex, (match, p1) => {
|
|
if (match == "^]")
|
|
return "^\\]";
|
|
if (match == "\\a")
|
|
return "";
|
|
if (match.startsWith("(?#"))
|
|
return "";
|
|
if (match == "\\A")
|
|
return "^";
|
|
if (match.startsWith("(?P<"))
|
|
return "(?" + match.slice(3);
|
|
if (match.startsWith("(?P="))
|
|
return `\\k<${p1}>`;
|
|
return "";
|
|
});
|
|
}
|
|
function getRegexText(prefix) {
|
|
if (prefix.startsWith("^"))
|
|
prefix = prefix.slice(1);
|
|
if (prefix.endsWith("$"))
|
|
prefix = prefix.slice(0, -1);
|
|
let content = prefix.replace(/\(.*\)\??/g, "");
|
|
content = content.replace(/\\/g, "");
|
|
return content;
|
|
}
|
|
function markdownBlock(code, filetype) {
|
|
filetype = filetype == "javascriptreact" ? "javascript" : filetype;
|
|
filetype = filetype == "typescriptreact" ? "typescript" : filetype;
|
|
return "``` " + filetype + "\n" + code + "\n```";
|
|
}
|
|
|
|
// src/languages.ts
|
|
var codesMap = new Map();
|
|
codesMap.set(1, "invalid snippet line, trigger requried.");
|
|
codesMap.set(2, 'invalid snippet option, option "$1" not supported.');
|
|
codesMap.set(3, "invalid python expression, $1");
|
|
codesMap.set(4, "invalid code interpolation, #! not supported.");
|
|
var validOptions = ["b", "i", "w", "r", "e", "A"];
|
|
var LanguageProvider = class {
|
|
constructor(channel, trace = "error") {
|
|
this.channel = channel;
|
|
this.trace = trace;
|
|
this.disposables = [];
|
|
this.collection = import_coc.languages.createDiagnosticCollection("snippets");
|
|
for (let doc of import_coc.workspace.documents) {
|
|
if (this.shouldValidate(doc.uri)) {
|
|
this.validate(doc.uri, doc.getDocumentContent()).catch((e) => {
|
|
channel.appendLine(`[Error ${new Date().toLocaleTimeString()}]: ${e.message}`);
|
|
});
|
|
}
|
|
}
|
|
import_coc.workspace.onDidOpenTextDocument(async (textDocument) => {
|
|
let doc = import_coc.workspace.getDocument(textDocument.uri);
|
|
if (!this.shouldValidate(doc.uri))
|
|
return;
|
|
await this.validate(doc.uri, doc.getDocumentContent());
|
|
}, null, this.disposables);
|
|
import_coc.workspace.onDidChangeTextDocument(async (ev) => {
|
|
let doc = import_coc.workspace.getDocument(ev.textDocument.uri);
|
|
if (!doc || !this.shouldValidate(doc.uri))
|
|
return;
|
|
await this.validate(doc.uri, doc.getDocumentContent());
|
|
}, null, this.disposables);
|
|
import_coc.workspace.onDidCloseTextDocument((e) => {
|
|
this.collection.delete(e.uri);
|
|
}, null, this.disposables);
|
|
}
|
|
shouldValidate(uri) {
|
|
return uri.endsWith(".snippets");
|
|
}
|
|
async validate(uri, content) {
|
|
let lines = content.split("\n");
|
|
let diagnostics = [];
|
|
let curr = 0;
|
|
for (let line of lines) {
|
|
if (/^snippet\s*$/.test(line)) {
|
|
let range = import_coc.Range.create(curr, 0, curr, line.length);
|
|
diagnostics.push(import_coc.Diagnostic.create(range, codesMap.get(1), import_coc.DiagnosticSeverity.Error, 1));
|
|
continue;
|
|
}
|
|
if (line.startsWith("snippet ")) {
|
|
let content2 = headTail(line)[1];
|
|
let ms = content2.match(/^(.+?)(?:\s+(?:"(.*?)")?(?:\s+"(.*?)")?(?:\s+(\w+))?)?$/);
|
|
let prefix = ms[1];
|
|
if (prefix.length > 2 && prefix[0] == prefix[prefix.length - 1] && !/\w/.test(prefix[0])) {
|
|
prefix = prefix.slice(1, prefix.length - 1);
|
|
}
|
|
let option = ms[4] || "";
|
|
let isExpression = option.indexOf("r") !== -1;
|
|
let startCharacter = line.length - option.length;
|
|
for (let ch of option) {
|
|
if (validOptions.indexOf(ch) == -1) {
|
|
let range = import_coc.Range.create(curr, startCharacter, curr, startCharacter + 1);
|
|
let message = codesMap.get(2).replace("$1", ch);
|
|
diagnostics.push(import_coc.Diagnostic.create(range, message, import_coc.DiagnosticSeverity.Error, 2));
|
|
}
|
|
startCharacter = startCharacter + 1;
|
|
}
|
|
if (isExpression) {
|
|
try {
|
|
convertRegex(prefix);
|
|
} catch (e) {
|
|
let start = line.indexOf(prefix);
|
|
let range = import_coc.Range.create(curr, start, curr, start + prefix.length);
|
|
let message = codesMap.get(3).replace("$1", e.message);
|
|
diagnostics.push(import_coc.Diagnostic.create(range, message, import_coc.DiagnosticSeverity.Error, 3));
|
|
}
|
|
}
|
|
} else {
|
|
let idx = line.indexOf("`#!");
|
|
if (idx !== -1) {
|
|
let range = import_coc.Range.create(curr, idx, curr, idx + 3);
|
|
let message = codesMap.get(4);
|
|
diagnostics.push(import_coc.Diagnostic.create(range, message, import_coc.DiagnosticSeverity.Error, 4));
|
|
}
|
|
}
|
|
curr++;
|
|
}
|
|
if (this.trace == "verbose") {
|
|
this.channel.appendLine(`[Debug ${new Date().toLocaleTimeString()}] diagnostics of ${uri} -> ${JSON.stringify(diagnostics)}`);
|
|
}
|
|
this.collection.set(uri, diagnostics);
|
|
}
|
|
provideCompletionItems(_document, position, _token, context) {
|
|
let {input, col} = context.option;
|
|
if (context.triggerCharacter == "$") {
|
|
return [{
|
|
label: "$VISUAL",
|
|
kind: import_coc.CompletionItemKind.Snippet,
|
|
detail: "${VISUAL}",
|
|
insertTextFormat: import_coc.InsertTextFormat.Snippet,
|
|
textEdit: {
|
|
range: import_coc.Range.create(position.line, position.character - 1, position.line, position.character),
|
|
newText: "\\${VISUAL${1::default}\\}"
|
|
}
|
|
}];
|
|
}
|
|
if (col == 0 && "snippet".startsWith(input)) {
|
|
return [{
|
|
label: "snippet",
|
|
kind: import_coc.CompletionItemKind.Snippet,
|
|
detail: "Snippet definition",
|
|
insertTextFormat: import_coc.InsertTextFormat.Snippet,
|
|
insertText: 'snippet ${1:Tab_trigger} "${2:Description}" ${3:b}\n$0\nendsnippet'
|
|
}];
|
|
}
|
|
return [];
|
|
}
|
|
async resolveCompletionItem(item) {
|
|
let text = item.insertText || item.textEdit.newText;
|
|
let snip = await Promise.resolve(import_coc.snippetManager.resolveSnippet(text));
|
|
item.documentation = {
|
|
kind: "markdown",
|
|
value: markdownBlock(snip.toString(), "snippets")
|
|
};
|
|
return item;
|
|
}
|
|
};
|
|
var languages_default = LanguageProvider;
|
|
|
|
// src/list/snippet.ts
|
|
var import_coc2 = __toModule(require("coc.nvim"));
|
|
var import_os = __toModule(require("os"));
|
|
var SnippetsList = class extends import_coc2.BasicList {
|
|
constructor(nvim, manager, mru) {
|
|
super(nvim);
|
|
this.manager = manager;
|
|
this.mru = mru;
|
|
this.name = "snippets";
|
|
this.description = "snippets list";
|
|
this.addLocationActions();
|
|
}
|
|
async loadItems(context) {
|
|
let {window: window4} = context;
|
|
let valid = await window4.valid;
|
|
if (!valid)
|
|
return;
|
|
let buf = await window4.buffer;
|
|
let doc = import_coc2.workspace.getDocument(buf.id);
|
|
if (!doc)
|
|
return [];
|
|
let snippets = await this.manager.getSnippets(doc.filetype);
|
|
let res = [];
|
|
for (let snip of snippets) {
|
|
let pos = import_coc2.Position.create(snip.lnum, 0);
|
|
let location = import_coc2.Location.create(import_coc2.Uri.file(snip.filepath).toString(), import_coc2.Range.create(pos, pos));
|
|
let prefix = snip.prefix;
|
|
if (prefix.length < 20) {
|
|
prefix = `${prefix}${" ".repeat(20 - prefix.length)}`;
|
|
}
|
|
res.push({
|
|
label: `${prefix} ${snip.description} ${snip.filepath.replace(import_os.default.homedir(), "~")}`,
|
|
filterText: `${snip.prefix} ${snip.description}`,
|
|
location
|
|
});
|
|
}
|
|
return res;
|
|
}
|
|
async doHighlight() {
|
|
let {nvim} = import_coc2.workspace;
|
|
nvim.pauseNotification();
|
|
nvim.command("syntax match CocSnippetsPrefix /\\v^\\S+/ contained containedin=CocSnippetsLine", true);
|
|
nvim.command("syntax match CocSnippetsFile /\\v\\t\\S+$/ contained containedin=CocSnippetsLine", true);
|
|
nvim.command("highlight default link CocSnippetsPrefix Identifier", true);
|
|
nvim.command("highlight default link CocSnippetsFile Comment", true);
|
|
await nvim.resumeNotification();
|
|
}
|
|
};
|
|
var snippet_default = SnippetsList;
|
|
|
|
// src/provider.ts
|
|
var import_coc3 = __toModule(require("coc.nvim"));
|
|
var import_path = __toModule(require("path"));
|
|
|
|
// src/types.ts
|
|
var TriggerKind;
|
|
(function(TriggerKind2) {
|
|
TriggerKind2[TriggerKind2["SpaceBefore"] = 0] = "SpaceBefore";
|
|
TriggerKind2[TriggerKind2["LineBegin"] = 1] = "LineBegin";
|
|
TriggerKind2[TriggerKind2["WordBoundary"] = 2] = "WordBoundary";
|
|
TriggerKind2[TriggerKind2["InWord"] = 3] = "InWord";
|
|
})(TriggerKind || (TriggerKind = {}));
|
|
|
|
// src/provider.ts
|
|
var ProviderManager = class {
|
|
constructor(channel) {
|
|
this.channel = channel;
|
|
this.providers = new Map();
|
|
}
|
|
regist(provider, name) {
|
|
this.providers.set(name, provider);
|
|
return import_coc3.Disposable.create(() => {
|
|
this.providers.delete(name);
|
|
});
|
|
}
|
|
get hasProvider() {
|
|
return this.providers.size > 0;
|
|
}
|
|
async init() {
|
|
let providers = Array.from(this.providers.values());
|
|
await Promise.all(providers.map((provider) => {
|
|
return provider.init();
|
|
})).catch((e) => {
|
|
this.appendError("init", e);
|
|
});
|
|
}
|
|
async getSnippets(filetype) {
|
|
let names = Array.from(this.providers.keys());
|
|
let list = [];
|
|
for (let name of names) {
|
|
let provider = this.providers.get(name);
|
|
try {
|
|
let snippets = await provider.getSnippets(filetype);
|
|
snippets.map((s) => s.provider = name);
|
|
list.push(...snippets);
|
|
} catch (e) {
|
|
this.appendError(`getSnippets of ${name}`, e);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
async getSnippetFiles(filetype) {
|
|
let files = [];
|
|
for (let [name, provider] of this.providers.entries()) {
|
|
try {
|
|
let res = await provider.getSnippetFiles(filetype);
|
|
files = files.concat(res);
|
|
} catch (e) {
|
|
this.appendError(`getSnippetFiles of ${name}`, e);
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
async getTriggerSnippets(bufnr, autoTrigger = false) {
|
|
let doc = import_coc3.workspace.getDocument(bufnr);
|
|
if (!doc)
|
|
return [];
|
|
let position = await import_coc3.window.getCursorPosition();
|
|
let names = Array.from(this.providers.keys());
|
|
let list = [];
|
|
for (let name of names) {
|
|
let provider = this.providers.get(name);
|
|
try {
|
|
let items = await provider.getTriggerSnippets(doc, position, autoTrigger);
|
|
for (let item of items) {
|
|
if (list.findIndex((o) => o.prefix == item.prefix) == -1) {
|
|
list.push(item);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
this.appendError(`getTriggerSnippets of ${name}`, e);
|
|
}
|
|
}
|
|
list.sort((a, b) => b.priority - a.priority);
|
|
if (list.length > 1 && list[0].priority > 0) {
|
|
list = list.filter((o) => o.priority > 0);
|
|
}
|
|
return list;
|
|
}
|
|
appendError(name, e) {
|
|
this.channel.appendLine(`[Error ${new Date().toLocaleTimeString()}] Error on ${name}: ${typeof e === "string" ? e : e.message}`);
|
|
if (e instanceof Error) {
|
|
this.channel.appendLine(e.stack);
|
|
}
|
|
}
|
|
async provideCompletionItems(document, position, _token, context) {
|
|
let doc = import_coc3.workspace.getDocument(document.uri);
|
|
if (!doc)
|
|
return [];
|
|
let snippets = await this.getSnippets(doc.filetype);
|
|
let currline = doc.getline(position.line, true);
|
|
let {input, col} = context.option;
|
|
let character = characterIndex(currline, col);
|
|
let before_content = currline.slice(0, character);
|
|
let res = [];
|
|
let contextPrefixes = [];
|
|
for (let snip of snippets) {
|
|
let contentBehind = before_content;
|
|
if (contextPrefixes.indexOf(snip.prefix) !== -1)
|
|
continue;
|
|
if (snip.regex != null && snip.prefix == "")
|
|
continue;
|
|
if (snip.context) {
|
|
let provider = this.providers.get(snip.provider);
|
|
let valid;
|
|
try {
|
|
valid = await provider.checkContext(snip.context);
|
|
} catch (e) {
|
|
this.appendError(`checkContext of ${snip.provider}`, e);
|
|
valid = false;
|
|
}
|
|
if (!valid)
|
|
continue;
|
|
contextPrefixes.push(snip.prefix);
|
|
}
|
|
let head = this.getPrefixHead(doc, snip.prefix);
|
|
if (input.length == 0 && !before_content.endsWith(snip.prefix))
|
|
continue;
|
|
let item = {
|
|
label: snip.prefix,
|
|
kind: import_coc3.CompletionItemKind.Snippet,
|
|
filterText: snip.prefix,
|
|
detail: snip.description,
|
|
insertTextFormat: import_coc3.InsertTextFormat.Snippet
|
|
};
|
|
item.data = {
|
|
snip,
|
|
provider: snip.provider,
|
|
filepath: `${import_path.default.basename(snip.filepath)}:${snip.lnum}`
|
|
};
|
|
if (snip.regex) {
|
|
if (!input.length || snip.prefix && input[0] != snip.prefix[0])
|
|
continue;
|
|
let content = before_content + snip.prefix;
|
|
let ms = content.match(snip.regex);
|
|
if (!ms)
|
|
continue;
|
|
} else if (head && before_content.endsWith(head)) {
|
|
contentBehind = before_content.slice(0, -head.length);
|
|
let prefix = snip.prefix.slice(head.length);
|
|
Object.assign(item, {
|
|
textEdit: {
|
|
range: import_coc3.Range.create({line: position.line, character: character - head.length}, position),
|
|
newText: prefix
|
|
}
|
|
});
|
|
} else if (input.length == 0) {
|
|
let {prefix} = snip;
|
|
contentBehind = before_content.slice(0, -prefix.length);
|
|
Object.assign(item, {
|
|
preselect: true,
|
|
textEdit: {
|
|
range: import_coc3.Range.create({line: position.line, character: character - prefix.length}, position),
|
|
newText: prefix
|
|
}
|
|
});
|
|
}
|
|
if (snip.triggerKind == TriggerKind.LineBegin && contentBehind.trim().length)
|
|
continue;
|
|
if (snip.triggerKind == TriggerKind.SpaceBefore) {
|
|
if (contentBehind.length && !/\s/.test(contentBehind[contentBehind.length - 1])) {
|
|
continue;
|
|
}
|
|
}
|
|
if (!item.textEdit) {
|
|
item.textEdit = {
|
|
range: import_coc3.Range.create({line: position.line, character}, position),
|
|
newText: item.label
|
|
};
|
|
}
|
|
item.data.location = `${snip.filepath}:${snip.lnum}`;
|
|
item.data.line = contentBehind + snip.prefix;
|
|
res.push(item);
|
|
}
|
|
return res;
|
|
}
|
|
async resolveCompletionItem(item) {
|
|
let provider = this.providers.get(item.data.provider);
|
|
if (provider) {
|
|
let filetype = await import_coc3.workspace.nvim.eval("&filetype");
|
|
let insertSnippet;
|
|
try {
|
|
insertSnippet = await provider.resolveSnippetBody(item.data.snip, item.textEdit.range, item.data.line);
|
|
} catch (e) {
|
|
this.appendError(`resolveSnippetBody of ${item.data.provider}`, e);
|
|
return item;
|
|
}
|
|
item.textEdit.newText = insertSnippet;
|
|
if (import_coc3.snippetManager) {
|
|
let snip = await Promise.resolve(import_coc3.snippetManager.resolveSnippet(insertSnippet));
|
|
item.documentation = {
|
|
kind: "markdown",
|
|
value: markdownBlock(snip.toString(), filetype.match(/^\w+/)[0])
|
|
};
|
|
}
|
|
}
|
|
return item;
|
|
}
|
|
getPrefixHead(doc, prefix) {
|
|
let res = 0;
|
|
for (let idx = prefix.length - 1; idx >= 0; idx--) {
|
|
if (!doc.isWord(prefix[idx])) {
|
|
res = idx;
|
|
break;
|
|
}
|
|
}
|
|
return res == 0 ? "" : prefix.slice(0, res + 1);
|
|
}
|
|
};
|
|
function characterIndex(content, byteIndex) {
|
|
let buf = Buffer.from(content, "utf8");
|
|
return buf.slice(0, byteIndex).toString("utf8").length;
|
|
}
|
|
|
|
// src/snipmateProvider.ts
|
|
var import_coc5 = __toModule(require("coc.nvim"));
|
|
var import_fs2 = __toModule(require("fs"));
|
|
var import_path2 = __toModule(require("path"));
|
|
var import_readline = __toModule(require("readline"));
|
|
|
|
// src/baseProvider.ts
|
|
var import_coc4 = __toModule(require("coc.nvim"));
|
|
var BaseProvider = class {
|
|
constructor(config) {
|
|
this.config = config;
|
|
}
|
|
async checkContext(_context) {
|
|
return true;
|
|
}
|
|
getExtendsFiletypes(filetype, exists = new Set()) {
|
|
if (exists.has(filetype))
|
|
return [];
|
|
let extend = this.config.extends ? this.config.extends[filetype] : null;
|
|
exists.add(filetype);
|
|
if (!extend || extend.length == 0)
|
|
return [];
|
|
return extend.reduce((arr, curr) => {
|
|
return arr.concat([curr], this.getExtendsFiletypes(curr, exists));
|
|
}, []);
|
|
}
|
|
getFiletypes(filetype) {
|
|
let filetypes = [filetype];
|
|
if (filetype.indexOf(".") !== -1) {
|
|
filetypes.push(...filetype.split("."));
|
|
}
|
|
if (filetype == "javascript.jsx")
|
|
filetypes.push("javascriptreact");
|
|
if (filetype == "typescript.jsx" || filetype == "typescript.tsx")
|
|
filetypes.push("typescriptreact");
|
|
let map = import_coc4.workspace.env.filetypeMap;
|
|
if (map && map[filetype]) {
|
|
filetypes.push(map[filetype]);
|
|
}
|
|
if (filetype == "javascriptreact" && !filetypes.includes("javascript")) {
|
|
filetypes.push("javascript");
|
|
}
|
|
if (filetype == "typescriptreact" && !filetypes.includes("typescript")) {
|
|
filetypes.push("typescript");
|
|
}
|
|
let extendFiletypes = filetypes.reduce((arr, curr) => {
|
|
return arr.concat(this.getExtendsFiletypes(curr));
|
|
}, []);
|
|
filetypes.push(...extendFiletypes);
|
|
filetypes.reverse();
|
|
return distinct(filetypes);
|
|
}
|
|
};
|
|
var baseProvider_default = BaseProvider;
|
|
|
|
// src/parser.ts
|
|
var Parser = class {
|
|
constructor(_content) {
|
|
this._content = _content;
|
|
this._curr = 0;
|
|
}
|
|
eof() {
|
|
return this._curr >= this._content.length;
|
|
}
|
|
skipSpaces() {
|
|
for (let i = this._curr; i <= this._content.length; i++) {
|
|
let ch = this._content[i];
|
|
if (!ch || /\S/.test(ch)) {
|
|
this._curr = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
get index() {
|
|
return this._curr;
|
|
}
|
|
get curr() {
|
|
return this._content[this._curr] || "";
|
|
}
|
|
get len() {
|
|
return this._content.length;
|
|
}
|
|
next(count = 1) {
|
|
return this._content.slice(this._curr + 1, this._curr + 1 + count);
|
|
}
|
|
nextIndex(character, checkEscape = true, allowEnd = true) {
|
|
if (this._curr >= this.len - 1)
|
|
return allowEnd ? this.len - 1 : -1;
|
|
let i = this._curr + 1;
|
|
let pre = this.curr || "";
|
|
while (i != this.len - 1) {
|
|
let ch = this._content[i];
|
|
if (ch == character && (!checkEscape || pre !== "\\")) {
|
|
break;
|
|
}
|
|
pre = ch;
|
|
i = i + 1;
|
|
}
|
|
if (!allowEnd && i == this.len - 1 && character != this._content[i]) {
|
|
return -1;
|
|
}
|
|
return i;
|
|
}
|
|
prev() {
|
|
return this._content[this._curr - 1] || "";
|
|
}
|
|
iterate(fn) {
|
|
while (this._curr < this._content.length) {
|
|
let fine = fn(this.curr, this._curr);
|
|
if (fine === false) {
|
|
break;
|
|
}
|
|
this._curr = this._curr + 1;
|
|
}
|
|
}
|
|
eat(count) {
|
|
let end = this._curr + count;
|
|
end = Math.min(end, this.len);
|
|
let str = this._content.slice(this._curr, end);
|
|
this._curr = end;
|
|
return str;
|
|
}
|
|
eatTo(index) {
|
|
if (index == this._curr)
|
|
return "";
|
|
let str = this._content.slice(this._curr, index);
|
|
this._curr = index;
|
|
return str;
|
|
}
|
|
};
|
|
var parser_default = Parser;
|
|
|
|
// src/snipmateProvider.ts
|
|
var SnipmateProvider = class extends baseProvider_default {
|
|
constructor(channel, trace, config) {
|
|
super(config);
|
|
this.channel = channel;
|
|
this.trace = trace;
|
|
this.snippetFiles = [];
|
|
this.disposables = [];
|
|
import_coc5.workspace.onDidSaveTextDocument(async (doc) => {
|
|
let uri = import_coc5.Uri.parse(doc.uri);
|
|
if (uri.scheme != "file")
|
|
return;
|
|
let filepath = uri.fsPath;
|
|
if (!import_fs2.default.existsSync(filepath))
|
|
return;
|
|
let snippetFile = this.snippetFiles.find((s) => s.filepath == filepath);
|
|
if (snippetFile)
|
|
await this.loadSnippetsFromFile(snippetFile.filetype, snippetFile.directory, filepath);
|
|
}, null, this.disposables);
|
|
}
|
|
async init() {
|
|
let arr = await this.getAllSnippetFiles();
|
|
let {nvim} = import_coc5.workspace;
|
|
let author = await nvim.getVar("snips_author");
|
|
if (!author)
|
|
await nvim.setVar("snips_author", this.config.author);
|
|
await Promise.all(arr.map(({filepath, directory, filetype}) => {
|
|
return this.loadSnippetsFromFile(filetype, directory, filepath);
|
|
}));
|
|
}
|
|
async loadSnippetsFromFile(filetype, directory, filepath) {
|
|
let snippets = await this.parseSnippetsFile(filepath);
|
|
let idx = this.snippetFiles.findIndex((o) => o.filepath == filepath);
|
|
if (idx !== -1)
|
|
this.snippetFiles.splice(idx, 1);
|
|
this.snippetFiles.push({
|
|
directory,
|
|
filepath,
|
|
filetype,
|
|
snippets
|
|
});
|
|
this.channel.appendLine(`[Info ${new Date().toISOString()}] Loaded ${snippets.length} snipmate snippets from: ${filepath}`);
|
|
}
|
|
async resolveSnippetBody(snippet, _range, _line) {
|
|
let parser2 = new parser_default(snippet.body);
|
|
let resolved = "";
|
|
let {nvim} = import_coc5.workspace;
|
|
while (!parser2.eof()) {
|
|
if (parser2.curr == "`") {
|
|
let idx = parser2.nextIndex("`", true, false);
|
|
if (idx == -1) {
|
|
resolved = resolved + parser2.eatTo(parser2.len);
|
|
break;
|
|
}
|
|
let code = parser2.eatTo(idx + 1);
|
|
code = code.slice(1, -1);
|
|
if (code.startsWith("Filename")) {
|
|
resolved = resolved + await nvim.call("expand", "%:p:t");
|
|
} else {
|
|
try {
|
|
resolved = resolved + await nvim.eval(code);
|
|
} catch (e) {
|
|
this.channel.appendLine(`[Error ${new Date().toLocaleTimeString()}] Error on eval: ${code}`);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
parser2.iterate((ch) => {
|
|
if (ch == "`") {
|
|
return false;
|
|
} else {
|
|
resolved = resolved + ch;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
return resolved;
|
|
}
|
|
parseSnippetsFile(filepath) {
|
|
let res = [];
|
|
const rl = import_readline.default.createInterface({
|
|
input: import_fs2.default.createReadStream(filepath, "utf8"),
|
|
crlfDelay: Infinity
|
|
});
|
|
let lnum = 0;
|
|
let lines = [];
|
|
let prefix = "";
|
|
let description = "";
|
|
rl.on("line", (line) => {
|
|
lnum += 1;
|
|
if (line.startsWith("#"))
|
|
return;
|
|
if (line.startsWith("snippet")) {
|
|
line = line.replace(/\s*$/, "");
|
|
if (lines.length && prefix) {
|
|
res.push({
|
|
filepath,
|
|
lnum: lnum - lines.length - 1,
|
|
body: lines.join("\n").replace(/\s+$/, ""),
|
|
prefix,
|
|
description,
|
|
triggerKind: TriggerKind.SpaceBefore
|
|
});
|
|
lines = [];
|
|
}
|
|
let ms = line.match(/^snippet\s+(\S+)(?:\s(.+))?$/);
|
|
if (!ms) {
|
|
prefix = "";
|
|
this.channel.appendLine(`[Error ${new Date().toLocaleTimeString()}] Broken line on ${filepath}:${lnum}`);
|
|
return;
|
|
}
|
|
prefix = ms[1];
|
|
description = ms[2] || "";
|
|
return;
|
|
}
|
|
if (prefix) {
|
|
if (line.indexOf("VISUAL") !== -1) {
|
|
line = line.replace(/\$(\{?)VISUAL\b(:[^\}])?(\}?)/g, "$$$1TM_SELECTED_TEXT$2$3");
|
|
}
|
|
if (line.startsWith(" ")) {
|
|
lines.push(line.slice(1));
|
|
} else {
|
|
lines.push(line);
|
|
}
|
|
}
|
|
});
|
|
return new Promise((resolve) => {
|
|
rl.on("close", async () => {
|
|
if (lines.length) {
|
|
res.push({
|
|
filepath,
|
|
lnum: lnum - lines.length - 1,
|
|
body: lines.join("\n"),
|
|
prefix,
|
|
description,
|
|
triggerKind: TriggerKind.SpaceBefore
|
|
});
|
|
}
|
|
resolve(res);
|
|
});
|
|
});
|
|
}
|
|
async getTriggerSnippets(document, position, autoTrigger) {
|
|
if (autoTrigger)
|
|
return [];
|
|
let snippets = await this.getSnippets(document.filetype);
|
|
let line = document.getline(position.line);
|
|
line = line.slice(0, position.character);
|
|
if (!line || line[line.length - 1] == " ")
|
|
return [];
|
|
snippets = snippets.filter((s) => {
|
|
let {prefix} = s;
|
|
if (!line.endsWith(prefix))
|
|
return false;
|
|
let pre = line.slice(0, line.length - prefix.length);
|
|
return pre.length == 0 || /\s/.test(pre[pre.length - 1]);
|
|
});
|
|
let edits = [];
|
|
for (let s of snippets) {
|
|
let character = position.character - s.prefix.length;
|
|
let range = import_coc5.Range.create(position.line, character, position.line, position.character);
|
|
let newText = await this.resolveSnippetBody(s, range, line);
|
|
edits.push({
|
|
prefix: s.prefix,
|
|
description: s.description,
|
|
location: s.filepath,
|
|
range,
|
|
newText,
|
|
priority: -1
|
|
});
|
|
}
|
|
return edits;
|
|
}
|
|
async getSnippetFiles(filetype) {
|
|
let filetypes = this.getFiletypes(filetype);
|
|
let res = [];
|
|
for (let s of this.snippetFiles) {
|
|
if (filetypes.indexOf(s.filetype) !== -1) {
|
|
res.push(s.filepath);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
async getSnippets(filetype) {
|
|
let filetypes = this.getFiletypes(filetype);
|
|
filetypes.push("_");
|
|
let snippetFiles = this.snippetFiles.filter((o) => filetypes.indexOf(o.filetype) !== -1);
|
|
let result = [];
|
|
snippetFiles.sort((a, b) => {
|
|
if (a.filetype == b.filetype)
|
|
return 1;
|
|
if (a.filetype == filetype)
|
|
return -1;
|
|
return 1;
|
|
});
|
|
for (let file of snippetFiles) {
|
|
let {snippets} = file;
|
|
for (let snip of snippets) {
|
|
result.push(snip);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
async getAllSnippetFiles() {
|
|
let {nvim} = import_coc5.workspace;
|
|
let opt = await nvim.eval("&rtp");
|
|
let rtps = opt.split(",");
|
|
let res = [];
|
|
for (let rtp of rtps) {
|
|
let items = await this.getSnippetFileItems(import_path2.default.join(rtp, "snippets"));
|
|
res.push(...items);
|
|
}
|
|
return res;
|
|
}
|
|
async getSnippetFileItems(directory) {
|
|
let res = [];
|
|
let stat = await statAsync(directory);
|
|
if (stat && stat.isDirectory()) {
|
|
let files = await readdirAsync(directory);
|
|
if (files.length) {
|
|
for (let f of files) {
|
|
let file = import_path2.default.join(directory, f);
|
|
if (file.endsWith(".snippets")) {
|
|
let basename = import_path2.default.basename(f, ".snippets");
|
|
let filetype = basename.split("-", 2)[0];
|
|
res.push({filepath: file, directory, filetype});
|
|
} else {
|
|
let stat2 = await statAsync(file);
|
|
if (stat2 && stat2.isDirectory()) {
|
|
let files2 = await readdirAsync(file);
|
|
for (let filename of files2) {
|
|
if (filename.endsWith(".snippets")) {
|
|
res.push({filepath: import_path2.default.join(file, filename), directory, filetype: f});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
};
|
|
|
|
// src/textmateProvider.ts
|
|
var import_coc6 = __toModule(require("coc.nvim"));
|
|
var import_fs3 = __toModule(require("fs"));
|
|
|
|
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
"use strict";
|
|
function createScanner(text, ignoreTrivia) {
|
|
if (ignoreTrivia === void 0) {
|
|
ignoreTrivia = false;
|
|
}
|
|
var len = text.length;
|
|
var pos = 0, value = "", tokenOffset = 0, token = 16, lineNumber = 0, lineStartOffset = 0, tokenLineStartOffset = 0, prevTokenLineStartOffset = 0, scanError = 0;
|
|
function scanHexDigits(count, exact) {
|
|
var digits = 0;
|
|
var value2 = 0;
|
|
while (digits < count || !exact) {
|
|
var ch = text.charCodeAt(pos);
|
|
if (ch >= 48 && ch <= 57) {
|
|
value2 = value2 * 16 + ch - 48;
|
|
} else if (ch >= 65 && ch <= 70) {
|
|
value2 = value2 * 16 + ch - 65 + 10;
|
|
} else if (ch >= 97 && ch <= 102) {
|
|
value2 = value2 * 16 + ch - 97 + 10;
|
|
} else {
|
|
break;
|
|
}
|
|
pos++;
|
|
digits++;
|
|
}
|
|
if (digits < count) {
|
|
value2 = -1;
|
|
}
|
|
return value2;
|
|
}
|
|
function setPosition(newPosition) {
|
|
pos = newPosition;
|
|
value = "";
|
|
tokenOffset = 0;
|
|
token = 16;
|
|
scanError = 0;
|
|
}
|
|
function scanNumber() {
|
|
var start = pos;
|
|
if (text.charCodeAt(pos) === 48) {
|
|
pos++;
|
|
} else {
|
|
pos++;
|
|
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
pos++;
|
|
}
|
|
}
|
|
if (pos < text.length && text.charCodeAt(pos) === 46) {
|
|
pos++;
|
|
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
pos++;
|
|
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
pos++;
|
|
}
|
|
} else {
|
|
scanError = 3;
|
|
return text.substring(start, pos);
|
|
}
|
|
}
|
|
var end = pos;
|
|
if (pos < text.length && (text.charCodeAt(pos) === 69 || text.charCodeAt(pos) === 101)) {
|
|
pos++;
|
|
if (pos < text.length && text.charCodeAt(pos) === 43 || text.charCodeAt(pos) === 45) {
|
|
pos++;
|
|
}
|
|
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
pos++;
|
|
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
pos++;
|
|
}
|
|
end = pos;
|
|
} else {
|
|
scanError = 3;
|
|
}
|
|
}
|
|
return text.substring(start, end);
|
|
}
|
|
function scanString() {
|
|
var result = "", start = pos;
|
|
while (true) {
|
|
if (pos >= len) {
|
|
result += text.substring(start, pos);
|
|
scanError = 2;
|
|
break;
|
|
}
|
|
var ch = text.charCodeAt(pos);
|
|
if (ch === 34) {
|
|
result += text.substring(start, pos);
|
|
pos++;
|
|
break;
|
|
}
|
|
if (ch === 92) {
|
|
result += text.substring(start, pos);
|
|
pos++;
|
|
if (pos >= len) {
|
|
scanError = 2;
|
|
break;
|
|
}
|
|
var ch2 = text.charCodeAt(pos++);
|
|
switch (ch2) {
|
|
case 34:
|
|
result += '"';
|
|
break;
|
|
case 92:
|
|
result += "\\";
|
|
break;
|
|
case 47:
|
|
result += "/";
|
|
break;
|
|
case 98:
|
|
result += "\b";
|
|
break;
|
|
case 102:
|
|
result += "\f";
|
|
break;
|
|
case 110:
|
|
result += "\n";
|
|
break;
|
|
case 114:
|
|
result += "\r";
|
|
break;
|
|
case 116:
|
|
result += " ";
|
|
break;
|
|
case 117:
|
|
var ch3 = scanHexDigits(4, true);
|
|
if (ch3 >= 0) {
|
|
result += String.fromCharCode(ch3);
|
|
} else {
|
|
scanError = 4;
|
|
}
|
|
break;
|
|
default:
|
|
scanError = 5;
|
|
}
|
|
start = pos;
|
|
continue;
|
|
}
|
|
if (ch >= 0 && ch <= 31) {
|
|
if (isLineBreak(ch)) {
|
|
result += text.substring(start, pos);
|
|
scanError = 2;
|
|
break;
|
|
} else {
|
|
scanError = 6;
|
|
}
|
|
}
|
|
pos++;
|
|
}
|
|
return result;
|
|
}
|
|
function scanNext() {
|
|
value = "";
|
|
scanError = 0;
|
|
tokenOffset = pos;
|
|
lineStartOffset = lineNumber;
|
|
prevTokenLineStartOffset = tokenLineStartOffset;
|
|
if (pos >= len) {
|
|
tokenOffset = len;
|
|
return token = 17;
|
|
}
|
|
var code = text.charCodeAt(pos);
|
|
if (isWhiteSpace(code)) {
|
|
do {
|
|
pos++;
|
|
value += String.fromCharCode(code);
|
|
code = text.charCodeAt(pos);
|
|
} while (isWhiteSpace(code));
|
|
return token = 15;
|
|
}
|
|
if (isLineBreak(code)) {
|
|
pos++;
|
|
value += String.fromCharCode(code);
|
|
if (code === 13 && text.charCodeAt(pos) === 10) {
|
|
pos++;
|
|
value += "\n";
|
|
}
|
|
lineNumber++;
|
|
tokenLineStartOffset = pos;
|
|
return token = 14;
|
|
}
|
|
switch (code) {
|
|
case 123:
|
|
pos++;
|
|
return token = 1;
|
|
case 125:
|
|
pos++;
|
|
return token = 2;
|
|
case 91:
|
|
pos++;
|
|
return token = 3;
|
|
case 93:
|
|
pos++;
|
|
return token = 4;
|
|
case 58:
|
|
pos++;
|
|
return token = 6;
|
|
case 44:
|
|
pos++;
|
|
return token = 5;
|
|
case 34:
|
|
pos++;
|
|
value = scanString();
|
|
return token = 10;
|
|
case 47:
|
|
var start = pos - 1;
|
|
if (text.charCodeAt(pos + 1) === 47) {
|
|
pos += 2;
|
|
while (pos < len) {
|
|
if (isLineBreak(text.charCodeAt(pos))) {
|
|
break;
|
|
}
|
|
pos++;
|
|
}
|
|
value = text.substring(start, pos);
|
|
return token = 12;
|
|
}
|
|
if (text.charCodeAt(pos + 1) === 42) {
|
|
pos += 2;
|
|
var safeLength = len - 1;
|
|
var commentClosed = false;
|
|
while (pos < safeLength) {
|
|
var ch = text.charCodeAt(pos);
|
|
if (ch === 42 && text.charCodeAt(pos + 1) === 47) {
|
|
pos += 2;
|
|
commentClosed = true;
|
|
break;
|
|
}
|
|
pos++;
|
|
if (isLineBreak(ch)) {
|
|
if (ch === 13 && text.charCodeAt(pos) === 10) {
|
|
pos++;
|
|
}
|
|
lineNumber++;
|
|
tokenLineStartOffset = pos;
|
|
}
|
|
}
|
|
if (!commentClosed) {
|
|
pos++;
|
|
scanError = 1;
|
|
}
|
|
value = text.substring(start, pos);
|
|
return token = 13;
|
|
}
|
|
value += String.fromCharCode(code);
|
|
pos++;
|
|
return token = 16;
|
|
case 45:
|
|
value += String.fromCharCode(code);
|
|
pos++;
|
|
if (pos === len || !isDigit(text.charCodeAt(pos))) {
|
|
return token = 16;
|
|
}
|
|
case 48:
|
|
case 49:
|
|
case 50:
|
|
case 51:
|
|
case 52:
|
|
case 53:
|
|
case 54:
|
|
case 55:
|
|
case 56:
|
|
case 57:
|
|
value += scanNumber();
|
|
return token = 11;
|
|
default:
|
|
while (pos < len && isUnknownContentCharacter(code)) {
|
|
pos++;
|
|
code = text.charCodeAt(pos);
|
|
}
|
|
if (tokenOffset !== pos) {
|
|
value = text.substring(tokenOffset, pos);
|
|
switch (value) {
|
|
case "true":
|
|
return token = 8;
|
|
case "false":
|
|
return token = 9;
|
|
case "null":
|
|
return token = 7;
|
|
}
|
|
return token = 16;
|
|
}
|
|
value += String.fromCharCode(code);
|
|
pos++;
|
|
return token = 16;
|
|
}
|
|
}
|
|
function isUnknownContentCharacter(code) {
|
|
if (isWhiteSpace(code) || isLineBreak(code)) {
|
|
return false;
|
|
}
|
|
switch (code) {
|
|
case 125:
|
|
case 93:
|
|
case 123:
|
|
case 91:
|
|
case 34:
|
|
case 58:
|
|
case 44:
|
|
case 47:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function scanNextNonTrivia() {
|
|
var result;
|
|
do {
|
|
result = scanNext();
|
|
} while (result >= 12 && result <= 15);
|
|
return result;
|
|
}
|
|
return {
|
|
setPosition,
|
|
getPosition: function() {
|
|
return pos;
|
|
},
|
|
scan: ignoreTrivia ? scanNextNonTrivia : scanNext,
|
|
getToken: function() {
|
|
return token;
|
|
},
|
|
getTokenValue: function() {
|
|
return value;
|
|
},
|
|
getTokenOffset: function() {
|
|
return tokenOffset;
|
|
},
|
|
getTokenLength: function() {
|
|
return pos - tokenOffset;
|
|
},
|
|
getTokenStartLine: function() {
|
|
return lineStartOffset;
|
|
},
|
|
getTokenStartCharacter: function() {
|
|
return tokenOffset - prevTokenLineStartOffset;
|
|
},
|
|
getTokenError: function() {
|
|
return scanError;
|
|
}
|
|
};
|
|
}
|
|
function isWhiteSpace(ch) {
|
|
return ch === 32 || ch === 9 || ch === 11 || ch === 12 || ch === 160 || ch === 5760 || ch >= 8192 && ch <= 8203 || ch === 8239 || ch === 8287 || ch === 12288 || ch === 65279;
|
|
}
|
|
function isLineBreak(ch) {
|
|
return ch === 10 || ch === 13 || ch === 8232 || ch === 8233;
|
|
}
|
|
function isDigit(ch) {
|
|
return ch >= 48 && ch <= 57;
|
|
}
|
|
|
|
// node_modules/jsonc-parser/lib/esm/impl/format.js
|
|
"use strict";
|
|
|
|
// node_modules/jsonc-parser/lib/esm/impl/parser.js
|
|
"use strict";
|
|
var ParseOptions;
|
|
(function(ParseOptions2) {
|
|
ParseOptions2.DEFAULT = {
|
|
allowTrailingComma: false
|
|
};
|
|
})(ParseOptions || (ParseOptions = {}));
|
|
function parse(text, errors, options) {
|
|
if (errors === void 0) {
|
|
errors = [];
|
|
}
|
|
if (options === void 0) {
|
|
options = ParseOptions.DEFAULT;
|
|
}
|
|
var currentProperty = null;
|
|
var currentParent = [];
|
|
var previousParents = [];
|
|
function onValue(value) {
|
|
if (Array.isArray(currentParent)) {
|
|
currentParent.push(value);
|
|
} else if (currentProperty !== null) {
|
|
currentParent[currentProperty] = value;
|
|
}
|
|
}
|
|
var visitor = {
|
|
onObjectBegin: function() {
|
|
var object = {};
|
|
onValue(object);
|
|
previousParents.push(currentParent);
|
|
currentParent = object;
|
|
currentProperty = null;
|
|
},
|
|
onObjectProperty: function(name) {
|
|
currentProperty = name;
|
|
},
|
|
onObjectEnd: function() {
|
|
currentParent = previousParents.pop();
|
|
},
|
|
onArrayBegin: function() {
|
|
var array = [];
|
|
onValue(array);
|
|
previousParents.push(currentParent);
|
|
currentParent = array;
|
|
currentProperty = null;
|
|
},
|
|
onArrayEnd: function() {
|
|
currentParent = previousParents.pop();
|
|
},
|
|
onLiteralValue: onValue,
|
|
onError: function(error, offset, length) {
|
|
errors.push({error, offset, length});
|
|
}
|
|
};
|
|
visit(text, visitor, options);
|
|
return currentParent[0];
|
|
}
|
|
function visit(text, visitor, options) {
|
|
if (options === void 0) {
|
|
options = ParseOptions.DEFAULT;
|
|
}
|
|
var _scanner = createScanner(text, false);
|
|
function toNoArgVisit(visitFunction) {
|
|
return visitFunction ? function() {
|
|
return visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter());
|
|
} : function() {
|
|
return true;
|
|
};
|
|
}
|
|
function toOneArgVisit(visitFunction) {
|
|
return visitFunction ? function(arg) {
|
|
return visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter());
|
|
} : function() {
|
|
return true;
|
|
};
|
|
}
|
|
var onObjectBegin = toNoArgVisit(visitor.onObjectBegin), onObjectProperty = toOneArgVisit(visitor.onObjectProperty), onObjectEnd = toNoArgVisit(visitor.onObjectEnd), onArrayBegin = toNoArgVisit(visitor.onArrayBegin), onArrayEnd = toNoArgVisit(visitor.onArrayEnd), onLiteralValue = toOneArgVisit(visitor.onLiteralValue), onSeparator = toOneArgVisit(visitor.onSeparator), onComment = toNoArgVisit(visitor.onComment), onError = toOneArgVisit(visitor.onError);
|
|
var disallowComments = options && options.disallowComments;
|
|
var allowTrailingComma = options && options.allowTrailingComma;
|
|
function scanNext() {
|
|
while (true) {
|
|
var token = _scanner.scan();
|
|
switch (_scanner.getTokenError()) {
|
|
case 4:
|
|
handleError(14);
|
|
break;
|
|
case 5:
|
|
handleError(15);
|
|
break;
|
|
case 3:
|
|
handleError(13);
|
|
break;
|
|
case 1:
|
|
if (!disallowComments) {
|
|
handleError(11);
|
|
}
|
|
break;
|
|
case 2:
|
|
handleError(12);
|
|
break;
|
|
case 6:
|
|
handleError(16);
|
|
break;
|
|
}
|
|
switch (token) {
|
|
case 12:
|
|
case 13:
|
|
if (disallowComments) {
|
|
handleError(10);
|
|
} else {
|
|
onComment();
|
|
}
|
|
break;
|
|
case 16:
|
|
handleError(1);
|
|
break;
|
|
case 15:
|
|
case 14:
|
|
break;
|
|
default:
|
|
return token;
|
|
}
|
|
}
|
|
}
|
|
function handleError(error, skipUntilAfter, skipUntil) {
|
|
if (skipUntilAfter === void 0) {
|
|
skipUntilAfter = [];
|
|
}
|
|
if (skipUntil === void 0) {
|
|
skipUntil = [];
|
|
}
|
|
onError(error);
|
|
if (skipUntilAfter.length + skipUntil.length > 0) {
|
|
var token = _scanner.getToken();
|
|
while (token !== 17) {
|
|
if (skipUntilAfter.indexOf(token) !== -1) {
|
|
scanNext();
|
|
break;
|
|
} else if (skipUntil.indexOf(token) !== -1) {
|
|
break;
|
|
}
|
|
token = scanNext();
|
|
}
|
|
}
|
|
}
|
|
function parseString(isValue) {
|
|
var value = _scanner.getTokenValue();
|
|
if (isValue) {
|
|
onLiteralValue(value);
|
|
} else {
|
|
onObjectProperty(value);
|
|
}
|
|
scanNext();
|
|
return true;
|
|
}
|
|
function parseLiteral() {
|
|
switch (_scanner.getToken()) {
|
|
case 11:
|
|
var tokenValue = _scanner.getTokenValue();
|
|
var value = Number(tokenValue);
|
|
if (isNaN(value)) {
|
|
handleError(2);
|
|
value = 0;
|
|
}
|
|
onLiteralValue(value);
|
|
break;
|
|
case 7:
|
|
onLiteralValue(null);
|
|
break;
|
|
case 8:
|
|
onLiteralValue(true);
|
|
break;
|
|
case 9:
|
|
onLiteralValue(false);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
scanNext();
|
|
return true;
|
|
}
|
|
function parseProperty() {
|
|
if (_scanner.getToken() !== 10) {
|
|
handleError(3, [], [2, 5]);
|
|
return false;
|
|
}
|
|
parseString(false);
|
|
if (_scanner.getToken() === 6) {
|
|
onSeparator(":");
|
|
scanNext();
|
|
if (!parseValue()) {
|
|
handleError(4, [], [2, 5]);
|
|
}
|
|
} else {
|
|
handleError(5, [], [2, 5]);
|
|
}
|
|
return true;
|
|
}
|
|
function parseObject() {
|
|
onObjectBegin();
|
|
scanNext();
|
|
var needsComma = false;
|
|
while (_scanner.getToken() !== 2 && _scanner.getToken() !== 17) {
|
|
if (_scanner.getToken() === 5) {
|
|
if (!needsComma) {
|
|
handleError(4, [], []);
|
|
}
|
|
onSeparator(",");
|
|
scanNext();
|
|
if (_scanner.getToken() === 2 && allowTrailingComma) {
|
|
break;
|
|
}
|
|
} else if (needsComma) {
|
|
handleError(6, [], []);
|
|
}
|
|
if (!parseProperty()) {
|
|
handleError(4, [], [2, 5]);
|
|
}
|
|
needsComma = true;
|
|
}
|
|
onObjectEnd();
|
|
if (_scanner.getToken() !== 2) {
|
|
handleError(7, [2], []);
|
|
} else {
|
|
scanNext();
|
|
}
|
|
return true;
|
|
}
|
|
function parseArray() {
|
|
onArrayBegin();
|
|
scanNext();
|
|
var needsComma = false;
|
|
while (_scanner.getToken() !== 4 && _scanner.getToken() !== 17) {
|
|
if (_scanner.getToken() === 5) {
|
|
if (!needsComma) {
|
|
handleError(4, [], []);
|
|
}
|
|
onSeparator(",");
|
|
scanNext();
|
|
if (_scanner.getToken() === 4 && allowTrailingComma) {
|
|
break;
|
|
}
|
|
} else if (needsComma) {
|
|
handleError(6, [], []);
|
|
}
|
|
if (!parseValue()) {
|
|
handleError(4, [], [4, 5]);
|
|
}
|
|
needsComma = true;
|
|
}
|
|
onArrayEnd();
|
|
if (_scanner.getToken() !== 4) {
|
|
handleError(8, [4], []);
|
|
} else {
|
|
scanNext();
|
|
}
|
|
return true;
|
|
}
|
|
function parseValue() {
|
|
switch (_scanner.getToken()) {
|
|
case 3:
|
|
return parseArray();
|
|
case 1:
|
|
return parseObject();
|
|
case 10:
|
|
return parseString(true);
|
|
default:
|
|
return parseLiteral();
|
|
}
|
|
}
|
|
scanNext();
|
|
if (_scanner.getToken() === 17) {
|
|
if (options.allowEmptyContent) {
|
|
return true;
|
|
}
|
|
handleError(4, [], []);
|
|
return false;
|
|
}
|
|
if (!parseValue()) {
|
|
handleError(4, [], []);
|
|
return false;
|
|
}
|
|
if (_scanner.getToken() !== 17) {
|
|
handleError(9, [], []);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// node_modules/jsonc-parser/lib/esm/impl/edit.js
|
|
"use strict";
|
|
|
|
// node_modules/jsonc-parser/lib/esm/main.js
|
|
"use strict";
|
|
var parse2 = parse;
|
|
|
|
// src/textmateProvider.ts
|
|
var import_os2 = __toModule(require("os"));
|
|
var import_path3 = __toModule(require("path"));
|
|
var import_util5 = __toModule(require("util"));
|
|
var TextmateProvider = class extends baseProvider_default {
|
|
constructor(channel, trace, config) {
|
|
super(config);
|
|
this.channel = channel;
|
|
this.trace = trace;
|
|
this._snippetCache = {};
|
|
this._userSnippets = {};
|
|
if (config.loadFromExtensions) {
|
|
import_coc6.extensions.onDidLoadExtension((extension) => {
|
|
this.loadSnippetsFromExtension(extension).catch((e) => {
|
|
channel.appendLine(`[Error] ${e.message}`);
|
|
});
|
|
});
|
|
import_coc6.extensions.onDidUnloadExtension((id) => {
|
|
delete this._snippetCache[id];
|
|
});
|
|
}
|
|
}
|
|
async init() {
|
|
if (this.config.loadFromExtensions) {
|
|
for (let extension of import_coc6.extensions.all) {
|
|
await this.loadSnippetsFromExtension(extension);
|
|
}
|
|
}
|
|
let paths = this.config.snippetsRoots;
|
|
if (paths && paths.length) {
|
|
for (let dir of paths) {
|
|
await this.loadSnippetsFromRoot(dir);
|
|
}
|
|
}
|
|
}
|
|
async getSnippetFiles(filetype) {
|
|
let filetypes = this.getFiletypes(filetype);
|
|
let filepaths = [];
|
|
if (this.config.loadFromExtensions) {
|
|
for (let key of Object.keys(this._snippetCache)) {
|
|
let cache = this._snippetCache[key];
|
|
for (let filetype2 of filetypes) {
|
|
let snippets = cache[filetype2];
|
|
if (snippets && snippets.length) {
|
|
filepaths.push(snippets[0].filepath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (let filetype2 of filetypes) {
|
|
let snippets = this._userSnippets[filetype2];
|
|
if (snippets && snippets.length) {
|
|
for (let snip of snippets) {
|
|
let {filepath} = snip;
|
|
if (filepaths.indexOf(filepath) == -1) {
|
|
filepaths.push(filepath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return distinct(filepaths);
|
|
}
|
|
async getTriggerSnippets(document, position, autoTrigger) {
|
|
if (autoTrigger)
|
|
return [];
|
|
let line = document.getline(position.line);
|
|
line = line.slice(0, position.character);
|
|
let snippets = await this.getSnippets(document.filetype);
|
|
if (!snippets || !snippets.length)
|
|
return [];
|
|
let edits = [];
|
|
for (let snip of snippets) {
|
|
let {prefix} = snip;
|
|
if (!line.endsWith(prefix))
|
|
continue;
|
|
let pre = line.slice(0, line.length - prefix.length);
|
|
if (pre.length && /\w/.test(pre[pre.length - 1]))
|
|
continue;
|
|
edits.push({
|
|
prefix,
|
|
range: import_coc6.Range.create(position.line, position.character - prefix.length, position.line, position.character),
|
|
newText: snip.body,
|
|
location: snip.filepath,
|
|
description: snip.description,
|
|
priority: -1
|
|
});
|
|
}
|
|
return edits;
|
|
}
|
|
async getSnippets(filetype) {
|
|
let res = [];
|
|
let filetypes = this.getFiletypes(filetype);
|
|
let added = new Set();
|
|
for (let key of Object.keys(this._snippetCache)) {
|
|
let cache = this._snippetCache[key];
|
|
for (let filetype2 of filetypes) {
|
|
let snippets = cache[filetype2];
|
|
if (snippets) {
|
|
for (let snip of snippets) {
|
|
if (!added.has(snip.prefix)) {
|
|
added.add(snip.prefix);
|
|
res.push(snip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (let filetype2 of filetypes) {
|
|
let snippets = this._userSnippets[filetype2];
|
|
if (snippets && snippets.length) {
|
|
for (let snip of snippets) {
|
|
if (!added.has(snip.prefix)) {
|
|
added.add(snip.prefix);
|
|
res.push(snip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
async resolveSnippetBody(snip, _range) {
|
|
return snip.body;
|
|
}
|
|
async loadSnippetsFromExtension(extension) {
|
|
let {packageJSON} = extension;
|
|
if (packageJSON.contributes && packageJSON.contributes.snippets) {
|
|
let {snippets} = packageJSON.contributes;
|
|
let def = {
|
|
extensionId: extension.id,
|
|
snippets: []
|
|
};
|
|
for (let item of snippets) {
|
|
let p = import_path3.default.join(extension.extensionPath, item.path);
|
|
let {language} = item;
|
|
def.snippets.push({
|
|
languageId: language,
|
|
filepath: p
|
|
});
|
|
}
|
|
if (snippets && snippets.length) {
|
|
await this.loadSnippetsFromDefinition(def);
|
|
}
|
|
}
|
|
}
|
|
async loadSnippetsFromRoot(root) {
|
|
let {_userSnippets} = this;
|
|
if (root.startsWith("~"))
|
|
root = root.replace(/^~/, import_os2.default.homedir());
|
|
let files = await import_util5.default.promisify(import_fs3.default.readdir)(root, "utf8");
|
|
files = files.filter((f) => f.endsWith(".json") || f.endsWith(".code-snippets"));
|
|
await Promise.all(files.map((file) => {
|
|
file = import_path3.default.join(root, file);
|
|
let basename = import_path3.default.basename(file, ".json");
|
|
basename = basename.replace(/\.code-snippets$/, "");
|
|
return this.loadSnippetsFromFile(file).then((snippets) => {
|
|
_userSnippets[basename] = snippets;
|
|
});
|
|
}));
|
|
}
|
|
async loadSnippetsFromDefinition(def) {
|
|
let {extensionId, snippets} = def;
|
|
let cache = this._snippetCache[extensionId] = {};
|
|
for (let item of snippets) {
|
|
let {languageId} = item;
|
|
if (!import_fs3.default.existsSync(item.filepath))
|
|
continue;
|
|
let arr = await this.loadSnippetsFromFile(item.filepath);
|
|
let exists = cache[languageId] || [];
|
|
cache[languageId] = [...exists, ...arr];
|
|
}
|
|
}
|
|
async loadSnippetsFromFile(snippetFilePath) {
|
|
const contents = await new Promise((resolve, reject) => {
|
|
import_fs3.default.readFile(snippetFilePath, "utf8", (err, data) => {
|
|
if (err)
|
|
return reject(err);
|
|
resolve(data);
|
|
});
|
|
});
|
|
const snippets = this.loadSnippetsFromText(snippetFilePath, contents);
|
|
this.channel.appendLine(`[Info ${new Date().toISOString()}] Loaded ${snippets.length} textmate snippets from ${snippetFilePath}`);
|
|
return snippets;
|
|
}
|
|
loadSnippetsFromText(filepath, contents) {
|
|
let snippets = [];
|
|
try {
|
|
let errors = [];
|
|
let snippetObject = parse2(contents, errors, {allowTrailingComma: true});
|
|
if (errors.length) {
|
|
this.channel.appendLine(`[Error ${new Date().toLocaleDateString()}] parser error of ${filepath}: ${JSON.stringify(errors, null, 2)}`);
|
|
}
|
|
if (snippetObject) {
|
|
for (let key of Object.keys(snippetObject)) {
|
|
snippets.push(snippetObject[key]);
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
this.channel.appendLine(`[Error ${new Date().toLocaleDateString()}] ${ex.stack}`);
|
|
snippets = [];
|
|
}
|
|
const normalizedSnippets = snippets.map((snip) => {
|
|
let prefix = Array.isArray(snip.prefix) ? snip.prefix[0] : snip.prefix;
|
|
return {
|
|
filepath,
|
|
lnum: 0,
|
|
body: typeof snip.body === "string" ? snip.body : snip.body.join("\n"),
|
|
prefix,
|
|
description: typeof snip.description === "string" ? snip.description : typeof snip.description !== "undefined" ? snip.description.join("\n") : "",
|
|
triggerKind: TriggerKind.WordBoundary
|
|
};
|
|
});
|
|
return normalizedSnippets;
|
|
}
|
|
};
|
|
|
|
// src/ultisnipsProvider.ts
|
|
var import_coc8 = __toModule(require("coc.nvim"));
|
|
var import_fs5 = __toModule(require("fs"));
|
|
var import_os3 = __toModule(require("os"));
|
|
var import_path4 = __toModule(require("path"));
|
|
var import_coc9 = __toModule(require("coc.nvim"));
|
|
|
|
// src/ultisnipsParser.ts
|
|
var import_child_process = __toModule(require("child_process"));
|
|
var import_coc7 = __toModule(require("coc.nvim"));
|
|
var import_fs4 = __toModule(require("fs"));
|
|
var import_pify2 = __toModule(require_pify());
|
|
var import_readline2 = __toModule(require("readline"));
|
|
var UltiSnipsParser = class {
|
|
constructor(pyMethod, channel, trace = "error") {
|
|
this.pyMethod = pyMethod;
|
|
this.channel = channel;
|
|
this.trace = trace;
|
|
}
|
|
parseUltisnipsFile(filepath) {
|
|
const rl = import_readline2.default.createInterface({
|
|
input: import_fs4.default.createReadStream(filepath, "utf8"),
|
|
crlfDelay: Infinity
|
|
});
|
|
let pycodes = [];
|
|
let snippets = [];
|
|
let block;
|
|
let preLines = [];
|
|
let first;
|
|
let priority = 0;
|
|
let lnum = 0;
|
|
let clearsnippets = null;
|
|
let parsedContext = null;
|
|
let extendFiletypes = [];
|
|
rl.on("line", (line) => {
|
|
lnum += 1;
|
|
if (!block && (line.startsWith("#") || line.length == 0))
|
|
return;
|
|
const [head, tail] = headTail(line);
|
|
if (!block) {
|
|
switch (head) {
|
|
case "priority":
|
|
let n = parseInt(tail.trim(), 10);
|
|
if (!isNaN(n))
|
|
priority = n;
|
|
break;
|
|
case "extends":
|
|
let fts = tail.trim().split(/,\s+/);
|
|
for (let ft of fts) {
|
|
if (extendFiletypes.indexOf(ft) == -1) {
|
|
extendFiletypes.push(ft);
|
|
}
|
|
}
|
|
break;
|
|
case "clearsnippets":
|
|
clearsnippets = priority;
|
|
break;
|
|
case "context":
|
|
parsedContext = tail.replace(/^"(.+)"$/, "$1");
|
|
break;
|
|
case "snippet":
|
|
case "global":
|
|
block = head;
|
|
first = tail;
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
if (head == "endglobal" && block == "global") {
|
|
block = null;
|
|
pycodes.push(...preLines);
|
|
preLines = [];
|
|
return;
|
|
}
|
|
if (head == "endsnippet" && block == "snippet") {
|
|
block = null;
|
|
try {
|
|
let originRegex;
|
|
let body = preLines.join("\n");
|
|
body = body.replace(/((?:[^\\]?\$\{\w+?)\/)([^\n]*?[^\\])(?=\/)/g, (_match, p1, p2) => {
|
|
return p1 + convertRegex(p2);
|
|
});
|
|
let ms = first.match(/^(.+?)(?:\s+(?:"(.*?)")?(?:\s+"(.*?)")?(?:\s+(\w+))?)?\s*$/);
|
|
let prefix = ms[1];
|
|
let description = ms[2] || "";
|
|
let context = ms[3];
|
|
let option = ms[4] || "";
|
|
if (prefix.length > 2 && prefix[1] != prefix[0] && prefix[0] == prefix[prefix.length - 1] && !/\w/.test(prefix[0])) {
|
|
prefix = prefix.slice(1, prefix.length - 1);
|
|
}
|
|
let isExpression = option.indexOf("r") !== -1;
|
|
let regex2 = null;
|
|
if (isExpression) {
|
|
originRegex = prefix;
|
|
prefix = convertRegex(prefix);
|
|
prefix = prefix.endsWith("$") ? prefix : prefix + "$";
|
|
try {
|
|
regex2 = new RegExp(prefix);
|
|
prefix = getRegexText(prefix);
|
|
} catch (e) {
|
|
this.error(`Convert regex error for: ${prefix}`);
|
|
}
|
|
}
|
|
if (parsedContext) {
|
|
context = parsedContext;
|
|
} else if (option.indexOf("e") == -1) {
|
|
context = null;
|
|
}
|
|
let snippet = {
|
|
filepath,
|
|
context,
|
|
originRegex,
|
|
autoTrigger: option.indexOf("A") !== -1,
|
|
lnum: lnum - preLines.length - 2,
|
|
triggerKind: getTriggerKind(option),
|
|
prefix,
|
|
description,
|
|
regex: regex2,
|
|
body,
|
|
priority
|
|
};
|
|
snippets.push(snippet);
|
|
} catch (e) {
|
|
this.error(`Create snippet error on: ${filepath}:${lnum - preLines.length - 1} ${e.message}`);
|
|
} finally {
|
|
parsedContext = null;
|
|
preLines = [];
|
|
}
|
|
}
|
|
if (block == "snippet" || block == "global") {
|
|
preLines.push(line);
|
|
return;
|
|
}
|
|
});
|
|
return new Promise((resolve) => {
|
|
rl.on("close", async () => {
|
|
resolve({snippets, clearsnippets, pythonCode: pycodes.join("\n"), extendFiletypes});
|
|
});
|
|
});
|
|
}
|
|
async resolveUltisnipsBody(body) {
|
|
let {pyMethod} = this;
|
|
let parser2 = new parser_default(body);
|
|
let resolved = "";
|
|
while (!parser2.eof()) {
|
|
let p = parser2.prev();
|
|
if (parser2.curr == "`" && (!p || p != "\\")) {
|
|
let idx = parser2.nextIndex("`", true, false);
|
|
if (idx == -1) {
|
|
resolved = resolved + parser2.eatTo(parser2.len);
|
|
break;
|
|
}
|
|
let code = parser2.eatTo(idx + 1);
|
|
code = code.slice(1, -1);
|
|
let indent = resolved.split(/\n/).slice(-1)[0].match(/^\s*/)[0];
|
|
resolved = resolved + await this.execute(code, pyMethod, indent);
|
|
continue;
|
|
} else if (parser2.curr == "$") {
|
|
let text = parser2.next(7);
|
|
if (text.startsWith("VISUAL") || text.startsWith("{VISUAL")) {
|
|
parser2.eat(8);
|
|
resolved += "$" + text.replace("VISUAL", "TM_SELECTED_TEXT");
|
|
} else if (!/^\d/.test(text) && !text.startsWith("{") && p != "\\") {
|
|
resolved += "\\" + parser2.eat(1);
|
|
} else {
|
|
resolved += parser2.eat(1);
|
|
}
|
|
}
|
|
let prev = parser2.prev() || "";
|
|
parser2.iterate((ch) => {
|
|
if (prev !== "\\" && (ch == "`" || ch == "$")) {
|
|
return false;
|
|
} else {
|
|
resolved = resolved + ch;
|
|
}
|
|
prev = ch;
|
|
return true;
|
|
});
|
|
}
|
|
resolved = decode(resolved);
|
|
this.debug(`resolved: ${resolved}`);
|
|
return resolved;
|
|
}
|
|
async execute(code, pyMethod, indent) {
|
|
let {nvim} = import_coc7.workspace;
|
|
let res = "";
|
|
if (code.startsWith("!")) {
|
|
code = code.trim().slice(1);
|
|
if (code.startsWith("p")) {
|
|
code = code.slice(1).trim();
|
|
let lines = [
|
|
"import traceback",
|
|
"try:",
|
|
' snip._reset("")'
|
|
];
|
|
lines.push(...code.split("\n").map((line) => " " + line.replace(/\t/g, " ")));
|
|
lines.push("except Exception as e:");
|
|
lines.push(" snip.rv = traceback.format_exc()");
|
|
await nvim.command(`${pyMethod} ${lines.join("\n")}`);
|
|
res = await nvim.call(`${pyMethod}eval`, "snip.rv");
|
|
} else if (code.startsWith("v")) {
|
|
code = code.replace(/^v\s*/, "");
|
|
try {
|
|
res = await nvim.eval(code);
|
|
} catch (e) {
|
|
res = `Error: ${e.message}`;
|
|
this.error(e.stack);
|
|
}
|
|
}
|
|
} else {
|
|
try {
|
|
res = await import_pify2.default(import_child_process.exec)(code);
|
|
} catch (e) {
|
|
res = `Error: ${e.message}`;
|
|
this.error(`Error on eval ${code}: ` + e.stack);
|
|
}
|
|
}
|
|
res = res.toString();
|
|
res = res.replace(/\r?\n$/, "");
|
|
let parts = res.split(/\r?\n/);
|
|
if (parts.length > 1) {
|
|
res = parts.map((s, idx) => {
|
|
if (idx == 0 || s.length == 0)
|
|
return s;
|
|
return `${indent}${s}`;
|
|
}).join("\n");
|
|
}
|
|
return res;
|
|
}
|
|
error(str) {
|
|
if (!this.channel)
|
|
return;
|
|
this.channel.appendLine(`[Error ${new Date().toLocaleTimeString()}] ${str}`);
|
|
}
|
|
debug(str) {
|
|
if (!this.channel || this.trace == "error")
|
|
return;
|
|
this.channel.appendLine(`[Debug ${new Date().toLocaleTimeString()}] ${str}`);
|
|
}
|
|
};
|
|
var ultisnipsParser_default = UltiSnipsParser;
|
|
function decode(str) {
|
|
return str.replace(/\\`/g, "`").replace(/\\{/g, "{");
|
|
}
|
|
function getTriggerKind(option) {
|
|
if (option.indexOf("w") !== -1) {
|
|
return TriggerKind.WordBoundary;
|
|
}
|
|
if (option.indexOf("b") !== -1) {
|
|
return TriggerKind.LineBegin;
|
|
}
|
|
if (option.indexOf("i") !== -1) {
|
|
return TriggerKind.InWord;
|
|
}
|
|
return TriggerKind.SpaceBefore;
|
|
}
|
|
|
|
// src/ultisnipsProvider.ts
|
|
var pythonCodes = new Map();
|
|
var UltiSnippetsProvider = class extends baseProvider_default {
|
|
constructor(channel, trace, config, context) {
|
|
super(config);
|
|
this.channel = channel;
|
|
this.trace = trace;
|
|
this.config = config;
|
|
this.context = context;
|
|
this.snippetFiles = [];
|
|
this.disposables = [];
|
|
this.directories = [];
|
|
this.runtimeDirs = [];
|
|
this.runtimeDirs = import_coc8.workspace.env.runtimepath.split(",");
|
|
import_coc8.workspace.watchOption("runtimepath", async (_, newValue) => {
|
|
let parts = newValue.split(",");
|
|
let subFolders = await this.getSubFolders();
|
|
let items = [];
|
|
for (let dir of parts) {
|
|
if (this.runtimeDirs.indexOf(dir) == -1) {
|
|
this.runtimeDirs.push(dir);
|
|
let res = await this.getSnippetsFromPlugin(dir, subFolders);
|
|
items.push(...res);
|
|
}
|
|
}
|
|
if (items.length) {
|
|
await Promise.all(items.map(({filepath, directory, filetype}) => {
|
|
return this.loadSnippetsFromFile(filetype, directory, filepath);
|
|
}));
|
|
let files = items.map((o) => o.filepath);
|
|
let pythonCode = "";
|
|
for (let file of files) {
|
|
let code = pythonCodes.get(file);
|
|
if (code) {
|
|
pythonCode += `# ${file}
|
|
` + code + "\n";
|
|
}
|
|
}
|
|
if (pythonCode) {
|
|
pythonCodes.clear();
|
|
await this.executePythonCode(pythonCode);
|
|
}
|
|
}
|
|
}, this.disposables);
|
|
}
|
|
checkLoaded(filepath) {
|
|
return this.snippetFiles.findIndex((o) => o.filepath == filepath) !== -1;
|
|
}
|
|
async init() {
|
|
let {nvim, env} = import_coc8.workspace;
|
|
let {runtimepath} = env;
|
|
let {config} = this;
|
|
for (let dir of config.directories) {
|
|
if (dir.startsWith("~") || dir.indexOf("$") !== -1) {
|
|
let res = await import_coc8.workspace.nvim.call("expand", [dir]);
|
|
this.directories.push(res);
|
|
} else {
|
|
this.directories.push(dir);
|
|
}
|
|
}
|
|
this.channel.appendLine(`[Info ${new Date().toISOString()}] Using ultisnips directories: ${this.directories.join(" ")}`);
|
|
let hasPythonx = await nvim.call("has", ["pythonx"]);
|
|
let pythonCode = await readFileAsync(this.context.asAbsolutePath("python/ultisnips.py"), "utf8");
|
|
if (hasPythonx && config.usePythonx) {
|
|
this.pyMethod = "pyx";
|
|
} else {
|
|
this.pyMethod = config.pythonVersion == 3 ? "py3" : "py";
|
|
}
|
|
this.channel.appendLine(`[Info ${new Date().toLocaleTimeString()}] Using ultisnips python command: ${this.pyMethod}`);
|
|
this.parser = new ultisnipsParser_default(this.pyMethod, this.channel, this.trace);
|
|
let arr = await this.getAllSnippetFiles(runtimepath);
|
|
let files = arr.map((o) => o.filepath);
|
|
await Promise.all(arr.map(({filepath, directory, filetype}) => {
|
|
return this.loadSnippetsFromFile(filetype, directory, filepath);
|
|
}));
|
|
for (let file of files) {
|
|
let code = pythonCodes.get(file);
|
|
if (code) {
|
|
pythonCode += `
|
|
# ${file}
|
|
` + code + "\n";
|
|
}
|
|
}
|
|
await this.executePythonCode(pythonCode);
|
|
import_coc8.workspace.onDidSaveTextDocument(async (doc) => {
|
|
let uri = import_coc8.Uri.parse(doc.uri);
|
|
if (uri.scheme != "file" || !doc.uri.endsWith(".snippets"))
|
|
return;
|
|
let filepath = uri.fsPath;
|
|
if (!import_fs5.default.existsSync(filepath))
|
|
return;
|
|
let snippetFile = this.snippetFiles.find((s) => s.filepath == filepath);
|
|
if (snippetFile) {
|
|
await this.loadSnippetsFromFile(snippetFile.filetype, snippetFile.directory, filepath);
|
|
} else {
|
|
let filetype = import_path4.default.basename(filepath, ".snippets");
|
|
await this.loadSnippetsFromFile(filetype, import_path4.default.dirname(filepath), filepath);
|
|
}
|
|
}, null, this.disposables);
|
|
}
|
|
async loadSnippetsFromFile(filetype, directory, filepath) {
|
|
let {snippets, pythonCode, extendFiletypes, clearsnippets} = await this.parser.parseUltisnipsFile(filepath);
|
|
let idx = this.snippetFiles.findIndex((o) => o.filepath == filepath);
|
|
if (idx !== -1)
|
|
this.snippetFiles.splice(idx, 1);
|
|
this.snippetFiles.push({
|
|
extendFiletypes,
|
|
clearsnippets,
|
|
directory,
|
|
filepath,
|
|
filetype,
|
|
snippets
|
|
});
|
|
if (extendFiletypes) {
|
|
let filetypes = this.config.extends[filetype] || [];
|
|
filetypes = filetypes.concat(extendFiletypes);
|
|
this.config.extends[filetype] = distinct(filetypes);
|
|
}
|
|
this.channel.appendLine(`[Info ${new Date().toISOString()}] Loaded ${snippets.length} UltiSnip snippets from: ${filepath}`);
|
|
pythonCodes.set(filepath, pythonCode);
|
|
}
|
|
async resolveSnippetBody(snippet, range, line) {
|
|
let {nvim} = import_coc8.workspace;
|
|
let {body, context, originRegex} = snippet;
|
|
let indentCount = await nvim.call("indent", ".");
|
|
let ind = " ".repeat(indentCount);
|
|
if (body.indexOf("`!p") !== -1) {
|
|
let values = new Map();
|
|
let re = /\$\{(\d+)(?::([^}]+))?\}/g;
|
|
let r;
|
|
while (r = re.exec(body)) {
|
|
let idx = parseInt(r[1], 10);
|
|
let val = r[2] || "";
|
|
let exists = values.get(idx);
|
|
if (exists == null || val && exists == "''") {
|
|
if (/^`!\w/.test(val) && val.endsWith("`")) {
|
|
let code = val.slice(1).slice(0, -1);
|
|
if (code.startsWith("!p")) {
|
|
val = "";
|
|
} else {
|
|
val = await this.parser.execute(code, this.pyMethod, ind);
|
|
}
|
|
}
|
|
val = val.replace(/'/g, "\\'").replace(/\n/g, "\\n");
|
|
values.set(idx, "r'" + val + "'");
|
|
}
|
|
}
|
|
re = /\$(\d+)/g;
|
|
while (r = re.exec(body)) {
|
|
let idx = parseInt(r[1], 10);
|
|
if (!values.has(idx)) {
|
|
values.set(idx, "''");
|
|
}
|
|
}
|
|
let len = values.size == 0 ? 0 : Math.max.apply(null, Array.from(values.keys()));
|
|
let vals = new Array(len).fill('""');
|
|
for (let [idx, val] of values.entries()) {
|
|
vals[idx] = val;
|
|
}
|
|
let pyCodes = [
|
|
"import re, os, vim, string, random",
|
|
`t = (${vals.join(",")})`,
|
|
`fn = vim.eval('expand("%:t")') or ""`,
|
|
`path = vim.eval('expand("%:p")') or ""`
|
|
];
|
|
if (context) {
|
|
pyCodes.push(`snip = ContextSnippet()`);
|
|
pyCodes.push(`context = ${context}`);
|
|
} else {
|
|
pyCodes.push(`context = {}`);
|
|
}
|
|
let start = `(${range.start.line},${Buffer.byteLength(line.slice(0, range.start.character))})`;
|
|
let end = `(${range.end.line},${Buffer.byteLength(line.slice(0, range.end.character))})`;
|
|
pyCodes.push(`snip = SnippetUtil('${ind}', ${start}, ${end}, context)`);
|
|
if (originRegex) {
|
|
pyCodes.push(`pattern = re.compile(r"${originRegex.replace(/"/g, '\\"')}")`);
|
|
pyCodes.push(`match = pattern.search("${line.replace(/"/g, '\\"')}")`);
|
|
}
|
|
await nvim.command(`${this.pyMethod} ${this.addPythonTryCatch(pyCodes.join("\n"))}`);
|
|
}
|
|
return this.parser.resolveUltisnipsBody(body);
|
|
}
|
|
addPythonTryCatch(code) {
|
|
if (!import_coc8.workspace.isVim)
|
|
return code;
|
|
let lines = [
|
|
"import traceback, vim",
|
|
`vim.vars['errmsg'] = ''`,
|
|
"try:"
|
|
];
|
|
lines.push(...code.split("\n").map((line) => " " + line));
|
|
lines.push("except Exception as e:");
|
|
lines.push(` vim.vars['errmsg'] = traceback.format_exc()`);
|
|
return lines.join("\n");
|
|
}
|
|
async checkContext(context) {
|
|
let {nvim} = import_coc8.workspace;
|
|
let pyCodes = [
|
|
"import re, os, vim, string, random",
|
|
"snip = ContextSnippet()",
|
|
`context = ${context}`
|
|
];
|
|
await nvim.command(`${this.pyMethod} ${this.addPythonTryCatch(pyCodes.join("\n"))}`);
|
|
return await nvim.call(`${this.pyMethod}eval`, "True if context else False");
|
|
}
|
|
async getTriggerSnippets(document, position, autoTrigger) {
|
|
let snippets = await this.getSnippets(document.filetype);
|
|
let line = document.getline(position.line);
|
|
line = line.slice(0, position.character);
|
|
if (!line || line[line.length - 1] == " ")
|
|
return [];
|
|
snippets = snippets.filter((s) => {
|
|
let {prefix, regex: regex2} = s;
|
|
if (autoTrigger && !s.autoTrigger)
|
|
return false;
|
|
if (regex2) {
|
|
let ms = line.match(regex2);
|
|
if (!ms)
|
|
return false;
|
|
prefix = ms[0];
|
|
}
|
|
if (!line.endsWith(prefix))
|
|
return false;
|
|
if (s.triggerKind == TriggerKind.InWord)
|
|
return true;
|
|
let pre = line.slice(0, line.length - prefix.length);
|
|
if (s.triggerKind == TriggerKind.LineBegin)
|
|
return pre.trim() == "";
|
|
if (s.triggerKind == TriggerKind.SpaceBefore)
|
|
return pre.length == 0 || /\s/.test(pre[pre.length - 1]);
|
|
if (s.triggerKind == TriggerKind.WordBoundary)
|
|
return pre.length == 0 || !document.isWord(pre[pre.length - 1]);
|
|
return false;
|
|
});
|
|
snippets.sort((a, b) => {
|
|
if (a.context && !b.context)
|
|
return -1;
|
|
if (b.context && !a.context)
|
|
return 1;
|
|
return 0;
|
|
});
|
|
let edits = [];
|
|
let contextPrefixes = [];
|
|
for (let s of snippets) {
|
|
let character;
|
|
if (s.context) {
|
|
let valid = await this.checkContext(s.context);
|
|
if (!valid)
|
|
continue;
|
|
contextPrefixes.push(s.context);
|
|
} else if (contextPrefixes.indexOf(s.prefix) !== -1) {
|
|
continue;
|
|
}
|
|
if (s.regex == null) {
|
|
character = position.character - s.prefix.length;
|
|
} else {
|
|
let len = line.match(s.regex)[0].length;
|
|
character = position.character - len;
|
|
}
|
|
let range = import_coc9.Range.create(position.line, character, position.line, position.character);
|
|
let newText = await this.resolveSnippetBody(s, range, line);
|
|
edits.push({
|
|
prefix: s.prefix,
|
|
description: s.description,
|
|
location: s.filepath,
|
|
priority: s.priority,
|
|
range,
|
|
newText
|
|
});
|
|
}
|
|
return edits;
|
|
}
|
|
async getSnippetFiles(filetype) {
|
|
let filetypes = this.getFiletypes(filetype);
|
|
let res = [];
|
|
for (let s of this.snippetFiles) {
|
|
if (filetypes.indexOf(s.filetype) !== -1) {
|
|
res.push(s.filepath);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
async getSnippets(filetype) {
|
|
let filetypes = this.getFiletypes(filetype);
|
|
filetypes.push("all");
|
|
let snippetFiles = this.snippetFiles.filter((o) => filetypes.indexOf(o.filetype) !== -1);
|
|
let min = null;
|
|
let result = [];
|
|
snippetFiles.sort((a, b) => {
|
|
if (a.filetype == b.filetype)
|
|
return 1;
|
|
if (a.filetype == filetype)
|
|
return -1;
|
|
return 1;
|
|
});
|
|
for (let file of snippetFiles) {
|
|
let {snippets, clearsnippets} = file;
|
|
if (typeof clearsnippets == "number") {
|
|
min = min ? Math.max(min, clearsnippets) : clearsnippets;
|
|
}
|
|
for (let snip of snippets) {
|
|
if (snip.regex || snip.context) {
|
|
result.push(snip);
|
|
} else {
|
|
let idx = result.findIndex((o) => o.prefix == snip.prefix && o.triggerKind == snip.triggerKind);
|
|
if (idx == -1) {
|
|
result.push(snip);
|
|
} else {
|
|
let item = result[idx];
|
|
if (snip.priority > item.priority) {
|
|
result[idx] = item;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (min != null)
|
|
result = result.filter((o) => o.priority >= min);
|
|
result.sort((a, b) => {
|
|
if (a.context && !b.context)
|
|
return -1;
|
|
if (b.context && !a.context)
|
|
return 1;
|
|
return 0;
|
|
});
|
|
return result;
|
|
}
|
|
async getAllSnippetFiles(runtimepath) {
|
|
let {directories} = this;
|
|
let res = [];
|
|
for (let directory of directories) {
|
|
if (import_path4.default.isAbsolute(directory)) {
|
|
let items = await this.getSnippetFileItems(directory);
|
|
res.push(...items);
|
|
}
|
|
}
|
|
let subFolders = await this.getSubFolders();
|
|
let rtps = runtimepath.split(",");
|
|
this.runtimeDirs = rtps;
|
|
for (let rtp of rtps) {
|
|
let items = await this.getSnippetsFromPlugin(rtp, subFolders);
|
|
res.push(...items);
|
|
}
|
|
return res;
|
|
}
|
|
async getSubFolders() {
|
|
let {directories} = this;
|
|
directories = directories.filter((s) => !import_path4.default.isAbsolute(s));
|
|
let dirs = await import_coc8.workspace.nvim.eval('get(g:, "UltiSnipsSnippetDirectories", [])');
|
|
for (let dir of dirs) {
|
|
if (directories.indexOf(dir) == -1) {
|
|
directories.push(dir);
|
|
}
|
|
}
|
|
return directories;
|
|
}
|
|
async getSnippetsFromPlugin(directory, subFolders) {
|
|
let res = [];
|
|
for (let folder of subFolders) {
|
|
let items = await this.getSnippetFileItems(import_path4.default.join(directory, folder));
|
|
res.push(...items);
|
|
}
|
|
return res;
|
|
}
|
|
async getSnippetFileItems(directory) {
|
|
let res = [];
|
|
let stat = await statAsync(directory);
|
|
if (stat && stat.isDirectory()) {
|
|
let files = await readdirAsync(directory);
|
|
if (files.length) {
|
|
for (let f of files) {
|
|
let file = import_path4.default.join(directory, f);
|
|
if (file.endsWith(".snippets")) {
|
|
let basename = import_path4.default.basename(f, ".snippets");
|
|
let filetype = basename.split("_", 2)[0];
|
|
res.push({filepath: file, directory, filetype});
|
|
} else {
|
|
let stat2 = await statAsync(file);
|
|
if (stat2 && stat2.isDirectory()) {
|
|
let files2 = await readdirAsync(file);
|
|
for (let filename of files2) {
|
|
if (filename.endsWith(".snippets")) {
|
|
res.push({filepath: import_path4.default.join(file, filename), directory, filetype: f});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
async executePythonCode(pythonCode) {
|
|
try {
|
|
let dir = import_path4.default.join(import_os3.default.tmpdir(), `coc.nvim-${process.pid}`);
|
|
if (!import_fs5.default.existsSync(dir))
|
|
import_fs5.default.mkdirSync(dir);
|
|
let tmpfile = import_path4.default.join(import_os3.default.tmpdir(), `coc.nvim-${process.pid}`, `coc-ultisnips-${uid()}.py`);
|
|
let code = this.addPythonTryCatch(pythonCode);
|
|
import_fs5.default.writeFileSync(tmpfile, "# -*- coding: utf-8 -*-\n" + code, "utf8");
|
|
await import_coc8.workspace.nvim.command(`exe '${this.pyMethod}file '.fnameescape('${tmpfile}')`);
|
|
pythonCodes.clear();
|
|
} catch (e) {
|
|
this.channel.appendLine(`Error on execute python script:`);
|
|
this.channel.append(e.message);
|
|
import_coc8.window.showMessage(`Error on execute python script: ${e.message}`, "error");
|
|
}
|
|
}
|
|
dispose() {
|
|
import_coc8.disposeAll(this.disposables);
|
|
}
|
|
};
|
|
|
|
// src/index.ts
|
|
var documentation = `# A valid snippet should starts with:
|
|
#
|
|
# snippet trigger_word [ "description" [ options ] ]
|
|
#
|
|
# and end with:
|
|
#
|
|
# endsnippet
|
|
#
|
|
# Snippet options:
|
|
#
|
|
# b - Beginning of line.
|
|
# i - In-word expansion.
|
|
# w - Word boundary.
|
|
# r - Regular expression
|
|
# e - Custom context snippet
|
|
# A - Snippet will be triggered automatically, when condition matches.
|
|
#
|
|
# Basic example:
|
|
#
|
|
# snippet emitter "emitter properties" b
|
|
# private readonly \${1} = new Emitter<$2>()
|
|
# public readonly \${1/^_(.*)/$1/}: Event<$2> = this.$1.event
|
|
# endsnippet
|
|
#
|
|
# Online reference: https://github.com/SirVer/ultisnips/blob/master/doc/UltiSnips.txt
|
|
`;
|
|
async function waitDocument(doc, changedtick) {
|
|
if (import_coc10.workspace.isNvim)
|
|
return true;
|
|
return new Promise((resolve) => {
|
|
let timeout = setTimeout(() => {
|
|
disposable.dispose();
|
|
resolve(doc.changedtick == changedtick);
|
|
}, 200);
|
|
let disposable = doc.onDocumentChange(() => {
|
|
clearTimeout(timeout);
|
|
disposable.dispose();
|
|
if (doc.changedtick == changedtick) {
|
|
resolve(true);
|
|
} else {
|
|
resolve(false);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
async function activate(context) {
|
|
let {subscriptions} = context;
|
|
const {nvim} = import_coc10.workspace;
|
|
const configuration = import_coc10.workspace.getConfiguration("snippets");
|
|
const filetypeExtends = configuration.get("extends", {});
|
|
const trace = configuration.get("trace", "error");
|
|
let mru = import_coc10.workspace.createMru("snippets-mru");
|
|
const channel = import_coc10.window.createOutputChannel("snippets");
|
|
const manager = new ProviderManager(channel);
|
|
let snippetsDir = configuration.get("userSnippetsDirectory");
|
|
if (snippetsDir) {
|
|
snippetsDir = snippetsDir.replace(/^~/, import_os4.default.homedir());
|
|
if (snippetsDir.indexOf("$") !== -1) {
|
|
snippetsDir = snippetsDir.replace(/\$(\w+)/g, (match, p1) => {
|
|
var _a;
|
|
return (_a = process.env[p1]) != null ? _a : match;
|
|
});
|
|
}
|
|
if (!import_path5.default.isAbsolute(snippetsDir)) {
|
|
import_coc10.window.showMessage(`snippets.userSnippetsDirectory => ${snippetsDir} should be absolute path`, "warning");
|
|
snippetsDir = null;
|
|
}
|
|
}
|
|
if (!snippetsDir)
|
|
snippetsDir = import_path5.default.join(import_path5.default.dirname(import_coc10.workspace.env.extensionRoot), "ultisnips");
|
|
if (!import_fs6.default.existsSync(snippetsDir)) {
|
|
await import_util9.default.promisify(import_fs6.default.mkdir)(snippetsDir);
|
|
}
|
|
import_coc10.events.on("CompleteDone", async (item) => {
|
|
if (typeof item.user_data === "string" && item.user_data.indexOf("snippets") !== -1) {
|
|
await mru.add(item.word);
|
|
}
|
|
}, null, subscriptions);
|
|
import_coc10.workspace.onDidOpenTextDocument(async (document) => {
|
|
if (document.uri.endsWith(".snippets")) {
|
|
let doc = import_coc10.workspace.getDocument(document.uri);
|
|
if (!doc)
|
|
return;
|
|
let {buffer} = doc;
|
|
await buffer.setOption("filetype", "snippets");
|
|
}
|
|
}, null, subscriptions);
|
|
if (configuration.get("ultisnips.enable", true)) {
|
|
let config2 = configuration.get("ultisnips", {});
|
|
let c = Object.assign({}, config2, {
|
|
extends: Object.assign({}, filetypeExtends)
|
|
});
|
|
c.directories = c.directories ? c.directories.slice() : [];
|
|
if (c.directories.indexOf(snippetsDir) == -1) {
|
|
c.directories.push(snippetsDir);
|
|
}
|
|
let provider2 = new UltiSnippetsProvider(channel, trace, c, context);
|
|
manager.regist(provider2, "ultisnips");
|
|
subscriptions.push(provider2);
|
|
nvim.getOption("runtimepath").then(async (rtp) => {
|
|
let paths = rtp.split(",");
|
|
let idx = paths.findIndex((s) => /^ultisnips$/i.test(import_path5.default.basename(s)));
|
|
if (idx !== -1)
|
|
return;
|
|
let directory = import_path5.default.resolve(__dirname, "..");
|
|
nvim.command("autocmd BufNewFile,BufRead *.snippets setf snippets", true);
|
|
nvim.command(`execute 'noa set rtp+='.fnameescape('${directory.replace(/'/g, "''")}')`, true);
|
|
import_coc10.workspace.documents.forEach((doc) => {
|
|
if (doc.uri.endsWith(".snippets")) {
|
|
doc.buffer.setOption("filetype", "snippets", true);
|
|
}
|
|
});
|
|
}, (_e) => {
|
|
});
|
|
}
|
|
let config = {
|
|
loadFromExtensions: configuration.get("loadFromExtensions", true),
|
|
snippetsRoots: configuration.get("textmateSnippetsRoots", []),
|
|
extends: Object.assign({}, filetypeExtends)
|
|
};
|
|
let provider = new TextmateProvider(channel, trace, config);
|
|
manager.regist(provider, "snippets");
|
|
if (configuration.get("snipmate.enable", true)) {
|
|
let config2 = {
|
|
author: configuration.get("snipmate.author", ""),
|
|
extends: Object.assign({}, filetypeExtends)
|
|
};
|
|
let provider2 = new SnipmateProvider(channel, trace, config2);
|
|
manager.regist(provider2, "snipmate");
|
|
}
|
|
if (configuration.get("autoTrigger", true)) {
|
|
let insertTs;
|
|
let insertLeaveTs;
|
|
let lastInsert;
|
|
import_coc10.events.on("InsertCharPre", (character) => {
|
|
insertTs = Date.now();
|
|
lastInsert = character;
|
|
}, null, subscriptions);
|
|
import_coc10.events.on("InsertLeave", () => {
|
|
insertLeaveTs = Date.now();
|
|
}, null, subscriptions);
|
|
let inserting = false;
|
|
const handleTextChange = async (bufnr, pre, changedtick) => {
|
|
let lastInsertTs = insertTs;
|
|
if (inserting)
|
|
return;
|
|
let doc = import_coc10.workspace.getDocument(bufnr);
|
|
if (!doc || doc.isCommandLine || !doc.attached)
|
|
return;
|
|
let now = Date.now();
|
|
if (!lastInsertTs || now - lastInsertTs > 100 || !pre.endsWith(lastInsert))
|
|
return;
|
|
let res = await waitDocument(doc, changedtick);
|
|
if (!res)
|
|
return;
|
|
let edits = await manager.getTriggerSnippets(bufnr, true);
|
|
if (edits.length == 0)
|
|
return;
|
|
if (edits.length > 1) {
|
|
channel.appendLine(`Multiple snippet found for auto trigger: ${edits.map((s) => s.prefix).join(", ")}`);
|
|
import_coc10.window.showMessage("Multiple snippet found for auto trigger, check output by :CocCommand workspace.showOutput", "warning");
|
|
}
|
|
if (insertLeaveTs > now || insertTs > now || inserting)
|
|
return;
|
|
inserting = true;
|
|
try {
|
|
await import_coc10.commands.executeCommand("editor.action.insertSnippet", edits[0]);
|
|
await mru.add(edits[0].prefix);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
inserting = false;
|
|
};
|
|
import_coc10.events.on("TextChangedI", async (bufnr, info) => {
|
|
await handleTextChange(bufnr, info.pre, info.changedtick);
|
|
}, null, subscriptions);
|
|
import_coc10.events.on("TextChangedP", async (bufnr, info) => {
|
|
await handleTextChange(bufnr, info.pre, info.changedtick);
|
|
}, null, subscriptions);
|
|
}
|
|
let statusItem;
|
|
if (configuration.get("enableStatusItem", true)) {
|
|
statusItem = import_coc10.window.createStatusBarItem(90, {progress: true});
|
|
statusItem.text = "loading snippets";
|
|
statusItem.show();
|
|
}
|
|
manager.init().then(() => {
|
|
statusItem == null ? void 0 : statusItem.hide();
|
|
});
|
|
if (manager.hasProvider) {
|
|
let disposable = import_coc10.languages.registerCompletionItemProvider("snippets", configuration.get("shortcut", "S"), null, manager, configuration.get("triggerCharacters", []), configuration.get("priority", 90));
|
|
subscriptions.push(disposable);
|
|
}
|
|
async function fallback() {
|
|
await nvim.call("coc#start", [{source: "snippets"}]);
|
|
}
|
|
async function doExpand(bufnr) {
|
|
let edits = await manager.getTriggerSnippets(bufnr);
|
|
if (edits.length == 0)
|
|
return false;
|
|
if (edits.length == 1) {
|
|
await import_coc10.commands.executeCommand("editor.action.insertSnippet", edits[0]);
|
|
await mru.add(edits[0].prefix);
|
|
} else {
|
|
let idx = await import_coc10.window.showQuickpick(edits.map((e) => e.description || e.prefix), "choose snippet:");
|
|
if (idx == -1)
|
|
return;
|
|
await import_coc10.commands.executeCommand("editor.action.insertSnippet", edits[idx]);
|
|
await mru.add(edits[idx].prefix);
|
|
}
|
|
return true;
|
|
}
|
|
subscriptions.push(import_coc10.workspace.registerKeymap(["x"], "convert-snippet", async () => {
|
|
let mode = await import_coc10.workspace.nvim.call("visualmode");
|
|
if (!mode)
|
|
return;
|
|
let doc = await import_coc10.workspace.document;
|
|
if (!doc)
|
|
return;
|
|
let range = await import_coc10.workspace.getSelectedRange(mode, doc);
|
|
let text = doc.textDocument.getText(range);
|
|
if (text)
|
|
await import_coc10.commands.executeCommand("snippets.editSnippets", text);
|
|
}, {sync: false}));
|
|
subscriptions.push(import_coc10.commands.registerCommand("snippets.editSnippets", async (text) => {
|
|
let buf = await nvim.buffer;
|
|
let doc = import_coc10.workspace.getDocument(buf.id);
|
|
if (!doc) {
|
|
import_coc10.window.showMessage("Document not found", "error");
|
|
return;
|
|
}
|
|
let file = import_path5.default.join(snippetsDir, `${doc.filetype}.snippets`);
|
|
if (!import_fs6.default.existsSync(file)) {
|
|
await import_util9.default.promisify(import_fs6.default.writeFile)(file, documentation, "utf8");
|
|
}
|
|
let uri = import_coc10.Uri.file(file).toString();
|
|
await import_coc10.workspace.jumpTo(uri, null, configuration.get("editSnippetsCommand"));
|
|
if (text) {
|
|
await nvim.command("normal! G");
|
|
await nvim.command("normal! 2o");
|
|
let position = await import_coc10.window.getCursorPosition();
|
|
let indent = text.match(/^\s*/)[0];
|
|
text = text.split(/\r?\n/).map((s) => s.startsWith(indent) ? s.slice(indent.length) : s).join("\n");
|
|
let escaped = text.replace(/([$}\]])/g, "\\$1");
|
|
let snippet = 'snippet ${1:Tab_trigger} "${2:Description}" ${3:b}\n' + escaped + "\nendsnippet";
|
|
let edit2 = import_coc10.TextEdit.insert(position, snippet);
|
|
await import_coc10.commands.executeCommand("editor.action.insertSnippet", edit2);
|
|
}
|
|
}));
|
|
subscriptions.push(import_coc10.commands.registerCommand("snippets.openSnippetFiles", async () => {
|
|
let buf = await nvim.buffer;
|
|
let doc = import_coc10.workspace.getDocument(buf.id);
|
|
if (!doc) {
|
|
import_coc10.window.showMessage("Document not found", "error");
|
|
return;
|
|
}
|
|
let files = await manager.getSnippetFiles(doc.filetype);
|
|
if (!files.length) {
|
|
import_coc10.window.showMessage("No related snippet file found", "warning");
|
|
return;
|
|
}
|
|
let idx = await import_coc10.window.showQuickpick(files, "choose snippet file:");
|
|
if (idx == -1)
|
|
return;
|
|
let uri = import_coc10.Uri.file(files[idx]).toString();
|
|
await import_coc10.workspace.jumpTo(uri, null, configuration.get("editSnippetsCommand"));
|
|
}));
|
|
subscriptions.push(import_coc10.workspace.registerKeymap(["i"], "snippets-expand", async () => {
|
|
let bufnr = await nvim.eval('bufnr("%")');
|
|
let expanded = await doExpand(bufnr);
|
|
if (!expanded)
|
|
await fallback();
|
|
}, {silent: true, sync: true, cancel: true}));
|
|
subscriptions.push(import_coc10.workspace.registerKeymap(["i"], "snippets-expand-jump", async () => {
|
|
let bufnr = await nvim.eval('bufnr("%")');
|
|
let expanded = await doExpand(bufnr);
|
|
if (!expanded) {
|
|
let session = import_coc10.snippetManager.getSession(bufnr);
|
|
if (session && session.isActive) {
|
|
await nvim.call("coc#_cancel", []);
|
|
await import_coc10.snippetManager.nextPlaceholder();
|
|
return;
|
|
}
|
|
await fallback();
|
|
}
|
|
}, {silent: true, sync: true, cancel: true}));
|
|
subscriptions.push(import_coc10.workspace.registerKeymap(["v"], "snippets-select", async () => {
|
|
let doc = await import_coc10.workspace.document;
|
|
if (!doc)
|
|
return;
|
|
let mode = await nvim.call("visualmode");
|
|
if (["v", "V"].indexOf(mode) == -1) {
|
|
import_coc10.window.showMessage(`visual mode ${mode} not supported`, "warning");
|
|
return;
|
|
}
|
|
await nvim.command("normal! `<");
|
|
let start = await import_coc10.window.getCursorPosition();
|
|
await nvim.command("normal! `>");
|
|
let end = await import_coc10.window.getCursorPosition();
|
|
end = import_coc10.Position.create(end.line, end.character + 1);
|
|
let range = import_coc10.Range.create(start, end);
|
|
let text = doc.textDocument.getText(range);
|
|
await nvim.call("feedkeys", ["i", "in"]);
|
|
if (mode == "v") {
|
|
await doc.applyEdits([{range, newText: ""}]);
|
|
} else {
|
|
let currline = doc.getline(start.line);
|
|
let indent = currline.match(/^\s*/)[0];
|
|
let lines = text.split(/\r?\n/);
|
|
lines = lines.map((s) => s.startsWith(indent) ? s.slice(indent.length) : s);
|
|
text = lines.join("\n");
|
|
range = import_coc10.Range.create(import_coc10.Position.create(start.line, indent.length), end);
|
|
await doc.applyEdits([{range, newText: ""}]);
|
|
}
|
|
await nvim.setVar("coc_selected_text", text);
|
|
await import_coc10.window.moveTo(range.start);
|
|
}, {silent: true, sync: false, cancel: true}));
|
|
let languageProvider = new languages_default(channel, trace);
|
|
subscriptions.push(import_coc10.languages.registerCompletionItemProvider("snippets-source", configuration.get("shortcut", "S"), ["snippets"], languageProvider, ["$"], configuration.get("priority", 90)));
|
|
subscriptions.push(statusItem);
|
|
subscriptions.push(channel);
|
|
subscriptions.push(import_coc10.listManager.registerList(new snippet_default(import_coc10.workspace.nvim, manager, mru)));
|
|
return {
|
|
expandable: async () => {
|
|
let bufnr = await nvim.eval('bufnr("%")');
|
|
let edits = await manager.getTriggerSnippets(bufnr);
|
|
return edits && edits.length > 0;
|
|
}
|
|
};
|
|
}
|
|
//# sourceMappingURL=index.js.map
|