diff --git a/extensions/ql-vscode/src/view/compare-performance/RAPrettyPrinter.tsx b/extensions/ql-vscode/src/view/compare-performance/RAPrettyPrinter.tsx
index d7fae2a32..010523194 100644
--- a/extensions/ql-vscode/src/view/compare-performance/RAPrettyPrinter.tsx
+++ b/extensions/ql-vscode/src/view/compare-performance/RAPrettyPrinter.tsx
@@ -1,4 +1,5 @@
import { Fragment } from "react";
+import { styled } from "styled-components";
/**
* A set of names, for generating unambiguous abbreviations.
@@ -12,7 +13,7 @@ class NameSet {
qnames
.map((qname) => builder.visitQName(qname))
.forEach((r, index) => {
- this.abbreviations.set(names[index], r.abbreviate());
+ this.abbreviations.set(names[index], r.abbreviate(true));
});
}
@@ -86,7 +87,7 @@ class TrieNode {
interface VisitResult {
node: TrieNode;
- abbreviate: () => React.ReactNode;
+ abbreviate: (isRoot?: boolean) => React.ReactNode;
}
class TrieBuilder {
@@ -118,13 +119,21 @@ class TrieBuilder {
}
return {
node: trieNode,
- abbreviate: () => {
+ abbreviate: (isRoot = false) => {
const result: React.ReactNode[] = [];
if (prefix != null) {
result.push(prefix.abbreviate());
result.push("::");
}
- result.push(qname.name);
+ const { name } = qname;
+ const hash = name.indexOf("#");
+ if (hash !== -1 && isRoot) {
+ const shortName = name.substring(0, hash);
+ result.push({shortName});
+ result.push(name.substring(hash));
+ } else {
+ result.push(isRoot ? {name} : name);
+ }
if (args != null) {
result.push("<");
if (trieNodeBeforeArgs.children.size === 1) {
@@ -148,33 +157,73 @@ class TrieBuilder {
}
}
-const nameTokenRegex = /\b[^ ]+::[^ (]+\b/g;
+/**
+ * Span enclosing an entire qualified name.
+ *
+ * Can be used to gray out uninteresting parts of the name, though this looks worse than expected.
+ */
+const QNameSpan = styled.span`
+ /* color: var(--vscode-disabledForeground); */
+`;
+
+/** Span enclosing the innermost identifier, e.g. the `foo` in `A::B::foo#abc` */
+const IdentifierSpan = styled.span`
+ font-weight: 600;
+`;
+
+/** Span enclosing keywords such as `JOIN` and `WITH`. */
+const KeywordSpan = styled.span`
+ font-weight: 500;
+`;
+
+const nameTokenRegex = /\b[^ (]+\b/g;
+
+function traverseMatches(
+ text: string,
+ regex: RegExp,
+ callbacks: {
+ onMatch: (match: RegExpMatchArray) => void;
+ onText: (text: string) => void;
+ },
+) {
+ const matches = Array.from(text.matchAll(regex));
+ let lastIndex = 0;
+ for (const match of matches) {
+ const before = text.substring(lastIndex, match.index);
+ if (before !== "") {
+ callbacks.onText(before);
+ }
+ callbacks.onMatch(match);
+ lastIndex = match.index + match[0].length;
+ }
+ const after = text.substring(lastIndex);
+ if (after !== "") {
+ callbacks.onText(after);
+ }
+}
export function abbreviateRASteps(steps: string[]): React.ReactNode[] {
const nameTokens = steps.flatMap((step) =>
Array.from(step.matchAll(nameTokenRegex)).map((tok) => tok[0]),
);
- const nameSet = new NameSet(nameTokens);
+ const nameSet = new NameSet(nameTokens.filter((name) => name.includes("::")));
return steps.map((step, index) => {
- const matches = Array.from(step.matchAll(nameTokenRegex));
const result: React.ReactNode[] = [];
- for (let i = 0; i < matches.length; i++) {
- const match = matches[i];
- const before = step.slice(
- i === 0 ? 0 : matches[i - 1].index + matches[i - 1][0].length,
- match.index,
- );
- result.push(before);
- result.push(nameSet.getAbbreviation(match[0]));
- }
- result.push(
- matches.length === 0
- ? step
- : step.slice(
- matches[matches.length - 1].index +
- matches[matches.length - 1][0].length,
- ),
- );
+ traverseMatches(step, nameTokenRegex, {
+ onMatch(match) {
+ const text = match[0];
+ if (text.includes("::")) {
+ result.push({nameSet.getAbbreviation(text)});
+ } else if (/[A-Z]+/.test(text)) {
+ result.push({text});
+ } else {
+ result.push(match[0]);
+ }
+ },
+ onText(text) {
+ result.push(text);
+ },
+ });
return {result};
});
}