168 lines
4.3 KiB
TypeScript
168 lines
4.3 KiB
TypeScript
import { Lexer } from '../../library-ts/browser/text/lexer/Lexer.js';
|
|
|
|
export interface MultiLineBlock
|
|
{
|
|
type: string;
|
|
start: RegExp;
|
|
end: RegExp;
|
|
cmClass: string;
|
|
}
|
|
|
|
interface KeywordSet
|
|
{
|
|
id: string;
|
|
words: Set<string>;
|
|
cmClass: string;
|
|
tokenTypes: string[];
|
|
}
|
|
|
|
export class CodeMirrorLexerMode
|
|
{
|
|
private _lexer: Lexer;
|
|
private _typeToClass: Record<string, string | null>;
|
|
private _multiLineBlocks: MultiLineBlock[];
|
|
private _multiLineTypes: Set<string>;
|
|
private _keywordSets: KeywordSet[] = [];
|
|
private _cmModeCache: any = null;
|
|
|
|
name: string = '';
|
|
|
|
constructor(
|
|
lexer: Lexer,
|
|
typeToClass: Record<string, string | null>,
|
|
multiLineBlocks: MultiLineBlock[] = []
|
|
)
|
|
{
|
|
this._lexer = lexer;
|
|
this._typeToClass = typeToClass;
|
|
this._multiLineBlocks = multiLineBlocks;
|
|
this._multiLineTypes = new Set( multiLineBlocks.map( b => b.type ) );
|
|
}
|
|
|
|
setKeywordSet( id: string, words: string[], cmClass: string, tokenTypes: string[] = [ 'CWORD' ] ): this
|
|
{
|
|
const existing = this._keywordSets.find( ks => ks.id === id );
|
|
|
|
if ( existing )
|
|
{
|
|
existing.words = new Set( words );
|
|
existing.cmClass = cmClass;
|
|
existing.tokenTypes = tokenTypes;
|
|
}
|
|
else
|
|
{
|
|
this._keywordSets.push( { id, words: new Set( words ), cmClass, tokenTypes } );
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
removeKeywordSet( id: string ): this
|
|
{
|
|
this._keywordSets = this._keywordSets.filter( ks => ks.id !== id );
|
|
return this;
|
|
}
|
|
|
|
refresh( cm: any ): void
|
|
{
|
|
cm.setOption( 'mode', this.name );
|
|
}
|
|
|
|
get cmMode(): any
|
|
{
|
|
if ( this._cmModeCache ) return this._cmModeCache;
|
|
|
|
const self = this;
|
|
|
|
this._cmModeCache =
|
|
{
|
|
startState: () => ( { multiLine: null as string | null } ),
|
|
|
|
token( stream: any, state: any ): string | null
|
|
{
|
|
// Ongoing multi-line block
|
|
if ( state.multiLine !== null )
|
|
{
|
|
const block = self._multiLineBlocks.find( b => b.type === state.multiLine )!;
|
|
const remaining = stream.string.slice( stream.pos );
|
|
const endMatch = block.end.exec( remaining );
|
|
|
|
if ( endMatch !== null )
|
|
{
|
|
const len = endMatch.index + endMatch[ 0 ].length;
|
|
for ( let i = 0; i < len; i++ ) stream.next();
|
|
state.multiLine = null;
|
|
}
|
|
else
|
|
{
|
|
stream.skipToEnd();
|
|
}
|
|
|
|
return block.cmClass;
|
|
}
|
|
|
|
// Multi-line block starts
|
|
for ( const block of self._multiLineBlocks )
|
|
{
|
|
const remaining = stream.string.slice( stream.pos );
|
|
const startMatch = block.start.exec( remaining );
|
|
|
|
if ( startMatch && startMatch.index === 0 )
|
|
{
|
|
const startLen = startMatch[ 0 ].length;
|
|
const afterStart = remaining.slice( startLen );
|
|
const endMatch = block.end.exec( afterStart );
|
|
|
|
if ( endMatch !== null )
|
|
{
|
|
const totalLen = startLen + endMatch.index + endMatch[ 0 ].length;
|
|
for ( let i = 0; i < totalLen; i++ ) stream.next();
|
|
}
|
|
else
|
|
{
|
|
for ( let i = 0; i < startLen; i++ ) stream.next();
|
|
stream.skipToEnd();
|
|
state.multiLine = block.type;
|
|
}
|
|
|
|
return block.cmClass;
|
|
}
|
|
}
|
|
|
|
// Regular matchers
|
|
const matchers = self._lexer.modes.get( 'default' ) ?? [];
|
|
|
|
for ( const matcher of matchers )
|
|
{
|
|
if ( self._multiLineTypes.has( matcher.type ) ) continue;
|
|
|
|
const startPos = stream.pos;
|
|
const len = matcher.matchLength( stream.string, startPos );
|
|
|
|
if ( len > 0 )
|
|
{
|
|
for ( let i = 0; i < len; i++ ) stream.next();
|
|
|
|
const word = stream.string.slice( startPos, stream.pos );
|
|
|
|
for ( const ks of self._keywordSets )
|
|
{
|
|
if ( ks.tokenTypes.includes( matcher.type ) && ks.words.has( word ) )
|
|
{
|
|
return ks.cmClass;
|
|
}
|
|
}
|
|
|
|
return self._typeToClass[ matcher.type ] ?? null;
|
|
}
|
|
}
|
|
|
|
stream.next();
|
|
return null;
|
|
}
|
|
};
|
|
|
|
return this._cmModeCache;
|
|
}
|
|
}
|