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
|
// The module 'vscode' contains the VS Code extensibility API
|
||||||
// Import the module and reference it with the alias vscode in your code below
|
// Import the module and reference it with the alias vscode in your code below
|
||||||
import * as vscode from 'vscode';
|
import {
|
||||||
const RULES = require('../packages/www/src/RULES.js');
|
window, workspace, commands,
|
||||||
|
ExtensionContext, ConfigurationChangeEvent
|
||||||
|
} from 'vscode';
|
||||||
|
import { IRule } from './interface';
|
||||||
|
import { AnyRule } from './anyrule';
|
||||||
|
|
||||||
// this method is called when your extension is activated
|
// this method is called when your extension is activated
|
||||||
// your extension is activated the very first time the command is executed
|
// 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)
|
// 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
|
// This line of code will only be executed once when your extension is activated
|
||||||
console.log('Congratulations, your extension "any-rule" is now active!');
|
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
|
// The command has been defined in the package.json file
|
||||||
// Now provide the implementation of the command with registerCommand
|
// Now provide the implementation of the command with registerCommand
|
||||||
// The commandId parameter must match the command field in package.json
|
// The commandId parameter must match the command field in package.json
|
||||||
|
|
||||||
|
|
||||||
// let disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
|
// 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 => {
|
const anyRule = new AnyRule(context);
|
||||||
selections.forEach(selection => {
|
|
||||||
const { start, end } = selection;
|
workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
|
||||||
const range = new vscode.Range(start, end);
|
anyRule.reload();
|
||||||
editBuilder.replace(range, String(rule));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
commands.registerCommand('extension.update', () => {
|
||||||
|
anyRule.update();
|
||||||
});
|
});
|
||||||
// Display a message box to the user
|
commands.registerCommand('extension.reload', () => {
|
||||||
vscode.window.showInformationMessage(`已插入正则: ${title}`);
|
anyRule.reload();
|
||||||
} else {
|
window.showInformationMessage('重新加载插件成功');
|
||||||
vscode.window.showWarningMessage('any-rule: 只有在编辑文本的时候才可以使用!');
|
});
|
||||||
|
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
|
// this method is called when your extension is deactivated
|
||||||
export function deactivate() {
|
export function deactivate() { }
|
||||||
vscode.window.showWarningMessage('any-rule: 已关闭!');
|
|
||||||
}
|
|
||||||
|
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