merge: 和并"zz."功能

This commit is contained in:
宁宁 2020-02-20 12:09:29 +08:00
commit 8c83e20d11
8 changed files with 452 additions and 35 deletions

142
src/anyrule.ts Normal file
View 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
View 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
View 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}/',
});
}
}

View File

@ -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;
const anyRule = new AnyRule(context);
editor.edit(editBuilder => {
selections.forEach(selection => {
const { start, end } = selection;
const range = new vscode.Range(start, end);
editBuilder.replace(range, String(rule));
});
});
// Display a message box to the user
vscode.window.showInformationMessage(`已插入正则: ${title}`);
} else {
vscode.window.showWarningMessage('any-rule: 只有在编辑文本的时候才可以使用!');
}
});
context.subscriptions.push(disposable);
workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
anyRule.reload();
});
commands.registerCommand('extension.update', () => {
anyRule.update();
});
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('更新关联语言失败');
}
}
});
}
// 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
View 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
View 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
View 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;
}