Apply styling to RA predicate names
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
import { styled } from "styled-components";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of names, for generating unambiguous abbreviations.
|
* A set of names, for generating unambiguous abbreviations.
|
||||||
@@ -12,7 +13,7 @@ class NameSet {
|
|||||||
qnames
|
qnames
|
||||||
.map((qname) => builder.visitQName(qname))
|
.map((qname) => builder.visitQName(qname))
|
||||||
.forEach((r, index) => {
|
.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 {
|
interface VisitResult {
|
||||||
node: TrieNode;
|
node: TrieNode;
|
||||||
abbreviate: () => React.ReactNode;
|
abbreviate: (isRoot?: boolean) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrieBuilder {
|
class TrieBuilder {
|
||||||
@@ -118,13 +119,21 @@ class TrieBuilder {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
node: trieNode,
|
node: trieNode,
|
||||||
abbreviate: () => {
|
abbreviate: (isRoot = false) => {
|
||||||
const result: React.ReactNode[] = [];
|
const result: React.ReactNode[] = [];
|
||||||
if (prefix != null) {
|
if (prefix != null) {
|
||||||
result.push(prefix.abbreviate());
|
result.push(prefix.abbreviate());
|
||||||
result.push("::");
|
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(<IdentifierSpan>{shortName}</IdentifierSpan>);
|
||||||
|
result.push(name.substring(hash));
|
||||||
|
} else {
|
||||||
|
result.push(isRoot ? <IdentifierSpan>{name}</IdentifierSpan> : name);
|
||||||
|
}
|
||||||
if (args != null) {
|
if (args != null) {
|
||||||
result.push("<");
|
result.push("<");
|
||||||
if (trieNodeBeforeArgs.children.size === 1) {
|
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<X>::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[] {
|
export function abbreviateRASteps(steps: string[]): React.ReactNode[] {
|
||||||
const nameTokens = steps.flatMap((step) =>
|
const nameTokens = steps.flatMap((step) =>
|
||||||
Array.from(step.matchAll(nameTokenRegex)).map((tok) => tok[0]),
|
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) => {
|
return steps.map((step, index) => {
|
||||||
const matches = Array.from(step.matchAll(nameTokenRegex));
|
|
||||||
const result: React.ReactNode[] = [];
|
const result: React.ReactNode[] = [];
|
||||||
for (let i = 0; i < matches.length; i++) {
|
traverseMatches(step, nameTokenRegex, {
|
||||||
const match = matches[i];
|
onMatch(match) {
|
||||||
const before = step.slice(
|
const text = match[0];
|
||||||
i === 0 ? 0 : matches[i - 1].index + matches[i - 1][0].length,
|
if (text.includes("::")) {
|
||||||
match.index,
|
result.push(<QNameSpan>{nameSet.getAbbreviation(text)}</QNameSpan>);
|
||||||
);
|
} else if (/[A-Z]+/.test(text)) {
|
||||||
result.push(before);
|
result.push(<KeywordSpan>{text}</KeywordSpan>);
|
||||||
result.push(nameSet.getAbbreviation(match[0]));
|
} else {
|
||||||
}
|
result.push(match[0]);
|
||||||
result.push(
|
}
|
||||||
matches.length === 0
|
},
|
||||||
? step
|
onText(text) {
|
||||||
: step.slice(
|
result.push(text);
|
||||||
matches[matches.length - 1].index +
|
},
|
||||||
matches[matches.length - 1][0].length,
|
});
|
||||||
),
|
|
||||||
);
|
|
||||||
return <Fragment key={index}>{result}</Fragment>;
|
return <Fragment key={index}>{result}</Fragment>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user