import { CodeLensProvider, TextDocument, CodeLens, Range, Command } from "vscode"; export interface Reference { document: TextDocument; citekey: string; pagenr: number | null; range: Range; } export class ZoteroCodeLensProvider implements CodeLensProvider { // Each provider requires a provideCodeLenses function which will give the various documents // the code lenses async provideCodeLenses(document: TextDocument): Promise { const references = this.findReferences(document); return references // sort by line, citekey, pagenr .sort((a, b) => { if (a.range.start.line !== b.range.start.line) { return a.range.start.line - b.range.start.line; } if (a.citekey !== b.citekey) { return a.citekey.localeCompare(b.citekey); } // nulls sort before anything else if (a.pagenr === null) { return -1; } if (b.pagenr === null) { return 1; } return a.pagenr - b.pagenr; }) // remove duplicate items (which are in sequence) .filter((reference, index) => { if (index === 0) { return true; } if ( references[index - 1].range.start.line !== reference.range.start.line || references[index - 1].citekey !== reference.citekey || references[index - 1].pagenr !== reference.pagenr ) { return true; } return false; }) .map((reference, index) => { let title = ""; if ( index === 0 || references[index - 1].range.start.line !== reference.range.start.line || references[index - 1].citekey !== reference.citekey ) { title = `@${reference.citekey}`; if (reference.pagenr) { title += ` p.${reference.pagenr}`; } } else { // there has to be a pagenr because of filtering title = `p.${reference.pagenr}`; } // ignore char pos so that the order of the array // is actually used (by default CodeLenses are sorted by position) const range = new Range( reference.range.start.line, 0, reference.range.end.line, 0 ); return [ new CodeLens(range, { title: title, // command: 'zoterolens.showInZotero', command: 'zoterolens.openZoteroPDF', arguments: [reference] }), // new CodeLens(reference.range, { // title: '(pdf)', // command: 'zoterolens.openZoteroPDF', // arguments: [reference] // }) ] }) .flat(); // let codeLens = new CodeLens(topOfDocument, c); // return [codeLens]; } // TODO match citations in brackets [@citation] inline @citatinon, // but also page number in [see @citation p.23] and // inlince @citation [p.23] (so brackets after inline) // TODO possibly use https://github.com/martinring/markdown-it-citations/blob/ba82a511de047a2438b4ac060c4c71b5a5c82da9/src/index.ts#L43 findReferences(document: TextDocument): Reference[] { const matches: Reference[] = []; for (let lineNr = 0; lineNr < document.lineCount; lineNr++) { const line = document.lineAt(lineNr); let match: RegExpExecArray | null; let regex = /(?<=@)([\w\.]+)[, ]*\[?(?:[p]{0,2}\.)?(\d+)?(?:-+\d+)?\]?/g; regex.lastIndex = 0; const text = line.text;//.substring(0, 1000); while ((match = regex.exec(text))) { const result = { document: document, citekey: match[1], pagenr: match[2] ? parseInt(match[2]) : null, range: new Range(lineNr, match.index, lineNr, match.index + match[0].length) } as Reference; // if (result) { matches.push(result); // } } } return matches; } }