mirror of
https://github.com/any86/any-rule.git
synced 2025-07-14 15:38:58 +08:00
增加正则调试的功能
This commit is contained in:
parent
f1a23bffed
commit
057a8c62e2
12
package.json
12
package.json
@ -8,7 +8,7 @@
|
||||
"build:md": "node ./scripts/md.js",
|
||||
"build": "npm run test:rules && npm version patch && node ./scripts/genCommond.js && vsce package && npm run build:md",
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "webpack --mode production",
|
||||
"compile": "webpack --mode development",
|
||||
"watch": "webpack --mode development",
|
||||
"pretest": "npm run compile",
|
||||
"test": "node ./out/test/runTest.js",
|
||||
@ -335,10 +335,20 @@
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/codemirror": "^0.0.88",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"antd": "^4.0.4",
|
||||
"axios": "^0.19.2",
|
||||
"codemirror": "^5.52.2",
|
||||
"css-loader": "^3.4.2",
|
||||
"less": "^3.11.1",
|
||||
"less-loader": "^5.0.0",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"regulex-cjs": "^0.0.7",
|
||||
"shortid": "^2.2.15",
|
||||
"style-loader": "^1.1.3",
|
||||
"transliteration": "^2.1.8"
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,23 @@ function getWebViewContent(context: ExtensionContext, templatePath: string) {
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从一段文本中提取出所有的正则表达式字符串
|
||||
* 1. 通过栈 来匹配正则的配对规则来匹配,区域可重叠(确保是正确的格式)
|
||||
* - 每一个 '/' 都作为起始字符来处理来进行扫描
|
||||
* - [] 中可以有不配对的 '[', ']', '(', ')' 字符
|
||||
* - 对正则进行语法检测,可以使用 Regulex 的相关方法
|
||||
* 2. 对正则列表根据上下文进行可靠性判断
|
||||
* - 如果正则开始符号或终止符号,在上下文的字符串中,在上下文的计算式中(作为除号),则排除这个式子
|
||||
* - 对于赋值语句的判断,比如将正则赋值给一个变量的这种情况判定为可靠
|
||||
* - 排除占用了已经确定的正则的开始或终止标识的正则
|
||||
* - 实在没办法的就通过,一起列出来。存在待处理文本是经过不可靠的截断导致上下文失效的情况
|
||||
* @param content 待处理文本
|
||||
*/
|
||||
function pickRegularExpressions(content: string) {
|
||||
// TODO 实现一个可靠的正则表达式提取算法
|
||||
}
|
||||
|
||||
export default function useDiagram(context: ExtensionContext) {
|
||||
|
||||
commands.registerTextEditorCommand('extension.showDiagram', (editor, edit) => {
|
||||
@ -50,6 +67,7 @@ export default function useDiagram(context: ExtensionContext) {
|
||||
regexpList.push(matches[1]);
|
||||
}
|
||||
if (regexpList.length) {
|
||||
console.log(regexpList);
|
||||
const panel = window.createWebviewPanel(
|
||||
'Diagram',
|
||||
'Diagram',
|
||||
@ -60,8 +78,20 @@ export default function useDiagram(context: ExtensionContext) {
|
||||
);
|
||||
panel.webview.html = getWebViewContent(context, 'out/diagram/index.html')
|
||||
.replace('{{ inject-script }}', `<script src="${getExtensionFileVscodeResource(context, 'out/diagram/diagram.js')}"></script>`);
|
||||
panel.webview.onDidReceiveMessage(message => {
|
||||
switch (message.command) {
|
||||
case 'getRegexpList':
|
||||
panel.webview.postMessage({
|
||||
regexpGroups: regexpList,
|
||||
regexpList
|
||||
});
|
||||
break;
|
||||
case 'replaceRegexp':
|
||||
console.log(message.regexp);
|
||||
break;
|
||||
case 'insertRegexp':
|
||||
console.log(message.regexp);
|
||||
break;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.showWarningMessage('未找到正则表达式');
|
||||
|
49
src/diagram/webview/App.less
Normal file
49
src/diagram/webview/App.less
Normal file
@ -0,0 +1,49 @@
|
||||
@primary-color: var(--vscode-button-background);
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
font-size: var(--vscode-font-size);
|
||||
color: var(--vscode-foreground);
|
||||
background: var(--vscode-editor-background);
|
||||
}
|
||||
|
||||
body.vscode-light {
|
||||
color: black;
|
||||
}
|
||||
|
||||
body.vscode-dark {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.app {
|
||||
background: transparent;
|
||||
|
||||
.ant-collapse-borderless {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ant-collapse-borderless {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ant-collapse-header {
|
||||
background: var(--vscode-sideBarSectionHeader-background);
|
||||
color: var(--vscode-editor-foreground) !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ant-btn-primary {
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
&:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
}
|
||||
|
||||
.regex-collapse-panel {
|
||||
border: 0;
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { RegexpDiagramView } from './views/RegexpDiagram';
|
||||
import { RegexToolView } from './views/RegexTools';
|
||||
import 'antd/dist/antd.less';
|
||||
import './App.less';
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="app">
|
||||
<RegexpDiagramView />
|
||||
<RegexToolView />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,20 +1,30 @@
|
||||
import * as React from 'react';
|
||||
import { parse, visualize, Raphael } from 'regulex-cjs';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useContext, HTMLAttributes } from 'react';
|
||||
import * as shortid from 'shortid';
|
||||
import { RegexContext } from './Workbench/Workbench';
|
||||
|
||||
interface IDiagramProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
regexp: string | RegExp;
|
||||
}
|
||||
|
||||
export const RegExpDiagram: React.FC<IDiagramProps> = (props) => {
|
||||
useEffect(() => {
|
||||
const regexpString = typeof props.regexp === 'string' ? props.regexp : props.regexp.source;
|
||||
const ast = parse(regexpString);
|
||||
const paper = Raphael('regexp-diagram');
|
||||
visualize(ast, 'g', paper, { color: { background: 'transparent' } });
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="regexp-diagram" />
|
||||
);
|
||||
const mode = {
|
||||
light: {},
|
||||
dark: {},
|
||||
};
|
||||
|
||||
export const RegExpDiagram: React.FC<HTMLAttributes<HTMLDivElement>> = () => {
|
||||
const id = `regexp-diagram-${shortid.generate()}`;
|
||||
const { regex } = useContext(RegexContext);
|
||||
useEffect(() => {
|
||||
// const regexpString = typeof props.regexp === 'string' ? props.regexp : props.regexp.source;
|
||||
document.getElementById(id)!.innerHTML = '';
|
||||
try {
|
||||
const ast = parse(regex);
|
||||
const paper = Raphael(id);
|
||||
visualize(ast, 'g', paper, { color: { background: 'transparent' } });
|
||||
} catch (e) {
|
||||
console.log('error occured', e);
|
||||
document.getElementById(id)!.innerHTML =
|
||||
'<div stype="padding: 30px; text-align: center;">无法解析正则表达式</div>';
|
||||
}
|
||||
}, [regex]);
|
||||
|
||||
return <div id={id} />;
|
||||
};
|
||||
|
47
src/diagram/webview/components/RegexEditor/RegexEditor.less
Normal file
47
src/diagram/webview/components/RegexEditor/RegexEditor.less
Normal file
@ -0,0 +1,47 @@
|
||||
.regex-editor {
|
||||
display: flex;
|
||||
|
||||
input.ant-input {
|
||||
border: none;
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
outline: none;
|
||||
border-radius: 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.codemirror-wrapper {
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
background: var(--vscode-input-background) !important;
|
||||
border: none !important;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.CodeMirror-hscrollbar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.separator {
|
||||
background: var(--vscode-input-background);
|
||||
color: #bbb;
|
||||
width: 16px;
|
||||
max-width: 16px;
|
||||
min-width: 16px;
|
||||
line-height: 32px;
|
||||
height: 32px;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
64
src/diagram/webview/components/RegexEditor/RegexEditor.tsx
Normal file
64
src/diagram/webview/components/RegexEditor/RegexEditor.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import * as React from 'react';
|
||||
import { useRef, useEffect, MutableRefObject, useContext } from 'react';
|
||||
|
||||
import * as CodeMirror from 'codemirror';
|
||||
import 'codemirror/addon/display/placeholder';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import { RegexContext } from '../Workbench/Workbench';
|
||||
import * as shortid from 'shortid';
|
||||
import './RegexEditor.less';
|
||||
|
||||
export const RegexEditor: React.FC<{}> = () => {
|
||||
const id = `regex-editor${shortid.generate()}`;
|
||||
|
||||
const { regex, flag, dispatch } = useContext(RegexContext);
|
||||
const codeMirrorElement: MutableRefObject<HTMLDivElement | null> = useRef(
|
||||
null,
|
||||
);
|
||||
const regexEditorElement: MutableRefObject<HTMLDivElement | null> = useRef(
|
||||
null,
|
||||
);
|
||||
|
||||
let codeMirror: CodeMirror.Editor;
|
||||
|
||||
useEffect(() => {
|
||||
codeMirror = CodeMirror(codeMirrorElement.current!, {
|
||||
value: regex,
|
||||
lineWrapping: false,
|
||||
placeholder: '请输入测试文本',
|
||||
});
|
||||
|
||||
codeMirror.on('focus', () => {
|
||||
regexEditorElement.current?.classList.add('active');
|
||||
});
|
||||
|
||||
codeMirror.on('blur', () => {
|
||||
regexEditorElement.current?.classList.remove('active');
|
||||
});
|
||||
|
||||
codeMirror.on('change', editor => {
|
||||
// setRegexp(codeMirror.getValue());
|
||||
dispatch({
|
||||
type: 'regex',
|
||||
value: editor.getValue(),
|
||||
});
|
||||
});
|
||||
|
||||
codeMirror.on('beforeChange', (instance, change) => {
|
||||
var newtext = change.text.join('').replace(/\n/g, ''); // remove ALL \n !
|
||||
change.update!(change.from, change.to, [newtext]);
|
||||
return true;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div id={id} ref={regexEditorElement} className="regex-editor input-area">
|
||||
<div className="separator">/</div>
|
||||
<div className="codemirror-wrapper" ref={codeMirrorElement}></div>
|
||||
{/* <Input onFocus={onFocus} onBlur={onBlur} value={regexp} onChange={onChange} onKeyPress={onKeyPress} placeholder="请输入正则表达式" /> */}
|
||||
<div className="separator">/</div>
|
||||
{/* <Select></Select> */}
|
||||
<div className="flag-selector">{flag}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
.CodeMirror {
|
||||
background: var(--vscode-input-background);
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
pre.CodeMirror-placeholder {
|
||||
color: var(--vscode-editorGutter-commentRangeForeground);
|
||||
}
|
||||
.cm-string {
|
||||
background-color: var(--vscode-button-background);
|
||||
color: var(--vscode-input-background);
|
||||
}
|
||||
}
|
||||
|
||||
.test-case-editor {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-editor,
|
||||
.monaco-editor-background,
|
||||
.monaco-editor .inputarea.ime-input {
|
||||
background: var(--vscode-input-background) !important;
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import * as React from 'react';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { RegexContext } from '../Workbench/Workbench';
|
||||
|
||||
import * as CodeMirror from 'codemirror';
|
||||
import 'codemirror/addon/display/placeholder';
|
||||
import 'codemirror/addon/mode/simple';
|
||||
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import './TestCaseEditor.less';
|
||||
import shortid = require('shortid');
|
||||
|
||||
interface ICodeMirrorEditorProps {
|
||||
mode: string;
|
||||
}
|
||||
|
||||
const CodeMirrorEditor: React.FC<ICodeMirrorEditorProps> = props => {
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
const [value, setValue] = useState('');
|
||||
useEffect(() => {
|
||||
if (container.current) {
|
||||
container.current.innerHTML = '';
|
||||
const codeMirror = CodeMirror(container.current!, {
|
||||
value,
|
||||
lineWrapping: true,
|
||||
placeholder: '请输入测试文本',
|
||||
mode: props.mode,
|
||||
});
|
||||
|
||||
codeMirror.on('focus', () => {
|
||||
container.current?.classList.add('active');
|
||||
});
|
||||
|
||||
codeMirror.on('blur', () => {
|
||||
container.current?.classList.remove('active');
|
||||
});
|
||||
|
||||
codeMirror.on('change', cm => {
|
||||
setValue(cm.getValue());
|
||||
});
|
||||
}
|
||||
}, [props.mode]);
|
||||
|
||||
return (
|
||||
<div className="test-case-editor input-area" ref={container}></div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestCaseEditor = () => {
|
||||
const { regex } = useContext(RegexContext);
|
||||
|
||||
const [mode, setMode] = useState('simplemode');
|
||||
|
||||
useEffect(() => {
|
||||
const mode = `mode-${shortid.generate()}`;
|
||||
// @ts-ignore
|
||||
CodeMirror.defineSimpleMode(mode, {
|
||||
start: [
|
||||
{ regex: new RegExp(regex), token: 'string' },
|
||||
],
|
||||
});
|
||||
setMode(mode);
|
||||
}, [regex]);
|
||||
|
||||
return <CodeMirrorEditor mode={mode}></CodeMirrorEditor>;
|
||||
};
|
10
src/diagram/webview/components/Workbench/Workbench.less
Normal file
10
src/diagram/webview/components/Workbench/Workbench.less
Normal file
@ -0,0 +1,10 @@
|
||||
.anyrule-workbench {
|
||||
|
||||
.input-area {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.input-area.active {
|
||||
box-shadow: 0 0 0 1px var(--vscode-focusBorder);
|
||||
}
|
||||
}
|
73
src/diagram/webview/components/Workbench/Workbench.tsx
Normal file
73
src/diagram/webview/components/Workbench/Workbench.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import * as React from 'react';
|
||||
import { useReducer } from 'react';
|
||||
import { Row, Col, Button, Input } from 'antd';
|
||||
import { RegExpDiagram } from '../Diagram';
|
||||
import { Editor } from '../Editor/Editor';
|
||||
import './Workbench.less';
|
||||
import { RegexEditor } from '../RegexEditor/RegexEditor';
|
||||
import { TestCaseEditor } from '../TestCaseEditor/TestCaseEditor';
|
||||
|
||||
interface IWorkspaceProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
regexp: string;
|
||||
}
|
||||
|
||||
interface IWorkbenchState {
|
||||
regex: string;
|
||||
flag: string;
|
||||
}
|
||||
|
||||
interface IWorkbenchAction {
|
||||
type: 'regex' | 'flag';
|
||||
value: string;
|
||||
}
|
||||
|
||||
const reducer: React.Reducer<IWorkbenchState, IWorkbenchAction> = (
|
||||
state,
|
||||
action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case 'regex':
|
||||
return { regex: action.value, flag: state.flag };
|
||||
case 'flag':
|
||||
return { regex: action.value, flag: state.flag };
|
||||
}
|
||||
};
|
||||
|
||||
export const RegexContext = React.createContext({
|
||||
regex: '',
|
||||
flag: '',
|
||||
dispatch: (action: IWorkbenchAction) => {},
|
||||
});
|
||||
export const RegexWorkbench: React.FC<IWorkspaceProps> = props => {
|
||||
const rawRegexp = props.regexp;
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
regex: props.regexp,
|
||||
flag: '',
|
||||
});
|
||||
return (
|
||||
<div className="anyrule-workbench">
|
||||
<RegexContext.Provider value={{ regex: state.regex, flag: '', dispatch }}>
|
||||
<Row gutter={16} style={{ paddingTop: '16px' }}>
|
||||
<Col span={21}>
|
||||
<RegexEditor></RegexEditor>
|
||||
{/* <Input placeholder="请输入正则表达式" value={regexp} onChange={event => setRegexp(event.currentTarget.value)} /> */}
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Button type="primary" style={{ width: '100%' }}>
|
||||
替换
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row
|
||||
style={{ maxWidth: '100%', overflowX: 'scroll', margin: '20px 0' }}
|
||||
>
|
||||
<RegExpDiagram style={{ marginLeft: 'auto', marginRight: 'auto' }} />
|
||||
</Row>
|
||||
<Row>
|
||||
<TestCaseEditor></TestCaseEditor>
|
||||
{/* <Editor regexp={props.regexp} /> */}
|
||||
</Row>
|
||||
</RegexContext.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,11 +1,17 @@
|
||||
import { Render } from "./App";
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
const regexpGroups = event.data.regexpGroups;
|
||||
console.log(event.data);
|
||||
// @ts-ignore
|
||||
window.regexpGroups = regexpGroups;
|
||||
// @ts-ignore
|
||||
console.log(window.regexpGroups);
|
||||
Render();
|
||||
});
|
||||
// window.addEventListener('message', event => {
|
||||
// const regexpGroups = event.data.regexpGroups;
|
||||
// console.log('event', event);
|
||||
// // @ts-ignore
|
||||
// window.regexpGroups = regexpGroups;
|
||||
// // localStorage.setItem('regexp-list', JSON.stringify(regexpGroups));
|
||||
// // @ts-ignore
|
||||
// console.log(window.regexpGroups);
|
||||
// Render();
|
||||
// });
|
||||
|
||||
// @ts-ignore
|
||||
window.vscode = acquireVsCodeApi();
|
||||
|
||||
Render();
|
42
src/diagram/webview/views/RegexTools.tsx
Normal file
42
src/diagram/webview/views/RegexTools.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Collapse, Input, Row, Col, Button } from 'antd';
|
||||
import { RegexWorkbench } from '../components/Workbench/Workbench';
|
||||
|
||||
export const RegexToolView: React.FC = () => {
|
||||
// @ts-ignore
|
||||
// const regexpGroups = window.regexpGroups;
|
||||
const [ regexpList, setRegexpList ] = useState([] as string[]);
|
||||
window.addEventListener('message', event => {
|
||||
console.log(event);
|
||||
// return;
|
||||
if (event.data.regexpList) {
|
||||
setRegexpList(event.data.regexpList);
|
||||
}
|
||||
});
|
||||
// @ts-ignore
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
vscode.postMessage({
|
||||
command: 'getRegexpList',
|
||||
});
|
||||
}, []);
|
||||
// const regexpList = JSON.parse(localStorage.getItem('regexp-list') || '[]');
|
||||
console.log(regexpList);
|
||||
const { Panel } = Collapse;
|
||||
return (
|
||||
<>
|
||||
<Collapse accordion bordered={false} defaultActiveKey={['panel-0']}>
|
||||
{regexpList?.map((regexp: string, index: number) => (
|
||||
<Panel
|
||||
header={regexp}
|
||||
key={'panel-' + String(index)}
|
||||
className="regex-collapse-panel"
|
||||
>
|
||||
<RegexWorkbench regexp={regexp}></RegexWorkbench>
|
||||
</Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { RegExpDiagram } from '../components/Diagram';
|
||||
|
||||
export const RegexpDiagramView: React.FC = () => {
|
||||
// @ts-ignore
|
||||
const regexpGroups = window.regexpGroups;
|
||||
console.log(regexpGroups);
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{regexpGroups.map((regexp: string) => <RegExpDiagram className="regexp-diagram" regexp={new RegExp(regexp)} />)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -65,11 +65,33 @@ const webviewConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(css|less)$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'style-loader',
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
},
|
||||
{
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
modifyVars: {
|
||||
// 'primary-color': 'var(--vscode-button-background)',
|
||||
// '@link-color': '#1DA57A',
|
||||
// '@border-radius-base': '0px',
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin([
|
||||
{ from: 'src/diagram/webview/index.html', to: 'diagram/index.html'},
|
||||
{ from: 'src/diagram/webview/index.html', to: 'diagram/index.html' },
|
||||
]),
|
||||
],
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user