mirror of
https://github.com/any86/any-rule.git
synced 2025-07-14 15:38:58 +08:00
merge: 和并"zz."功能
This commit is contained in:
commit
8c83e20d11
142
src/anyrule.ts
Normal file
142
src/anyrule.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { ExtensionContext, CompletionItem, CompletionItemKind, languages, Disposable, workspace, window, commands, TextDocument, Position, Range, Selection } from "vscode";
|
||||
import { generateFilterString, getRulesByText } from "./utils";
|
||||
import { IRule } from "./interface";
|
||||
import { loadRules } from './loader';
|
||||
import { RegexDiagram } from './diagram/panel';
|
||||
|
||||
export class AnyRule {
|
||||
context: ExtensionContext;
|
||||
disposable: Disposable | null = null;
|
||||
rules: IRule[] | null = null;
|
||||
regexDiagram: RegexDiagram | null = null;
|
||||
constructor(context: ExtensionContext) {
|
||||
this.context = context;
|
||||
this.regexDiagram = new RegexDiagram(context);
|
||||
loadRules(context.extensionPath).then(rules => {
|
||||
this.rules = rules;
|
||||
this.load();
|
||||
this.oldFunctionCompatible();
|
||||
});
|
||||
}
|
||||
public load() {
|
||||
let currentRules: IRule[] = [];
|
||||
const configuration = workspace.getConfiguration();
|
||||
let START_IDENTIFIER: string = configuration.get('anyRule.triggerString') || 'zz';
|
||||
const setting: string = configuration.get('anyRule.supportedLanguages') || 'javascript,typescirpt' as string;
|
||||
const supportedLanguages = setting.split(',');
|
||||
this.commandRegisters(START_IDENTIFIER);
|
||||
this.disposable = languages.registerCompletionItemProvider(supportedLanguages, {
|
||||
provideCompletionItems: (document, position, token, context) => {
|
||||
const line = document.lineAt(position);
|
||||
const lineText = line.text.substring(0, position.character);
|
||||
if (new RegExp(`${START_IDENTIFIER}\.`, 'g').test(lineText)) {
|
||||
currentRules = getRulesByText(START_IDENTIFIER, this.rules || [], lineText);
|
||||
return currentRules.map(rule => {
|
||||
const item = new CompletionItem(rule.title, rule.regex ? CompletionItemKind.Field : CompletionItemKind.Folder);
|
||||
// @ts-ignore
|
||||
item.rule = rule;
|
||||
// item.commitCharacters = ['.'];
|
||||
item.filterText = generateFilterString(rule);
|
||||
item.documentation = rule.regex ? `${rule.title}\n${rule.examples ? '\n示例:\n' + rule.examples.join('\n') : ''}` : undefined;
|
||||
item.command = {
|
||||
title: '插入正则',
|
||||
command: 'functions.insertRegex',
|
||||
arguments: [document, position, rule]
|
||||
};
|
||||
return item;
|
||||
});
|
||||
}
|
||||
},
|
||||
resolveCompletionItem: (item: CompletionItem) => {
|
||||
// @ts-ignore
|
||||
const rule: IRule = item.rule;
|
||||
if (rule.regex) {
|
||||
return null;
|
||||
} else {
|
||||
item.insertText = item.label + '.';
|
||||
return item;
|
||||
}
|
||||
},
|
||||
}, '.');
|
||||
|
||||
this.context.subscriptions.push(this.disposable);
|
||||
// window.showInformationMessage('AnyRule加载成功');
|
||||
}
|
||||
|
||||
public reload() {
|
||||
if (this.disposable) {
|
||||
this.disposable.dispose();
|
||||
}
|
||||
|
||||
this.load();
|
||||
}
|
||||
|
||||
public update() {
|
||||
loadRules(this.context.extensionPath, true).then(rules => {
|
||||
this.rules = rules;
|
||||
this.reload();
|
||||
window.showInformationMessage('正则库已更新');
|
||||
});
|
||||
}
|
||||
|
||||
private commandRegisters(START_IDENTIFIER: string) {
|
||||
commands.getCommands().then((commandList) => {
|
||||
if (commandList.indexOf('functions.insertRegex') !== -1) {
|
||||
return;
|
||||
}
|
||||
commands.registerCommand('functions.insertRegex', (document: TextDocument, position: Position, rule: IRule) => {
|
||||
if (rule.regex) {
|
||||
const editor = window.activeTextEditor;
|
||||
editor?.edit(editBuilder => {
|
||||
const line = document.lineAt(position);
|
||||
const start = line.text.indexOf(START_IDENTIFIER);
|
||||
if (start === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rule.regex) {
|
||||
editBuilder.replace(
|
||||
new Range(new Position(line.lineNumber, start),
|
||||
new Position(line.lineNumber, line.text.length)),
|
||||
String(rule.regex)
|
||||
);
|
||||
// TODO 处理输入文本后选中字符串的问题
|
||||
setTimeout(() => {
|
||||
const end = new Position(line.lineNumber, line.text.length + String(rule.regex).length);
|
||||
editor.selection = new Selection(end, end);
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
commands.executeCommand('editor.action.triggerSuggest');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧的功能,大概率会在未来废弃,仅过度使用
|
||||
*/
|
||||
private oldFunctionCompatible() {
|
||||
this.rules?.forEach((rule, index) => {
|
||||
commands.registerCommand(`extension.rule${index}`, () => {
|
||||
const editor = window.activeTextEditor;
|
||||
if (editor) {
|
||||
const { selections } = editor;
|
||||
|
||||
editor.edit(editBuilder => {
|
||||
selections.forEach(selection => {
|
||||
const { start, end } = selection;
|
||||
const range = new Range(start, end);
|
||||
editBuilder.replace(range, String(rule.regex));
|
||||
});
|
||||
});
|
||||
// Display a message box to the user
|
||||
window.showInformationMessage(`已插入正则: ${rule.title}`);
|
||||
} else {
|
||||
window.showWarningMessage('any-rule: 只有在编辑文本的时候才可以使用!');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
0
src/constant.ts
Normal file
0
src/constant.ts
Normal file
107
src/diagram/index.html
Normal file
107
src/diagram/index.html
Normal file
File diff suppressed because one or more lines are too long
19
src/diagram/panel.ts
Normal file
19
src/diagram/panel.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { WebviewPanel, window, ViewColumn, Uri, workspace, ExtensionContext } from "vscode";
|
||||
import { join } from 'path';
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
export class RegexDiagram {
|
||||
regex: string | null = null;
|
||||
panel: WebviewPanel | null = null;
|
||||
constructor(context: ExtensionContext) {
|
||||
this.panel = window.createWebviewPanel('regexDiagram', '图解正则表达式', ViewColumn.Two, {
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: false,
|
||||
});
|
||||
console.log(join(context.extensionPath, 'src/diagram', './index.html'));
|
||||
this.panel.webview.html = readFileSync(join(context.extensionPath, 'src/diagram', './index.html')).toString();
|
||||
this.panel.webview.postMessage({
|
||||
regex: this.regex || '/aaaaab{1,3}/',
|
||||
});
|
||||
}
|
||||
}
|
@ -1,10 +1,16 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
const RULES = require('../packages/www/src/RULES.js');
|
||||
import {
|
||||
window, workspace, commands,
|
||||
ExtensionContext, ConfigurationChangeEvent
|
||||
} from 'vscode';
|
||||
import { IRule } from './interface';
|
||||
import { AnyRule } from './anyrule';
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
export function activate(context: ExtensionContext) {
|
||||
|
||||
// Use the console to output diagnostic information (console.log) and errors (console.error)
|
||||
// This line of code will only be executed once when your extension is activated
|
||||
console.log('Congratulations, your extension "any-rule" is now active!');
|
||||
@ -12,44 +18,44 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
// The command has been defined in the package.json file
|
||||
// Now provide the implementation of the command with registerCommand
|
||||
// The commandId parameter must match the command field in package.json
|
||||
|
||||
|
||||
// let disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
|
||||
// The code you place here will be executed every time your command is executed
|
||||
// // The code you place here will be executed every time your command is executed
|
||||
|
||||
// Display a message box to the user
|
||||
// vscode.window.showInformationMessage('Hello World123!');
|
||||
// });
|
||||
// context.subscriptions.push(disposable);
|
||||
|
||||
RULES.forEach(({ title, rule }: { title: string, rule: RegExp, example: string }, index: string) => {
|
||||
let disposable = vscode.commands.registerCommand(`extension.rule${index}`, () => {
|
||||
// The code you place here will be executed every time your command is executed
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (editor) {
|
||||
const { selections } = editor;
|
||||
|
||||
editor.edit(editBuilder => {
|
||||
selections.forEach(selection => {
|
||||
const { start, end } = selection;
|
||||
const range = new vscode.Range(start, end);
|
||||
editBuilder.replace(range, String(rule));
|
||||
const anyRule = new AnyRule(context);
|
||||
|
||||
workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
|
||||
anyRule.reload();
|
||||
});
|
||||
|
||||
commands.registerCommand('extension.update', () => {
|
||||
anyRule.update();
|
||||
});
|
||||
// Display a message box to the user
|
||||
vscode.window.showInformationMessage(`已插入正则: ${title}`);
|
||||
} else {
|
||||
vscode.window.showWarningMessage('any-rule: 只有在编辑文本的时候才可以使用!');
|
||||
commands.registerCommand('extension.reload', () => {
|
||||
anyRule.reload();
|
||||
window.showInformationMessage('重新加载插件成功');
|
||||
});
|
||||
commands.registerCommand('extension.support', () => {
|
||||
const currentLanguage = window.activeTextEditor?.document.languageId;
|
||||
if (currentLanguage) {
|
||||
try {
|
||||
const configuration = workspace.getConfiguration();
|
||||
const setting: string = configuration.get('anyRule.supportedLanguages') || 'javascript,typescirpt' as string;
|
||||
const supportedLanguages = setting.split(',');
|
||||
const set = new Set(supportedLanguages);
|
||||
set.add(currentLanguage);
|
||||
console.log(Array.from(set).join(','));
|
||||
configuration.update('anyRule.supportedLanguages', Array.from(set).join(',')).then(() => {
|
||||
anyRule.reload();
|
||||
});
|
||||
window.showInformationMessage('更新关联语言成功');
|
||||
} catch(e) {
|
||||
window.showInformationMessage('更新关联语言失败');
|
||||
}
|
||||
}
|
||||
});
|
||||
context.subscriptions.push(disposable);
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() {
|
||||
vscode.window.showWarningMessage('any-rule: 已关闭!');
|
||||
}
|
||||
export function deactivate() { }
|
||||
|
7
src/interface.d.ts
vendored
Normal file
7
src/interface.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export interface IRule {
|
||||
title: string;
|
||||
keywords?: string[];
|
||||
regex?: RegExp | string;
|
||||
rules?: IRule[];
|
||||
examples?: string[];
|
||||
}
|
45
src/loader.ts
Normal file
45
src/loader.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import axios from 'axios';
|
||||
import { IRule } from './interface';
|
||||
import { writeFileSync, readFileSync } from 'fs';
|
||||
import { join as pathJoin } from 'path';
|
||||
|
||||
async function loadRulesFromFile(path: string): Promise<IRule[] | null> {
|
||||
try {
|
||||
const json = readFileSync(path);
|
||||
return JSON.parse(json.toString()) as IRule[];
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRulesFromWeb(): Promise<IRule[]> {
|
||||
const dataSources = [
|
||||
'https://raw.githubusercontent.com/any86/any-rule/feature/vscode-refactor/rules.json'
|
||||
];
|
||||
let rules: IRule[] = [];
|
||||
for (const source of dataSources) {
|
||||
try {
|
||||
const response = await axios.get(source);
|
||||
const body = response.data;
|
||||
rules = body as IRule[];
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
export async function loadRules (extensionPath: string, force: boolean = false): Promise<IRule[]> {
|
||||
const rulePath = pathJoin(extensionPath, 'rules.json');
|
||||
let rules: IRule[] | null = null;
|
||||
if (!force) {
|
||||
rules = await loadRulesFromFile(rulePath);
|
||||
}
|
||||
if (!rules) {
|
||||
rules = await loadRulesFromWeb();
|
||||
writeFileSync(rulePath, Buffer.from(JSON.stringify(rules)));
|
||||
}
|
||||
return rules;
|
||||
}
|
91
src/utils.ts
Normal file
91
src/utils.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { IRule } from './interface';
|
||||
// import { convertToPinyin } from 'tiny-pinyin';
|
||||
import { slugify } from 'transliteration';
|
||||
|
||||
function preprocessText(START_IDENTIFIER: string, text: string): string[] | null {
|
||||
const start = text.indexOf(START_IDENTIFIER);
|
||||
if (start === -1) {
|
||||
return null;
|
||||
}
|
||||
const pathString = text.substring(start, text.length);
|
||||
const pathArray = pathString.split('.');
|
||||
return pathArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据特定的字符串解析出当前可以用的规则列表
|
||||
* @param text 待解析的字符串
|
||||
*/
|
||||
export function getRulesByText(START_IDENTIFIER: string, rules: IRule[], text: string): IRule[] {
|
||||
const pathArray = preprocessText(START_IDENTIFIER, text);
|
||||
if (!pathArray) {
|
||||
return [];
|
||||
}
|
||||
let currentRules: IRule[] = [];
|
||||
let targetRules = rules;
|
||||
|
||||
for (const path of pathArray) {
|
||||
if (path === START_IDENTIFIER) {
|
||||
currentRules = rules;
|
||||
} else if (path === '') {
|
||||
break;
|
||||
} else {
|
||||
const searchRule = targetRules.find(rule => rule.title === path);
|
||||
|
||||
if (!searchRule) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (searchRule.regex) {
|
||||
return [];
|
||||
}
|
||||
|
||||
currentRules = searchRule.rules || [];
|
||||
targetRules = currentRules;
|
||||
}
|
||||
}
|
||||
|
||||
return currentRules;
|
||||
}
|
||||
|
||||
export function getRuleByText(START_IDENTIFIER: string, rules: IRule[], text: string): IRule | null {
|
||||
const pathArray = preprocessText(START_IDENTIFIER, text);
|
||||
if (!pathArray) {
|
||||
return null;
|
||||
}
|
||||
let targetRules = rules;
|
||||
let searchRule: IRule | undefined;
|
||||
for (const path of pathArray) {
|
||||
if (path === START_IDENTIFIER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
searchRule = targetRules.find(rule => rule.title === path);
|
||||
if (!searchRule) {
|
||||
return null;
|
||||
}
|
||||
if (searchRule.regex) {
|
||||
break;
|
||||
} else {
|
||||
targetRules = searchRule.rules || [];
|
||||
}
|
||||
}
|
||||
|
||||
return searchRule || null;
|
||||
}
|
||||
|
||||
export function generateFilterString(rule: IRule) {
|
||||
let filterString = '';
|
||||
filterString += rule.title;
|
||||
const pinyin = slugify(rule.title).split('-');
|
||||
if (/.*[\u4e00-\u9fa5]+.*$/.test(rule.title)) {
|
||||
filterString += ' ' + pinyin.join('');
|
||||
filterString += ' ' + pinyin.map(item => item.length ? item[0] : '');
|
||||
}
|
||||
|
||||
if (rule.keywords) {
|
||||
filterString += ' ' + rule.keywords.join(' ');
|
||||
}
|
||||
|
||||
return filterString;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user