tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

import-folds.js (2714B)


      1 // Hash decorations project canonical UTF-16 document ranges. Editing a revealed
      2 // hash uses ordinary character-level selection and deletion.
      3 import { StateField, EditorSelection, EditorView, Decoration, WidgetType } from './codemirror.js';
      4 import { tokenize } from './syntax.js';
      5 const cachedSites = new WeakMap();
      6 export function importSites(state) {
      7     const cached = cachedSites.get(state.doc);
      8     if (cached)
      9         return cached;
     10     const source = state.doc.toString();
     11     const sites = tokenize(source).filter(token => token.kind === 'directive' && token.text === '!import').flatMap(token => {
     12         const line = state.doc.lineAt(token.start);
     13         if (source.slice(line.from, token.start).trim())
     14             return [];
     15         const match = /^[ \t]+([a-f0-9]{64})(?=[ \t\r\n]|$)/.exec(source.slice(token.end));
     16         if (!match)
     17             return [];
     18         const from = token.end + match[0].length - 64;
     19         return [{ from, to: from + 64, hash: match[1], line: line.number }];
     20     });
     21     cachedSites.set(state.doc, sites);
     22     return sites;
     23 }
     24 const short = (hash) => hash.slice(0, 3) + '...' + hash.slice(-3);
     25 class ImportHash extends WidgetType {
     26     site;
     27     constructor(site) {
     28         super();
     29         this.site = site;
     30     }
     31     eq(other) { return this.site.from === other.site.from && this.site.hash === other.site.hash; }
     32     toDOM(view) {
     33         const button = document.createElement('button');
     34         button.type = 'button';
     35         button.className = 'import-hash';
     36         button.textContent = short(this.site.hash);
     37         button.setAttribute('aria-label', `Place cursor at import ${this.site.hash}`);
     38         button.onclick = () => {
     39             view.dispatch({ selection: EditorSelection.single(this.site.to) });
     40             view.focus();
     41         };
     42         return button;
     43     }
     44     ignoreEvent() { return true; }
     45 }
     46 export function selectedImportLines(state) {
     47     return new Set(state.selection.ranges.map(range => state.doc.lineAt(range.head).number));
     48 }
     49 function decorate(state) {
     50     const selected = selectedImportLines(state), ranges = [];
     51     for (const site of importSites(state))
     52         ranges.push((selected.has(site.line) ? Decoration.mark({ class: 'import-hash-full', attributes: { 'data-import-hash': site.hash, role: 'button', tabindex: '0', 'aria-label': 'Browse imported module ' + site.hash } }) :
     53             Decoration.replace({ widget: new ImportHash(site) })).range(site.from, site.to));
     54     return Decoration.set(ranges, true);
     55 }
     56 export const importFolds = StateField.define({
     57     create: decorate,
     58     update: (old, tr) => tr.docChanged || tr.selection ? decorate(tr.state) : old,
     59     provide: field => EditorView.decorations.from(field)
     60 });