XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
// Explicit Monaco adapter for the website's unchanged highlight.js/csharp.js.
// The website does not itself use Monaco. Grammar output and CSS-derived
// foreground colors are shared; Monaco supplies editing and model behavior.
(function () {
    'use strict';
    if (typeof hljs !== 'object' || !hljs.getLanguage('csharp')) throw new Error('Website C# grammar was not loaded');
    if (!globalThis.xfeWebsiteCodePalette) throw new Error('Website CSS-derived palette was not loaded');
    const MAXIMUM_CODE_CHARACTERS = 131072;
    const palette = xfeWebsiteCodePalette;
    const tokenName = scopes => 'xfe.' + scopes.map(scope => scope.replace(/^hljs-/, '').replace(/-/g, '_')).join('.');
    const themeRules = palette.Rules.filter(rule => rule.Scopes[0] !== 'hljs' || rule.Scopes.length === 1).map(rule => {
        const result = {token: rule.Token || (rule.Scopes[0] === 'hljs' ? 'xfe' : tokenName(rule.Scopes))};
        if (rule.Foreground) result.foreground = rule.Foreground;
        if (rule.Background) result.background = rule.Background;
        if (rule.FontStyle) result.fontStyle = rule.FontStyle;
        return result;
    });
    function decode(value) {
        return value.replace(/&(?:amp|lt|gt|quot|#x27|#39);/g, entity => ({'&amp;':'&','&lt;':'<','&gt;':'>','&quot;':'"','&#x27;':"'",'&#39;':"'"})[entity]);
    }
    function tokenizeSource(source) {
        source = String(source);
        if (source.length > MAXIMUM_CODE_CHARACTERS) throw new Error('Website C# highlighting exceeds the explicit 128 KiB preview limit');
        const html = hljs.highlight(source, {language:'csharp', ignoreIllegals:true}).value;
        const lines = [[]];
        const stack = [];
        let offset = 0, line = 0, reconstructed = '';
        function text(value) {
            value = decode(value);
            reconstructed += value;
            for (const part of value.split(/(\n)/)) {
                if (part === '\n') {line++; lines.push([]); offset = 0; continue;}
                if (!part) continue;
                // Highlight.js scopes inherit CSS from their ancestors. Monaco
                // token types instead match from the root; keep the leaf scopes
                // and the website's two explicit descendant-selector overrides.
                let scopes = stack.length ? stack[stack.length-1] : [];
                const ancestors = stack.slice(0,-1).flat();
                if (scopes.includes('hljs-title')) {
                    if (ancestors.includes('hljs-constructor')) scopes=['hljs-constructor'].concat(scopes);
                    else if (ancestors.includes('hljs-function')) scopes=['hljs-function'].concat(scopes);
                } else if (ancestors.includes('hljs-meta') && (scopes.includes('hljs-string') || scopes.includes('hljs-keyword'))) scopes=['hljs-meta'].concat(scopes);
                const type = scopes.length ? tokenName(scopes) : 'xfe';
                const tokens = lines[line];
                if (!tokens.length || tokens[tokens.length-1].scopes !== type) tokens.push({startIndex:offset,scopes:type});
                offset += part.length;
            }
        }
        const tags = /<span class="([^"]*)">|<\/span>|([^<]+)/g;
        let match;
        while ((match = tags.exec(html))) {
            if (match[1] !== undefined) stack.push(match[1].split(/\s+/));
            else if (match[0] === '</span>') {if (!stack.length) throw new Error('Unexpected website highlight span end'); stack.pop();}
            else text(match[2]);
        }
        if (reconstructed !== source || stack.length) throw new Error('Website C# highlight output did not preserve the exact model text');
        for (const tokens of lines) if (!tokens.length) tokens.push({startIndex:0,scopes:'xfe'});
        return {source,html,lines};
    }
    function State(source, line, prefix) {this.source=source;this.line=line;this.prefix=prefix;}
    State.prototype.clone = function () {return new State(this.source,this.line,this.prefix);};
    State.prototype.equals = function (other) {return !!other && other.line===this.line && other.source===this.source && other.prefix===this.prefix;};
    function install(getModel) {
        monaco.editor.defineTheme('xfe-website', {base:'vs-dark',inherit:false,rules:themeRules,colors:{
            'editor.background':'#2B2B2B','editor.foreground':'#'+themeRules.find(rule=>rule.token==='xfe').foreground,'editorLineNumber.foreground':'#9B9B9B',
            'editorCursor.foreground':'#75C9F5','editor.selectionBackground':'#264F78','editor.lineHighlightBackground':'#353535'
        }});
        let cached = null;
        monaco.languages.setTokensProvider('csharp', {
            getInitialState:function () {return new State('',0,'');},
            tokenize:function (line,state) {
                const model = getModel && getModel();
                const realModel = model && model.getLanguageId()==='csharp' && state.line < model.getLineCount() && model.getLineContent(state.line+1)===line;
                const source = realModel ? model.getValue() : state.prefix+line;
                if (!cached || cached.source!==source) cached=tokenizeSource(source);
                const tokens = cached.lines[state.line] || [{startIndex:0,scopes:'xfe'}];
                return {tokens,endState:new State(source,state.line+1,state.prefix+line+'\n')};
            }
        });
        return {theme:'xfe-website',maximumCodeCharacters:MAXIMUM_CODE_CHARACTERS};
    }
    function getProof(editor) {
        const model=editor.getModel(), value=model.getValue();
        const active=document.activeElement, position=editor.getPosition(), selection=editor.getSelection();
        const truncated=value.length>16384;
        const source=truncated?value.slice(0,16384):value;
        const parsed=tokenizeSource(source);
        const lines=Array.from(editor.getDomNode().querySelectorAll('.view-lines .view-line')).slice(0,64).map(line=>({
            Line:Math.round(parseFloat(line.style.top)/20)+1,
            Text:line.textContent.replace(/\u00a0/g,' '),
            CssTop:parseFloat(line.style.top),
            Spans:Array.from(line.querySelectorAll('span[class]')).slice(0,256).map(span=>({
                Text:span.textContent.replace(/\u00a0/g,' '),Color:getComputedStyle(span).color,Classes:span.className
            }))
        }));
        const textSurface=editor.getDomNode().querySelector('.view-lines')||editor.getDomNode();
        return {Kind:'ActualMonacoWebsiteCSharpDomReadback',Language:model.getLanguageId(),ModelValue:source,
            ModelLength:value.length,Truncated:truncated,PaletteCssSha256:palette.CssSha256,PaletteCascadeSha256:palette.CascadeCssSha256,
            ActiveElementTag:active?active.tagName:null,ActiveElementClasses:active?active.className:null,
            ActiveElementId:active?active.id:null,EditorHasTextFocus:editor.hasTextFocus(),EditorHasWidgetFocus:editor.hasWidgetFocus(),
            CursorPosition:position?{LineNumber:position.lineNumber,Column:position.column}:null,
            Selection:selection?{StartLineNumber:selection.startLineNumber,StartColumn:selection.startColumn,
                EndLineNumber:selection.endLineNumber,EndColumn:selection.endColumn}:null,ModelVersionId:model.getVersionId(),
            ConfiguredLineHeightCss:20,TokenLines:parsed.lines,ViewLines:lines,
            FontFamily:getComputedStyle(textSurface).fontFamily,
            FontSize:getComputedStyle(textSurface).fontSize,
            HostScreenshotUsed:false,WorkerLanguageService:false};
    }
    globalThis.xfeWebsiteHighlight = {install,tokenizeSource,getProof,themeRules,paletteCssSha256:palette.CssSha256,paletteCascadeSha256:palette.CascadeCssSha256,maximumCodeCharacters:MAXIMUM_CODE_CHARACTERS};
})();