增加正则调试的功能

This commit is contained in:
microud 2020-04-02 22:16:02 +08:00
parent f1a23bffed
commit 057a8c62e2
16 changed files with 1613 additions and 60 deletions

View File

@ -8,7 +8,7 @@
"build:md": "node ./scripts/md.js", "build:md": "node ./scripts/md.js",
"build": "npm run test:rules && npm version patch && node ./scripts/genCommond.js && vsce package && npm run build:md", "build": "npm run test:rules && npm version patch && node ./scripts/genCommond.js && vsce package && npm run build:md",
"vscode:prepublish": "npm run compile", "vscode:prepublish": "npm run compile",
"compile": "webpack --mode production", "compile": "webpack --mode development",
"watch": "webpack --mode development", "watch": "webpack --mode development",
"pretest": "npm run compile", "pretest": "npm run compile",
"test": "node ./out/test/runTest.js", "test": "node ./out/test/runTest.js",
@ -335,10 +335,20 @@
"webpack-cli": "^3.3.11" "webpack-cli": "^3.3.11"
}, },
"dependencies": { "dependencies": {
"@types/codemirror": "^0.0.88",
"@types/shortid": "^0.0.29",
"antd": "^4.0.4",
"axios": "^0.19.2", "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": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"regulex-cjs": "^0.0.7", "regulex-cjs": "^0.0.7",
"shortid": "^2.2.15",
"style-loader": "^1.1.3",
"transliteration": "^2.1.8" "transliteration": "^2.1.8"
} }
} }

View File

@ -31,6 +31,23 @@ function getWebViewContent(context: ExtensionContext, templatePath: string) {
return html; return html;
} }
/**
*
* 1.
* - '/'
* - [] '[', ']', '(', ')'
* - 使 Regulex
* 2.
* -
* -
* -
* -
* @param content
*/
function pickRegularExpressions(content: string) {
// TODO 实现一个可靠的正则表达式提取算法
}
export default function useDiagram(context: ExtensionContext) { export default function useDiagram(context: ExtensionContext) {
commands.registerTextEditorCommand('extension.showDiagram', (editor, edit) => { commands.registerTextEditorCommand('extension.showDiagram', (editor, edit) => {
@ -50,6 +67,7 @@ export default function useDiagram(context: ExtensionContext) {
regexpList.push(matches[1]); regexpList.push(matches[1]);
} }
if (regexpList.length) { if (regexpList.length) {
console.log(regexpList);
const panel = window.createWebviewPanel( const panel = window.createWebviewPanel(
'Diagram', 'Diagram',
'Diagram', 'Diagram',
@ -60,8 +78,20 @@ export default function useDiagram(context: ExtensionContext) {
); );
panel.webview.html = getWebViewContent(context, 'out/diagram/index.html') panel.webview.html = getWebViewContent(context, 'out/diagram/index.html')
.replace('{{ inject-script }}', `<script src="${getExtensionFileVscodeResource(context, 'out/diagram/diagram.js')}"></script>`); .replace('{{ inject-script }}', `<script src="${getExtensionFileVscodeResource(context, 'out/diagram/diagram.js')}"></script>`);
panel.webview.onDidReceiveMessage(message => {
switch (message.command) {
case 'getRegexpList':
panel.webview.postMessage({ panel.webview.postMessage({
regexpGroups: regexpList, regexpList
});
break;
case 'replaceRegexp':
console.log(message.regexp);
break;
case 'insertRegexp':
console.log(message.regexp);
break;
}
}); });
} else { } else {
window.showWarningMessage('未找到正则表达式'); window.showWarningMessage('未找到正则表达式');

View 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;
}
}

View File

@ -1,12 +1,14 @@
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom'; 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 { class App extends React.Component {
render() { render() {
return ( return (
<div className="app"> <div className="app">
<RegexpDiagramView /> <RegexToolView />
</div> </div>
); );
} }

View File

@ -1,20 +1,30 @@
import * as React from 'react'; import * as React from 'react';
import { parse, visualize, Raphael } from 'regulex-cjs'; 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> { const mode = {
regexp: string | RegExp; light: {},
} dark: {},
};
export const RegExpDiagram: React.FC<IDiagramProps> = (props) => {
useEffect(() => { export const RegExpDiagram: React.FC<HTMLAttributes<HTMLDivElement>> = () => {
const regexpString = typeof props.regexp === 'string' ? props.regexp : props.regexp.source; const id = `regexp-diagram-${shortid.generate()}`;
const ast = parse(regexpString); const { regex } = useContext(RegexContext);
const paper = Raphael('regexp-diagram'); useEffect(() => {
visualize(ast, 'g', paper, { color: { background: 'transparent' } }); // const regexpString = typeof props.regexp === 'string' ? props.regexp : props.regexp.source;
}); document.getElementById(id)!.innerHTML = '';
try {
return ( const ast = parse(regex);
<div id="regexp-diagram" /> 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} />;
}; };

View 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;
}
}

View 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>
);
};

View File

@ -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;
}

View File

@ -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>;
};

View File

@ -0,0 +1,10 @@
.anyrule-workbench {
.input-area {
border: none;
}
.input-area.active {
box-shadow: 0 0 0 1px var(--vscode-focusBorder);
}
}

View 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>
);
};

View File

@ -1,11 +1,17 @@
import { Render } from "./App"; import { Render } from "./App";
window.addEventListener('message', event => { // window.addEventListener('message', event => {
const regexpGroups = event.data.regexpGroups; // const regexpGroups = event.data.regexpGroups;
console.log(event.data); // console.log('event', event);
// @ts-ignore // // @ts-ignore
window.regexpGroups = regexpGroups; // window.regexpGroups = regexpGroups;
// @ts-ignore // // localStorage.setItem('regexp-list', JSON.stringify(regexpGroups));
console.log(window.regexpGroups); // // @ts-ignore
Render(); // console.log(window.regexpGroups);
}); // Render();
// });
// @ts-ignore
window.vscode = acquireVsCodeApi();
Render();

View 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>
</>
);
};

View File

@ -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>
</>
);
};

View File

@ -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: [ plugins: [
new CopyPlugin([ new CopyPlugin([
{ from: 'src/diagram/webview/index.html', to: 'diagram/index.html'}, { from: 'src/diagram/webview/index.html', to: 'diagram/index.html' },
]), ]),
], ],
}; };

1143
yarn.lock

File diff suppressed because it is too large Load Diff