Skip to content

Commit

Permalink
fix: Improve regexp explorer (#559)
Browse files Browse the repository at this point in the history
* fix: remove unused images

* Improve Reg Exp Explorer
  • Loading branch information
Jason3S authored Sep 29, 2020
1 parent edb86ee commit 47f70c7
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 57 deletions.
3 changes: 2 additions & 1 deletion Spell Checker.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"${workspaceFolder:vscode-spell-checker}/cSpell.json"
],
"cSpell.customWorkspaceDictionaries": ["cspell-words"],
"typescript.tsdk": "client/node_modules/typescript/lib"
"typescript.tsdk": "client/node_modules/typescript/lib",
"svg.preview.background": "black"
}
}
1 change: 1 addition & 0 deletions cspell-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Lunr
Preformat
Staticman's
acanthopterygious
activitybar
algolia
anonymize
appid
Expand Down
28 changes: 24 additions & 4 deletions packages/_server/src/PatternMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ export class PatternMatcher {
async matchPatternsInText(patterns: Patterns, text: string, settings: PatternSettings): Promise<MatchResults> {
const resolvedPatterns = resolvePatterns(patterns, settings);

const uniquePatterns = [...new Map(resolvedPatterns
.map(p => [p.regexp.toString(), p])).values()];

// Optimistically expect them all to work.
try {
const result = await measurePromiseExecution(() => matchMatrix(this.worker, text, resolvedPatterns));
return result.r;
const result = await measurePromiseExecution(() => matchMatrix(this.worker, text, uniquePatterns));
return pairPatterns(result.r, resolvedPatterns);
} catch (e) {
if (!isTimeoutError(e)) {
return Promise.reject(e);
Expand All @@ -58,9 +61,26 @@ export class PatternMatcher {

// At least one of the expressions failed to complete in time.
// Process them one-by-one
const results = resolvedPatterns.map(pat => exec(this.worker, text, pat))
return Promise.all(results);
const results = uniquePatterns.map(pat => exec(this.worker, text, pat))
return Promise.all(results)
.then(r => pairPatterns(r, resolvedPatterns));
}
}

function pairPatterns(results: MatchResults, patterns: Pattern[]): MatchResults {
const defaultResult: PatternMatchTimeout = {
name: 'unknown',
regexp: /$^/,
elapsedTimeMs: 0,
message: 'Unmatched pattern',
}
const mapResults = new Map(results.map(r => [r.regexp.toString(), r]));
function matchPatternToResult(p: Pattern): MatchResult {
const { regexp, name } = p;
const r = mapResults.get(regexp.toString()) || defaultResult;
return {...r, name, regexp};
}
return patterns.map(matchPatternToResult);
}

export function isPatternMatch(m: MatchResult): m is PatternMatch {
Expand Down
2 changes: 2 additions & 0 deletions packages/client/.vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
**/*.test.*
**/*.ts
**/test.*
builds/**
docs/**
coverage/**
integrationTests/**
node_modules/**
Expand Down
Binary file removed packages/client/images/cSpell.png
Binary file not shown.
Binary file removed packages/client/images/cSpellIcon.png
Binary file not shown.
Binary file removed packages/client/images/icon.png
Binary file not shown.
Binary file removed packages/client/images/icon2.png
Binary file not shown.
35 changes: 32 additions & 3 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,37 @@
"commandPalette": [
{
"command": "cSpellRegExpTester.testRegExp",
"title": "Test a Regular Expression on the current document X.",
"when": "config.cSpell.experimental.enableRegexpView"
},
{
"command": "cSpellRegExpTester.editRegExp",
"when": "view == cSpellRegExpView"
}
],
"view/item/context": [
{
"command": "cSpellRegExpTester.editRegExp",
"when": "view == cSpellRegExpView && viewItem == regexp",
"group": "inline"
}
]

},
"viewsContainers": {
"activitybar": [
{
"id": "cspell-explorer",
"title": "Spell Checker Info",
"icon": "resources/dark/check_circle.svg",
"when": "config.cSpell.experimental.enableRegexpView"
}
]
},
"views": {
"explorer": [
"cspell-explorer": [
{
"id": "cSpellRegExpView",
"name": "cSpell RegExp",
"name": "Regular Expressions",
"when": "config.cSpell.experimental.enableRegexpView"
}
]
Expand Down Expand Up @@ -162,6 +183,14 @@
{
"command": "cSpellRegExpTester.testRegExp",
"title": "Test a Regular Expression on the current document."
},
{
"command": "cSpellRegExpTester.editRegExp",
"title": "Edit",
"icon": {
"dark": "resources/dark/edit.svg",
"light": "resources/light/edit.svg"
}
}
],
"languages": [
Expand Down
1 change: 1 addition & 0 deletions packages/client/resources/dark/check_circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/client/resources/dark/edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/client/resources/light/check_circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/client/resources/light/edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 38 additions & 18 deletions packages/client/src/extensionRegEx/RegexpOutlineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ export class RegexpOutlineProvider implements vscode.TreeDataProvider<OutlineIte
getTreeItem(offset: OutlineItem): vscode.TreeItem {
return offset.treeItem;
}

getParent(offset: OutlineItem): OutlineItem | undefined {
return offset.parent;
}
}

interface OutlineItem {
parent?: OutlineItem;
treeItem: vscode.TreeItem;
children?: OutlineItem[];
}
Expand All @@ -38,36 +43,22 @@ export type PatternMatchByCategory = Map<string, PatternMatch[]>;
function createOutlineItems(data: PatternMatchByCategory): OutlineItem {
const root: OutlineItem = {
treeItem: new vscode.TreeItem('root', vscode.TreeItemCollapsibleState.Expanded),
children: [...data.entries()].map(e => createCategoryItem(...e)),
};
root.children = [...data.entries()].map(e => createCategoryItem(root, ...e))
return root;
}

function createCategoryItem(category: string, matches: PatternMatch[]): OutlineItem {
function createCategoryItem(parent: OutlineItem, category: string, matches: PatternMatch[]): OutlineItem {
const item: OutlineItem = {
parent,
treeItem: new vscode.TreeItem(category, vscode.TreeItemCollapsibleState.Expanded),
children: matches.map(createLeaf),
};
return item;
}

function createLeaf(offset: PatternMatch): OutlineItem {
const treeItem = new vscode.TreeItem(trimName(offset.name));
const timeMs = offset.elapsedTime.toFixed(2);
const msg = offset.message ? ' ' + offset.message : ''
const parts = [
`${timeMs}ms`,
`(${offset.matches.length})`,
msg,
].filter(a => !!a);
treeItem.description = parts.join(' ');
treeItem.tooltip = offset.regexp
treeItem.command = {
command: 'cSpellRegExpTester.selectRegExp',
arguments: [offset.regexp],
title: 'Select RegExp'
}

const treeItem = new RegexpOutlineItem(offset);
const item: OutlineItem = {
treeItem,
};
Expand All @@ -82,3 +73,32 @@ function trimName(name: string) {
}
return name.substr(0, maxLen - 1) + '…';
}


export class RegexpOutlineItem extends vscode.TreeItem {

// public pattern: PatternMatch;

constructor(
public pattern: PatternMatch
) {
super(trimName(pattern.name));
this.pattern = pattern;

const timeMs = pattern.elapsedTime.toFixed(2);
const msg = pattern.message ? ' ' + pattern.message : ''
const parts = [
`${timeMs}ms`,
`(${pattern.matches.length})`,
msg,
].filter(a => !!a);
this.description = parts.join(' ');
this.tooltip = pattern.regexp.toString();
this.command = {
command: 'cSpellRegExpTester.selectRegExp',
arguments: [pattern.regexp],
title: 'Select RegExp'
}
this.contextValue = 'regexp';
}
}
60 changes: 29 additions & 31 deletions packages/client/src/extensionRegEx/extensionRegEx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import * as vscode from 'vscode';
import { CSpellClient } from '../client';
import { PatternMatch, CSpellUserSettings, NamedPattern } from '../server';
import { toRegExp } from './evaluateRegExp';
import { RegexpOutlineProvider } from './RegexpOutlineProvider';
import { RegexpOutlineItem, RegexpOutlineProvider } from './RegexpOutlineProvider';

interface DisposableLike {
dispose(): any;
}

const MAX_HISTORY_LENGTH = 5;

// this method is called when vs code is activated
export function activate(context: vscode.ExtensionContext, client: CSpellClient): void {

const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);

const disposables = new Set<DisposableLike>();
const outline = new RegexpOutlineProvider();
vscode.window.registerTreeDataProvider('cSpellRegExpView', outline);
Expand Down Expand Up @@ -41,6 +41,7 @@ export function activate(context: vscode.ExtensionContext, client: CSpellClient)

let activeEditor = isActive ? vscode.window.activeTextEditor : undefined;
let pattern: string | undefined = undefined;
let history: string[] = [];

async function updateDecorations() {
disposeCurrent();
Expand All @@ -49,13 +50,14 @@ export function activate(context: vscode.ExtensionContext, client: CSpellClient)
return;
}

const userPatterns = pattern ? [pattern] : [];
const document = activeEditor.document;
const version = document.version;
const config = await client.getConfigurationForDocument(document);
const extractedPatterns = extractPatternsFromConfig(config.docSettings, userPatterns);
const extractedPatterns = extractPatternsFromConfig(config.docSettings, history);
const patterns = extractedPatterns.map(p => p.pattern);

const highlightIndex = pattern ? 0 : -1;

client.matchPatternsInDocument(document, patterns).then(result => {
if (!vscode.window.activeTextEditor
|| document.version !== version
Expand All @@ -77,11 +79,9 @@ export function activate(context: vscode.ExtensionContext, client: CSpellClient)

outline.refresh(byCategory);
const activeEditor = vscode.window.activeTextEditor;
const processingTimeMs = result.patternMatches.map(m => m.elapsedTime).reduce((a, b) => a + b, 0);
const patternCount = result.patternMatches.map(m => m.matches.length > 0 ? 1 : 0).reduce((a, b) => a + b, 0);
const failedCount = result.patternMatches.map(m => m.message ? 1 : 0).reduce((a, b) => a + b, 0);
const flattenResults = result.patternMatches
.filter(patternMatch => patternMatch.regexp === pattern)
.filter((_, i) => i === highlightIndex)
.filter(patternMatch => patternMatch.regexp === pattern || patternMatch.name === pattern)
.map(patternMatch => patternMatch.matches.map(range => ({ range, message: createHoverMessage(patternMatch) })))
.reduce((a, v) => a.concat(v), []);
const decorations: vscode.DecorationOptions[] = flattenResults.map(match => {
Expand All @@ -92,13 +92,11 @@ export function activate(context: vscode.ExtensionContext, client: CSpellClient)
return decoration;
});
activeEditor.setDecorations(decorationTypeExclude, decorations);
updateStatusBar(`Patterns: ${patternCount} | ${failedCount}`, result ? { elapsedTime: processingTimeMs, count: flattenResults.length } : undefined);
});
}

function clearDecorations() {
activeEditor?.setDecorations(decorationTypeExclude, []);
statusBar.hide();
}

function createHoverMessage(match: PatternMatch) {
Expand All @@ -112,28 +110,9 @@ export function activate(context: vscode.ExtensionContext, client: CSpellClient)
clearTimeout(timeout);
timeout = undefined;
}
updateStatusBar(pattern);
timeout = setTimeout(updateDecorations, 100);
}

interface StatusBarInfo {
elapsedTime: number;
count: number;
}

function updateStatusBar(pattern: string | undefined, info?: StatusBarInfo) {
if (isActive && pattern) {
const { elapsedTime, count = 0 } = info || {};
const time = elapsedTime ? `${elapsedTime.toFixed(2)}ms` : '$(clock)';
statusBar.text = `${time} | ${pattern}`;
statusBar.tooltip = elapsedTime ? 'Regular Expression Test Results, found ' + count : 'Running Regular Expression Test';
statusBar.command = 'cSpellRegExpTester.testRegExp';
statusBar.show();
} else {
statusBar.hide();
}
}

if (activeEditor) {
triggerUpdateDecorations();
}
Expand Down Expand Up @@ -173,19 +152,38 @@ export function activate(context: vscode.ExtensionContext, client: CSpellClient)
validateInput
}).then(value => {
pattern = value ? value : undefined;
updateHistory(pattern);
triggerUpdateDecorations();
});
}

function isNonEmptyString(s: string | undefined): s is string {
return typeof s === 'string' && !!s;
}

function updateHistory(pattern?: string) {
const unique = new Set([pattern].concat(history));
history = [...unique].filter(isNonEmptyString);
history.length = Math.min(history.length, MAX_HISTORY_LENGTH);
}

function userSelectRegExp(selectedRegExp?: string) {
if (pattern === selectedRegExp) {
pattern = undefined;
} else {
pattern = selectedRegExp;
}
updateHistory(pattern);
triggerUpdateDecorations();
}

function editRegExp(item: { treeItem: RegexpOutlineItem } | undefined) {
if (item?.treeItem?.pattern) {
triggerUpdateDecorations();
userTestRegExp(item.treeItem.pattern.regexp);
}
}

function updateIsActive() {
const currentIsActive = isActive;
isActive = fetchIsEnabledFromConfig();
Expand All @@ -210,9 +208,9 @@ export function activate(context: vscode.ExtensionContext, client: CSpellClient)

context.subscriptions.push(
{dispose},
statusBar,
vscode.commands.registerCommand('cSpellRegExpTester.testRegExp', userTestRegExp),
vscode.commands.registerCommand('cSpellRegExpTester.selectRegExp', userSelectRegExp),
vscode.commands.registerCommand('cSpellRegExpTester.editRegExp', editRegExp),
vscode.workspace.onDidChangeConfiguration(updateIsActive),
);
}
Expand Down

0 comments on commit 47f70c7

Please sign in to comment.