11import { Fragment } from "react" ;
2+ import { styled } from "styled-components" ;
23
34/**
45 * A set of names, for generating unambiguous abbreviations.
@@ -12,7 +13,7 @@ class NameSet {
1213 qnames
1314 . map ( ( qname ) => builder . visitQName ( qname ) )
1415 . forEach ( ( r , index ) => {
15- this . abbreviations . set ( names [ index ] , r . abbreviate ( ) ) ;
16+ this . abbreviations . set ( names [ index ] , r . abbreviate ( true ) ) ;
1617 } ) ;
1718 }
1819
@@ -86,7 +87,7 @@ class TrieNode {
8687
8788interface VisitResult {
8889 node : TrieNode ;
89- abbreviate : ( ) => React . ReactNode ;
90+ abbreviate : ( isRoot ?: boolean ) => React . ReactNode ;
9091}
9192
9293class TrieBuilder {
@@ -118,13 +119,21 @@ class TrieBuilder {
118119 }
119120 return {
120121 node : trieNode ,
121- abbreviate : ( ) => {
122+ abbreviate : ( isRoot = false ) => {
122123 const result : React . ReactNode [ ] = [ ] ;
123124 if ( prefix != null ) {
124125 result . push ( prefix . abbreviate ( ) ) ;
125126 result . push ( "::" ) ;
126127 }
127- result . push ( qname . name ) ;
128+ const { name } = qname ;
129+ const hash = name . indexOf ( "#" ) ;
130+ if ( hash !== - 1 && isRoot ) {
131+ const shortName = name . substring ( 0 , hash ) ;
132+ result . push ( < IdentifierSpan > { shortName } </ IdentifierSpan > ) ;
133+ result . push ( name . substring ( hash ) ) ;
134+ } else {
135+ result . push ( isRoot ? < IdentifierSpan > { name } </ IdentifierSpan > : name ) ;
136+ }
128137 if ( args != null ) {
129138 result . push ( "<" ) ;
130139 if ( trieNodeBeforeArgs . children . size === 1 ) {
@@ -148,33 +157,73 @@ class TrieBuilder {
148157 }
149158}
150159
151- const nameTokenRegex = / \b [ ^ ] + : : [ ^ ( ] + \b / g;
160+ /**
161+ * Span enclosing an entire qualified name.
162+ *
163+ * Can be used to gray out uninteresting parts of the name, though this looks worse than expected.
164+ */
165+ const QNameSpan = styled . span `
166+ /* color: var(--vscode-disabledForeground); */
167+ ` ;
168+
169+ /** Span enclosing the innermost identifier, e.g. the `foo` in `A::B<X>::foo#abc` */
170+ const IdentifierSpan = styled . span `
171+ font-weight: 600;
172+ ` ;
173+
174+ /** Span enclosing keywords such as `JOIN` and `WITH`. */
175+ const KeywordSpan = styled . span `
176+ font-weight: 500;
177+ ` ;
178+
179+ const nameTokenRegex = / \b [ ^ ( ] + \b / g;
180+
181+ function traverseMatches (
182+ text : string ,
183+ regex : RegExp ,
184+ callbacks : {
185+ onMatch : ( match : RegExpMatchArray ) => void ;
186+ onText : ( text : string ) => void ;
187+ } ,
188+ ) {
189+ const matches = Array . from ( text . matchAll ( regex ) ) ;
190+ let lastIndex = 0 ;
191+ for ( const match of matches ) {
192+ const before = text . substring ( lastIndex , match . index ) ;
193+ if ( before !== "" ) {
194+ callbacks . onText ( before ) ;
195+ }
196+ callbacks . onMatch ( match ) ;
197+ lastIndex = match . index + match [ 0 ] . length ;
198+ }
199+ const after = text . substring ( lastIndex ) ;
200+ if ( after !== "" ) {
201+ callbacks . onText ( after ) ;
202+ }
203+ }
152204
153205export function abbreviateRASteps ( steps : string [ ] ) : React . ReactNode [ ] {
154206 const nameTokens = steps . flatMap ( ( step ) =>
155207 Array . from ( step . matchAll ( nameTokenRegex ) ) . map ( ( tok ) => tok [ 0 ] ) ,
156208 ) ;
157- const nameSet = new NameSet ( nameTokens ) ;
209+ const nameSet = new NameSet ( nameTokens . filter ( ( name ) => name . includes ( "::" ) ) ) ;
158210 return steps . map ( ( step , index ) => {
159- const matches = Array . from ( step . matchAll ( nameTokenRegex ) ) ;
160211 const result : React . ReactNode [ ] = [ ] ;
161- for ( let i = 0 ; i < matches . length ; i ++ ) {
162- const match = matches [ i ] ;
163- const before = step . slice (
164- i === 0 ? 0 : matches [ i - 1 ] . index + matches [ i - 1 ] [ 0 ] . length ,
165- match . index ,
166- ) ;
167- result . push ( before ) ;
168- result . push ( nameSet . getAbbreviation ( match [ 0 ] ) ) ;
169- }
170- result . push (
171- matches . length === 0
172- ? step
173- : step . slice (
174- matches [ matches . length - 1 ] . index +
175- matches [ matches . length - 1 ] [ 0 ] . length ,
176- ) ,
177- ) ;
212+ traverseMatches ( step , nameTokenRegex , {
213+ onMatch ( match ) {
214+ const text = match [ 0 ] ;
215+ if ( text . includes ( "::" ) ) {
216+ result . push ( < QNameSpan > { nameSet . getAbbreviation ( text ) } </ QNameSpan > ) ;
217+ } else if ( / [ A - Z ] + / . test ( text ) ) {
218+ result . push ( < KeywordSpan > { text } </ KeywordSpan > ) ;
219+ } else {
220+ result . push ( match [ 0 ] ) ;
221+ }
222+ } ,
223+ onText ( text ) {
224+ result . push ( text ) ;
225+ } ,
226+ } ) ;
178227 return < Fragment key = { index } > { result } </ Fragment > ;
179228 } ) ;
180229}
0 commit comments