Apply styling to RA predicate names

This commit is contained in:
Asger F
2024-11-13 14:45:43 +01:00
parent 70ec5704c8
commit 8268d6812f

View File

@@ -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(<IdentifierSpan>{shortName}</IdentifierSpan>);
result.push(name.substring(hash));
} else {
result.push(isRoot ? <IdentifierSpan>{name}</IdentifierSpan> : 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<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[] {
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]));
traverseMatches(step, nameTokenRegex, {
onMatch(match) {
const text = match[0];
if (text.includes("::")) {
result.push(<QNameSpan>{nameSet.getAbbreviation(text)}</QNameSpan>);
} else if (/[A-Z]+/.test(text)) {
result.push(<KeywordSpan>{text}</KeywordSpan>);
} else {
result.push(match[0]);
}
result.push(
matches.length === 0
? step
: step.slice(
matches[matches.length - 1].index +
matches[matches.length - 1][0].length,
),
);
},
onText(text) {
result.push(text);
},
});
return <Fragment key={index}>{result}</Fragment>;
});
}