diff --git a/package.json b/package.json index 6648d0b..eacae5e 100644 --- a/package.json +++ b/package.json @@ -1,132 +1,131 @@ { - "name": "pandocciter", - "displayName": "Pandoc Citer", - "description": "Autocomplete bibtex citations for markdown/pandoc", - "version": "0.10.3", - "publisher": "notZaki", - "license": "MIT", - "engines": { - "vscode": "^1.78.1" - }, - "icon": "icon.png", - "homepage": "https://github.com/notZaki/PandocCiter", - "repository": { - "type": "git", - "url": "https://github.com/notZaki/PandocCiter" - }, - "bugs": { - "url": "https://github.com/notZaki/PandocCiter/issues" - }, - "categories": [ - "Other" - ], - "activationEvents": [ - "onLanguage:markdown", - "onLanguage:rmd", - "onLanguage:pweave_md", - "onLanguage:quarto" - ], - "main": "./out/extension", - "contributes": { - "grammars": [ - { - "injectTo": [ - "text.html.markdown", - "text.html.markdown.redcarpet", - "source.pweave.md" - ], - "scopeName": "pandoc-citation", - "path": "./syntaxes/citations.json" - } + "name": "pandocciter", + "displayName": "Pandoc Citer", + "description": "Autocomplete bibtex citations for markdown/pandoc", + "version": "0.10.3", + "publisher": "notZaki", + "license": "MIT", + "engines": { + "vscode": "^1.78.1" + }, + "icon": "icon.png", + "homepage": "https://github.com/notZaki/PandocCiter", + "repository": { + "type": "git", + "url": "https://github.com/notZaki/PandocCiter" + }, + "bugs": { + "url": "https://github.com/notZaki/PandocCiter/issues" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onLanguage:markdown", + "onLanguage:rmd", + "onLanguage:pweave_md", + "onLanguage:quarto" + ], + "main": "./out/extension", + "contributes": { + "grammars": [ + { + "injectTo": [ + "text.html.markdown", + "text.html.markdown.redcarpet", + "source.pweave.md" ], - "configuration": { - "title": "PandocCiter", - "properties": { - "PandocCiter.ViewType": { - "type": "string", - "enum": [ - "inline", - "browser" - ], - "default": "inline", - "description": "How citation completions are shown" - }, - "PandocCiter.RootFile": { - "type": "string", - "default": "", - "scope": "resource", - "description": "Main document containing YAML for bibliography" - }, - "PandocCiter.ShowLog": { - "type": "boolean", - "default": false, - "description": "Should log be shown in Output panel" - }, - "PandocCiter.DefaultBib": { - "type": "string", - "default": "", - "scope": "resource", - "description": "Path to default bib file" - }, - "PandocCiter.DefaultBibs": { - "type": "array", - "default": [], - "scope": "resource", - "description": "Path to default bib files" - }, - "PandocCiter.UseDefaultBib": { - "type": "boolean", - "default": true, - "scope": "resource", - "description": "Whether to use the Default Bib" - }, - "PandocCiter.ForgetUnusedBib": { - "type": "boolean", - "default": true, - "description": "Whether to forget bib file when changing documents" - }, - "PandocCiter.CitationFormat": { - "type": "array", - "default": [ - "author", - "title", - "journal", - "container-title", - "publisher", - "booktitle", - "year" - ], - "description": "List of fields displayed in preview" - }, - "PandocCiter.CrossRefMode": { - "type": "string", - "default": "full", - "description": "The features of CrossRef functionalities to enable", - "enum": [ - "full", - "minimal", - "none" - ] - } - } + "scopeName": "pandoc-citation", + "path": "./syntaxes/citations.json" + } + ], + "configuration": { + "title": "PandocCiter", + "properties": { + "PandocCiter.ViewType": { + "type": "string", + "enum": [ + "inline", + "browser" + ], + "default": "inline", + "description": "How citation completions are shown" + }, + "PandocCiter.RootFile": { + "type": "string", + "default": "", + "scope": "resource", + "description": "Main document containing YAML for bibliography" + }, + "PandocCiter.ShowLog": { + "type": "boolean", + "default": false, + "description": "Should log be shown in Output panel" + }, + "PandocCiter.DefaultBib": { + "type": "string", + "default": "", + "scope": "resource", + "description": "Path to default bib file" + }, + "PandocCiter.DefaultBibs": { + "type": "array", + "default": [], + "scope": "resource", + "description": "Path to default bib files" + }, + "PandocCiter.UseDefaultBib": { + "type": "boolean", + "default": true, + "scope": "resource", + "description": "Whether to use the Default Bib" + }, + "PandocCiter.ForgetUnusedBib": { + "type": "boolean", + "default": true, + "description": "Whether to forget bib file when changing documents" + }, + "PandocCiter.CitationFormat": { + "type": "array", + "default": [ + "author", + "title", + "journal", + "container-title", + "publisher", + "booktitle", + "year" + ], + "description": "List of fields displayed in preview" + }, + "PandocCiter.CrossRefMode": { + "type": "string", + "default": "full", + "description": "The features of CrossRef functionalities to enable", + "enum": [ + "full", + "minimal", + "none" + ] } - }, - "scripts": { - "vscode:prepublish": "npm run compile", - "compile": "tsc -p ./", - "watch": "tsc -watch -p ./", - "test": "npm run compile && node ./node_modules/vscode/bin/test" - }, - "dependencies": { - "chokidar": "^3.5.3", - "latex-utensils": "^6.1.0", - "js-yaml": "^4" - - }, - "devDependencies": { - "@types/node": "^20.2.5", - "eslint": "^8.42.0", - "typescript": "^5.1.3", - "@types/vscode": "^1.78.1" + } } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "test": "npm run compile && node ./node_modules/vscode/bin/test" + }, + "dependencies": { + "chokidar": "^3.5.3", + "latex-utensils": "^6.1.0", + "js-yaml": "^4" + }, + "devDependencies": { + "@types/node": "^20.2.5", + "eslint": "^8.42.0", + "typescript": "^5.1.3", + "@types/vscode": "^1.78.1" + } } diff --git a/src/components/manager.ts b/src/components/manager.ts index 1240e5c..76649d5 100644 --- a/src/components/manager.ts +++ b/src/components/manager.ts @@ -24,162 +24,177 @@ // SOFTWARE. ////////////////////////////////////////////////////////////////////////////////////////// -import * as vscode from 'vscode'; -import * as path from 'path'; -import * as fs from 'fs'; -import * as chokidar from 'chokidar'; -import yaml = require('js-yaml'); +import * as vscode from "vscode"; +import * as path from "path"; +import * as fs from "fs"; +import * as chokidar from "chokidar"; +import yaml = require("js-yaml"); -import {Extension} from '../extension'; +import { Extension } from "../extension"; export class Manager { - extension: Extension; - bibWatcher: chokidar.FSWatcher; - watched: string[]; + extension: Extension; + bibWatcher: chokidar.FSWatcher; + watched: string[]; - constructor(extension: Extension) { - this.extension = extension; - this.watched = []; - } + constructor(extension: Extension) { + this.extension = extension; + this.watched = []; + } - findBib() : void { - let foundFiles: string[] = []; - const activeText = vscode.window.activeTextEditor!.document.getText(); - - // Re-use the old reg-ex approach in case the yaml parser fails - const bibRegex = /^bibliography:\s* \[(.*)\]/m; - let bibresult = activeText.match(bibRegex); - if (bibresult) { - const bibFiles = bibresult[1].split(',').map(item => item.trim()); - for (let i in bibFiles) { - let bibFile = this.stripQuotes(bibFiles[i]); - bibFile = this.resolveBibFile(bibFile, undefined); - this.extension.log(`Looking for .bib file: ${bibFile}`); - this.addBibToWatcher(bibFile); - foundFiles.push(bibFile); - } - } + findBib(): void { + let foundFiles: string[] = []; + const activeText = vscode.window.activeTextEditor!.document.getText(); - // This is the newer approach using yaml-js - const docURI = vscode.window.activeTextEditor!.document.uri; - const configuration = vscode.workspace.getConfiguration('PandocCiter', docURI); - const rootFolder = vscode.workspace.getWorkspaceFolder(docURI)?.uri.fsPath; - const yamltext = activeText.match(/---\r?\n((.+\r?\n)+)---/gm) - const parsedyaml = yaml.loadAll(yamltext)[0] - if (parsedyaml && parsedyaml.bibliography) { - const bibInYaml = yaml.loadAll(yamltext)[0].bibliography - const bibFiles = (bibInYaml instanceof Array ? bibInYaml : [bibInYaml]); - for (let i in bibFiles) { - let bibFile = this.stripQuotes(bibFiles[i]); - bibFile = this.resolveBibFile(bibFile, undefined); - this.extension.log(`Looking for file: ${bibFile}`); - this.addBibToWatcher(bibFile); - foundFiles.push(bibFile); - } - } - const rootfile: string = configuration.get('RootFile') - if (rootfile !== "") { - let curInput = path.join(rootfile); - if (!path.isAbsolute(curInput) && rootFolder) { - curInput = path.join(rootFolder, rootfile); - } - const rootText = fs.readFileSync(curInput,'utf8'); - const bibInYaml = yaml.loadAll(rootText)[0].bibliography - const bibFiles = (bibInYaml instanceof Array ? bibInYaml : [bibInYaml]); - for (let i in bibFiles) { - let bibFile = path.join(path.dirname(curInput), bibFiles[i]); - bibFile = this.resolveBibFile(bibFile, rootFolder); - this.extension.log(`Looking for file: ${bibFile}`); - this.addBibToWatcher(bibFile); - foundFiles.push(bibFile); - } - } - if (configuration.get('UseDefaultBib') && (configuration.get('DefaultBib'))) { - let bibFile = path.join(configuration.get('DefaultBib')); - bibFile = this.resolveBibFile(bibFile, rootFolder); - this.extension.log(`Looking for file: ${bibFile}`); - this.addBibToWatcher(bibFile); - foundFiles.push(bibFile); - } - if (configuration.get('UseDefaultBib') && (configuration.get('DefaultBibs'))) { - let bibFiles: string[] = configuration.get('DefaultBibs'); - bibFiles.forEach(element => { - let bibFile = this.resolveBibFile(path.join(element), rootFolder); - this.extension.log(`Looking for file: ${bibFile}`); - this.addBibToWatcher(bibFile); - foundFiles.push(bibFile); - }); - } - let watched_but_not_found = this.watched.filter(e => !foundFiles.includes(e)); - if (configuration.get('ForgetUnusedBib')) { - if (watched_but_not_found.length > 0) { - this.forgetUnusedFiles(watched_but_not_found); - } - } - return; + // Re-use the old reg-ex approach in case the yaml parser fails + const bibRegex = /^bibliography:\s* \[(.*)\]/m; + let bibresult = activeText.match(bibRegex); + if (bibresult) { + const bibFiles = bibresult[1].split(",").map((item) => item.trim()); + for (let i in bibFiles) { + let bibFile = this.stripQuotes(bibFiles[i]); + bibFile = this.resolveBibFile(bibFile, undefined); + this.extension.log(`Looking for .bib file: ${bibFile}`); + this.addBibToWatcher(bibFile); + foundFiles.push(bibFile); + } } - stripQuotes(inputString: string) { - if (inputString[0] === inputString[inputString.length-1] && "\"'".includes(inputString[0])) { - return inputString.slice(1, -1); - } else { - return inputString; - } + // This is the newer approach using yaml-js + const docURI = vscode.window.activeTextEditor!.document.uri; + const configuration = vscode.workspace.getConfiguration( + "PandocCiter", + docURI + ); + const rootFolder = vscode.workspace.getWorkspaceFolder(docURI)?.uri.fsPath; + const yamltext = activeText.match(/---\r?\n((.+\r?\n)+)---/gm); + const parsedyaml = yaml.loadAll(yamltext)[0]; + if (parsedyaml && parsedyaml.bibliography) { + const bibInYaml = yaml.loadAll(yamltext)[0].bibliography; + const bibFiles = bibInYaml instanceof Array ? bibInYaml : [bibInYaml]; + for (let i in bibFiles) { + let bibFile = this.stripQuotes(bibFiles[i]); + bibFile = this.resolveBibFile(bibFile, undefined); + this.extension.log(`Looking for file: ${bibFile}`); + this.addBibToWatcher(bibFile); + foundFiles.push(bibFile); + } + } + const rootfile: string = configuration.get("RootFile"); + if (rootfile !== "") { + let curInput = path.join(rootfile); + if (!path.isAbsolute(curInput) && rootFolder) { + curInput = path.join(rootFolder, rootfile); + } + const rootText = fs.readFileSync(curInput, "utf8"); + const bibInYaml = yaml.loadAll(rootText)[0].bibliography; + const bibFiles = bibInYaml instanceof Array ? bibInYaml : [bibInYaml]; + for (let i in bibFiles) { + let bibFile = path.join(path.dirname(curInput), bibFiles[i]); + bibFile = this.resolveBibFile(bibFile, rootFolder); + this.extension.log(`Looking for file: ${bibFile}`); + this.addBibToWatcher(bibFile); + foundFiles.push(bibFile); + } + } + if (configuration.get("UseDefaultBib") && configuration.get("DefaultBib")) { + let bibFile = path.join(configuration.get("DefaultBib")); + bibFile = this.resolveBibFile(bibFile, rootFolder); + this.extension.log(`Looking for file: ${bibFile}`); + this.addBibToWatcher(bibFile); + foundFiles.push(bibFile); + } + if ( + configuration.get("UseDefaultBib") && + configuration.get("DefaultBibs") + ) { + let bibFiles: string[] = configuration.get("DefaultBibs"); + bibFiles.forEach((element) => { + let bibFile = this.resolveBibFile(path.join(element), rootFolder); + this.extension.log(`Looking for file: ${bibFile}`); + this.addBibToWatcher(bibFile); + foundFiles.push(bibFile); + }); } + let watched_but_not_found = this.watched.filter( + (e) => !foundFiles.includes(e) + ); + if (configuration.get("ForgetUnusedBib")) { + if (watched_but_not_found.length > 0) { + this.forgetUnusedFiles(watched_but_not_found); + } + } + return; + } - resolveBibFile(bibFile: string, rootFolder: string) { - if (path.isAbsolute(bibFile)) { - return bibFile; - } else if (rootFolder) { - return path.resolve(path.join(rootFolder, bibFile)); - } else { - return path.resolve(path.dirname(vscode.window.activeTextEditor!.document.fileName), bibFile) - } + stripQuotes(inputString: string) { + if ( + inputString[0] === inputString[inputString.length - 1] && + "\"'".includes(inputString[0]) + ) { + return inputString.slice(1, -1); + } else { + return inputString; } + } - addBibToWatcher(bibPath: string) { - if (!fs.existsSync(bibPath) && fs.existsSync(bibPath + '.json')) { - bibPath += '.json'; - } - if (!fs.existsSync(bibPath) && fs.existsSync(bibPath + '.bib')) { - bibPath += '.bib'; - } - if (fs.existsSync(bibPath)) { - this.extension.log(`Found file ${bibPath}`); - if (this.bibWatcher === undefined) { - this.extension.log(`Creating file watcher for files.`); - this.bibWatcher = chokidar.watch(bibPath, {awaitWriteFinish: true}); - this.bibWatcher.on('change', (filePath: string) => { - this.extension.log(`Bib file watcher - responding to change in ${filePath}`); - this.extension.completer.citation.parseBibFile(filePath); - }); - this.bibWatcher.on('unlink', (filePath: string) => { - this.extension.log(`Bib file watcher: ${filePath} deleted.`); - this.extension.completer.citation.forgetParsedBibItems(filePath); - this.bibWatcher.unwatch(filePath); - this.watched.splice(this.watched.indexOf(filePath), 1); - }); - this.extension.completer.citation.parseBibFile(bibPath); - } else if (this.watched.indexOf(bibPath) < 0) { - this.extension.log(`Adding file ${bibPath} to bib file watcher.`); - this.bibWatcher.add(bibPath); - this.watched.push(bibPath); - this.extension.completer.citation.parseBibFile(bibPath); - } else { - this.extension.log(`bib file ${bibPath} is already being watched.`); - } - } + resolveBibFile(bibFile: string, rootFolder: string) { + if (path.isAbsolute(bibFile)) { + return bibFile; + } else if (rootFolder) { + return path.resolve(path.join(rootFolder, bibFile)); + } else { + return path.resolve( + path.dirname(vscode.window.activeTextEditor!.document.fileName), + bibFile + ); } + } - forgetUnusedFiles(filesToForget: string[]) { - for (let i in filesToForget) { - let filePath = filesToForget[i]; - this.extension.log(`Forget unused bib file: ${filePath}`); - this.extension.completer.citation.forgetParsedBibItems(filePath); - this.bibWatcher.unwatch(filePath); - this.watched.splice(this.watched.indexOf(filePath), 1); - } - return; + addBibToWatcher(bibPath: string) { + if (!fs.existsSync(bibPath) && fs.existsSync(bibPath + ".json")) { + bibPath += ".json"; + } + if (!fs.existsSync(bibPath) && fs.existsSync(bibPath + ".bib")) { + bibPath += ".bib"; } + if (fs.existsSync(bibPath)) { + this.extension.log(`Found file ${bibPath}`); + if (this.bibWatcher === undefined) { + this.extension.log(`Creating file watcher for files.`); + this.bibWatcher = chokidar.watch(bibPath, { awaitWriteFinish: true }); + this.bibWatcher.on("change", (filePath: string) => { + this.extension.log( + `Bib file watcher - responding to change in ${filePath}` + ); + this.extension.completer.citation.parseBibFile(filePath); + }); + this.bibWatcher.on("unlink", (filePath: string) => { + this.extension.log(`Bib file watcher: ${filePath} deleted.`); + this.extension.completer.citation.forgetParsedBibItems(filePath); + this.bibWatcher.unwatch(filePath); + this.watched.splice(this.watched.indexOf(filePath), 1); + }); + this.extension.completer.citation.parseBibFile(bibPath); + } else if (this.watched.indexOf(bibPath) < 0) { + this.extension.log(`Adding file ${bibPath} to bib file watcher.`); + this.bibWatcher.add(bibPath); + this.watched.push(bibPath); + this.extension.completer.citation.parseBibFile(bibPath); + } else { + this.extension.log(`bib file ${bibPath} is already being watched.`); + } + } + } -} \ No newline at end of file + forgetUnusedFiles(filesToForget: string[]) { + for (let i in filesToForget) { + let filePath = filesToForget[i]; + this.extension.log(`Forget unused bib file: ${filePath}`); + this.extension.completer.citation.forgetParsedBibItems(filePath); + this.bibWatcher.unwatch(filePath); + this.watched.splice(this.watched.indexOf(filePath), 1); + } + return; + } +} diff --git a/src/extension.ts b/src/extension.ts index ac4b667..4385a55 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,74 +1,90 @@ -'use strict'; +"use strict"; -import * as vscode from 'vscode'; +import * as vscode from "vscode"; -import {Manager} from './components/manager'; -import {Completer} from './providers/completion'; -import {HoverProvider} from './providers/hover'; -import {DefinitionProvider} from './providers/definition'; +import { Manager } from "./components/manager"; +import { Completer } from "./providers/completion"; +import { HoverProvider } from "./providers/hover"; +import { DefinitionProvider } from "./providers/definition"; export function activate(context: vscode.ExtensionContext) { - const extension = new Extension(); + const extension = new Extension(); - context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(() => { - extension.log("Reacting to document open") - if (vscode.window.activeTextEditor) { - extension.manager.findBib(); - } - })); + context.subscriptions.push( + vscode.workspace.onDidOpenTextDocument(() => { + extension.log("Reacting to document open"); + if (vscode.window.activeTextEditor) { + extension.manager.findBib(); + } + }) + ); - context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(() => { - extension.log("Reacting to active document change") - if ((vscode.window.activeTextEditor) && - (['markdown', 'rmd', 'pweave_md'].includes(vscode.window.activeTextEditor.document.languageId))) { - extension.manager.findBib(); - } - })); + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor(() => { + extension.log("Reacting to active document change"); + if ( + vscode.window.activeTextEditor && + ["markdown", "rmd", "pweave_md"].includes( + vscode.window.activeTextEditor.document.languageId + ) + ) { + extension.manager.findBib(); + } + }) + ); - context.subscriptions.push(vscode.workspace.onDidSaveTextDocument(() => { - extension.log("Reacting to document save") - if (vscode.window.activeTextEditor) { - extension.manager.findBib(); - } - })); + context.subscriptions.push( + vscode.workspace.onDidSaveTextDocument(() => { + extension.log("Reacting to document save"); + if (vscode.window.activeTextEditor) { + extension.manager.findBib(); + } + }) + ); - const selector = ['markdown','rmd','pweave_md','quarto'].map((language)=>{ - return {scheme: 'file', language: language}; - }); + const selector = ["markdown", "rmd", "pweave_md", "quarto"].map( + (language) => { + return { scheme: "file", language: language }; + } + ); - extension.manager.findBib(); - context.subscriptions.push(vscode.languages.registerCompletionItemProvider( - selector, extension.completer, '@')); - context.subscriptions.push(vscode.languages.registerHoverProvider( - selector, extension.hover)); - context.subscriptions.push(vscode.languages.registerDefinitionProvider( - selector, extension.definition)); + extension.manager.findBib(); + context.subscriptions.push( + vscode.languages.registerCompletionItemProvider( + selector, + extension.completer, + "@" + ) + ); + context.subscriptions.push( + vscode.languages.registerHoverProvider(selector, extension.hover) + ); + context.subscriptions.push( + vscode.languages.registerDefinitionProvider(selector, extension.definition) + ); } export class Extension { - manager: Manager; - completer: Completer; - hover: HoverProvider; - definition: DefinitionProvider; - logPanel: vscode.OutputChannel; + manager: Manager; + completer: Completer; + hover: HoverProvider; + definition: DefinitionProvider; + logPanel: vscode.OutputChannel; - constructor() { - this.manager = new Manager(this); - this.completer = new Completer(this); - this.hover = new HoverProvider(this); - this.definition = new DefinitionProvider(this); - this.logPanel = vscode.window.createOutputChannel('PandocCiter'); - this.log(`PandocCiter is now activated`); - } - - log(msg: string) { - if (vscode.workspace.getConfiguration('PandocCiter').get('ShowLog')) { - this.logPanel.append(`${msg}\n`); - } - } + constructor() { + this.manager = new Manager(this); + this.completer = new Completer(this); + this.hover = new HoverProvider(this); + this.definition = new DefinitionProvider(this); + this.logPanel = vscode.window.createOutputChannel("PandocCiter"); + this.log(`PandocCiter is now activated`); + } + log(msg: string) { + if (vscode.workspace.getConfiguration("PandocCiter").get("ShowLog")) { + this.logPanel.append(`${msg}\n`); + } + } } - -export function deactivate() { -} \ No newline at end of file +export function deactivate() {} diff --git a/src/providers/completer/citation.ts b/src/providers/completer/citation.ts index 84a0171..5ba012f 100644 --- a/src/providers/completer/citation.ts +++ b/src/providers/completer/citation.ts @@ -23,213 +23,279 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as vscode from 'vscode'; -import * as path from 'path'; -import * as fs from 'fs'; - -import {Extension} from '../../extension'; -import {bibtexParser} from 'latex-utensils'; +import * as vscode from "vscode"; +import * as path from "path"; +import * as fs from "fs"; +import { Extension } from "../../extension"; +import { bibtexParser } from "latex-utensils"; export interface Suggestion extends vscode.CompletionItem { - key: string; - documentation: string; - fields: {[key: string]: string}; - file: string; - position: vscode.Position; // Unnecessary? + key: string; + documentation: string; + fields: { [key: string]: string }; + file: string; + position: vscode.Position; // Unnecessary? } export class Citation { - extension: Extension; - private bibEntries: {[file: string]: Suggestion[]} = {}; + extension: Extension; + private bibEntries: { [file: string]: Suggestion[] } = {}; - constructor(extension: Extension) { - this.extension = extension; - } - - provide(args?: {document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext}): vscode.CompletionItem[] { - // Compile the suggestion array to vscode completion array - return this.updateAll().map(item => { - item.filterText = Object.values(item.fields).join(" ") - item.insertText = item.key; - if (args) { - item.range = args.document.getWordRangeAtPosition(args.position, /[-a-zA-Z0-9_:.]+/); - } - return item; - }); - } + constructor(extension: Extension) { + this.extension = extension; + } - browser(_args?: {document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext}) { - vscode.window.showQuickPick(this.updateAll().map(item => { - return { - label: item.fields.title ? item.fields.title : '', - description: `${item.key}`, - detail: `Authors: ${item.fields.author ? item.fields.author : 'Unknown'}, publication: ${item.fields.journal ? item.fields.journal : (item.fields.journaltitle ? item.fields.journaltitle : (item.fields.publisher ? item.fields.publisher : 'Unknown'))}` - }; - }), { - placeHolder: 'Press ENTER to insert citation key at cursor', - matchOnDetail: true, - matchOnDescription: true, - ignoreFocusOut: true - }).then(selected => { - if (!selected) { - return; - } - if (vscode.window.activeTextEditor) { - const editor = vscode.window.activeTextEditor; - const content = editor.document.getText(new vscode.Range(new vscode.Position(0, 0), editor.selection.start)); - let start = editor.selection.start; - if (content.lastIndexOf('\\cite') > content.lastIndexOf('}')) { - const curlyStart = content.lastIndexOf('{') + 1; - const commaStart = content.lastIndexOf(',') + 1; - start = editor.document.positionAt(curlyStart > commaStart ? curlyStart : commaStart); - } - editor.edit(edit => edit.replace(new vscode.Range(start, editor.selection.start), selected.description || '')) - .then(() => editor.selection = new vscode.Selection(editor.selection.end, editor.selection.end)); - } - }); - } + provide(args?: { + document: vscode.TextDocument; + position: vscode.Position; + token: vscode.CancellationToken; + context: vscode.CompletionContext; + }): vscode.CompletionItem[] { + // Compile the suggestion array to vscode completion array + return this.updateAll().map((item) => { + item.filterText = Object.values(item.fields).join(" "); + item.insertText = item.key; + if (args) { + item.range = args.document.getWordRangeAtPosition( + args.position, + /[-a-zA-Z0-9_:.]+/ + ); + } + return item; + }); + } - parseBibFile(file:string) { - const ext = path.extname(file) - if (ext == ".bib") { - this.parseBibtexFile(file) - } else if (ext == ".json") { - this.parseBibjsonFile(file) - } else { - this.extension.log(`Unknown file extension force: ${file}`); + browser(_args?: { + document: vscode.TextDocument; + position: vscode.Position; + token: vscode.CancellationToken; + context: vscode.CompletionContext; + }) { + vscode.window + .showQuickPick( + this.updateAll().map((item) => { + return { + label: item.fields.title ? item.fields.title : "", + description: `${item.key}`, + detail: `Authors: ${ + item.fields.author ? item.fields.author : "Unknown" + }, publication: ${ + item.fields.journal + ? item.fields.journal + : item.fields.journaltitle + ? item.fields.journaltitle + : item.fields.publisher + ? item.fields.publisher + : "Unknown" + }`, + }; + }), + { + placeHolder: "Press ENTER to insert citation key at cursor", + matchOnDetail: true, + matchOnDescription: true, + ignoreFocusOut: true, } - } - - parseBibjsonFile(file: string) { - const fields: string[] = (vscode.workspace.getConfiguration('PandocCiter').get('CitationFormat') as string[]).map(f => { return f.toLowerCase(); }); - this.extension.log(`Parsing .bib entries from ${file}`); - this.bibEntries[file] = []; - let json = JSON.parse(fs.readFileSync(file, "utf-8")); - json.forEach((entry) => { - const item: Suggestion = { - key: entry.id, - label: entry.id, - file, - position: new vscode.Position(0, 0), - kind: vscode.CompletionItemKind.Reference, - documentation: '', - fields: {} - } - if (entry.author) { - entry.author.forEach(element => { - if (item.fields.author) { - item.fields.author += " and " - } else { - item.fields.author = "" - } - item.fields.author += Object.values(element).join(", ") - }); - item.documentation += `author: ${item.fields.author}\n` - } else if (entry.editor) { - entry.editor.forEach(element => { - if (item.fields.editor) { - item.fields.editor += " and " - } else { - item.fields.editor = "" - } - item.fields.editor += Object.values(element).join(", ") - }); - item.documentation += `editor: ${item.fields.editor}\n` - } - if (entry.issued) { - item.documentation += `date: ${Object.values(entry.issued).join()}\n` - } - if (entry.DOI) { - item.documentation += `link: https://doi.org/${entry.DOI}\n`; - } else if (entry.URL) { - item.documentation += `link: ${entry.URL}\n`; - } - fields.forEach(field => { - if (entry[field] && !item.fields[field]) { - item.fields[field] = entry[field] - item.documentation += `${field}: ${item.fields[field]}\n` - } - }) - this.bibEntries[file].push(item); - }) - this.extension.log(`Parsed ${this.bibEntries[file].length} bib entries from ${file}.`); - } - - parseBibtexFile(file: string) { - const fields: string[] = (vscode.workspace.getConfiguration('PandocCiter').get('CitationFormat') as string[]).map(f => { return f.toLowerCase(); }); - this.extension.log(`Parsing .bib entries from ${file}`); - this.bibEntries[file] = []; - const bibtex = fs.readFileSync(file).toString(); - const ast = bibtexParser.parse(bibtex); - ast.content - .filter(bibtexParser.isEntry) - .forEach((entry: bibtexParser.Entry) => { - if (entry.internalKey === undefined) { - return; - } - const item: Suggestion = { - key: entry.internalKey, - label: entry.internalKey, - file, - position: new vscode.Position(entry.location.start.line - 1, entry.location.start.column - 1), - kind: vscode.CompletionItemKind.Reference, - documentation: '', - fields: {} - }; - fields.forEach(field => { - const fieldcontents = entry.content.filter(e => e.name === field) - if (fieldcontents.length > 0) { - const fieldcontent = fieldcontents[0] - const value = Array.isArray(fieldcontent.value.content) ? - fieldcontent.value.content.join(' ') : this.deParenthesis(fieldcontent.value.content); - item.fields[fieldcontent.name] = value; - item.documentation += `${fieldcontent.name.charAt(0).toUpperCase() + fieldcontent.name.slice(1)}: ${value}\n`; - } - }) - this.bibEntries[file].push(item); - }); - this.extension.log(`Parsed ${this.bibEntries[file].length} bib entries from ${file}.`); - } + ) + .then((selected) => { + if (!selected) { + return; + } + if (vscode.window.activeTextEditor) { + const editor = vscode.window.activeTextEditor; + const content = editor.document.getText( + new vscode.Range(new vscode.Position(0, 0), editor.selection.start) + ); + let start = editor.selection.start; + if (content.lastIndexOf("\\cite") > content.lastIndexOf("}")) { + const curlyStart = content.lastIndexOf("{") + 1; + const commaStart = content.lastIndexOf(",") + 1; + start = editor.document.positionAt( + curlyStart > commaStart ? curlyStart : commaStart + ); + } + editor + .edit((edit) => + edit.replace( + new vscode.Range(start, editor.selection.start), + selected.description || "" + ) + ) + .then( + () => + (editor.selection = new vscode.Selection( + editor.selection.end, + editor.selection.end + )) + ); + } + }); + } - getEntry(key: string): Suggestion | undefined { - const suggestions = this.updateAll() - const entry = suggestions.find((elm) => elm.key === key) - return entry + parseBibFile(file: string) { + const ext = path.extname(file); + if (ext == ".bib") { + this.parseBibtexFile(file); + } else if (ext == ".json") { + this.parseBibjsonFile(file); + } else { + this.extension.log(`Unknown file extension force: ${file}`); } + } - private deParenthesis(str: string) { - return str.replace(/{+([^\\{}]+)}+/g, '$1'); - } + parseBibjsonFile(file: string) { + const fields: string[] = ( + vscode.workspace + .getConfiguration("PandocCiter") + .get("CitationFormat") as string[] + ).map((f) => { + return f.toLowerCase(); + }); + this.extension.log(`Parsing .bib entries from ${file}`); + this.bibEntries[file] = []; + let json = JSON.parse(fs.readFileSync(file, "utf-8")); + json.forEach((entry) => { + const item: Suggestion = { + key: entry.id, + label: entry.id, + file, + position: new vscode.Position(0, 0), + kind: vscode.CompletionItemKind.Reference, + documentation: "", + fields: {}, + }; + if (entry.author) { + entry.author.forEach((element) => { + if (item.fields.author) { + item.fields.author += " and "; + } else { + item.fields.author = ""; + } + item.fields.author += Object.values(element).join(", "); + }); + item.documentation += `author: ${item.fields.author}\n`; + } else if (entry.editor) { + entry.editor.forEach((element) => { + if (item.fields.editor) { + item.fields.editor += " and "; + } else { + item.fields.editor = ""; + } + item.fields.editor += Object.values(element).join(", "); + }); + item.documentation += `editor: ${item.fields.editor}\n`; + } + if (entry.issued) { + item.documentation += `date: ${Object.values(entry.issued).join()}\n`; + } + if (entry.DOI) { + item.documentation += `link: https://doi.org/${entry.DOI}\n`; + } else if (entry.URL) { + item.documentation += `link: ${entry.URL}\n`; + } + fields.forEach((field) => { + if (entry[field] && !item.fields[field]) { + item.fields[field] = entry[field]; + item.documentation += `${field}: ${item.fields[field]}\n`; + } + }); + this.bibEntries[file].push(item); + }); + this.extension.log( + `Parsed ${this.bibEntries[file].length} bib entries from ${file}.` + ); + } - private updateAll(bibFiles?: string[]): Suggestion[] { - let suggestions: Suggestion[] = []; - // From bib files - if (bibFiles === undefined) { - bibFiles = Object.keys(this.bibEntries); + parseBibtexFile(file: string) { + const fields: string[] = ( + vscode.workspace + .getConfiguration("PandocCiter") + .get("CitationFormat") as string[] + ).map((f) => { + return f.toLowerCase(); + }); + this.extension.log(`Parsing .bib entries from ${file}`); + this.bibEntries[file] = []; + const bibtex = fs.readFileSync(file).toString(); + const ast = bibtexParser.parse(bibtex); + ast.content + .filter(bibtexParser.isEntry) + .forEach((entry: bibtexParser.Entry) => { + if (entry.internalKey === undefined) { + return; } - bibFiles.forEach(file => { - suggestions = suggestions.concat(this.bibEntries[file]); + const item: Suggestion = { + key: entry.internalKey, + label: entry.internalKey, + file, + position: new vscode.Position( + entry.location.start.line - 1, + entry.location.start.column - 1 + ), + kind: vscode.CompletionItemKind.Reference, + documentation: "", + fields: {}, + }; + fields.forEach((field) => { + const fieldcontents = entry.content.filter((e) => e.name === field); + if (fieldcontents.length > 0) { + const fieldcontent = fieldcontents[0]; + const value = Array.isArray(fieldcontent.value.content) + ? fieldcontent.value.content.join(" ") + : this.deParenthesis(fieldcontent.value.content); + item.fields[fieldcontent.name] = value; + item.documentation += `${ + fieldcontent.name.charAt(0).toUpperCase() + + fieldcontent.name.slice(1) + }: ${value}\n`; + } }); - this.checkForDuplicates(suggestions); - return suggestions; - } + this.bibEntries[file].push(item); + }); + this.extension.log( + `Parsed ${this.bibEntries[file].length} bib entries from ${file}.` + ); + } - checkForDuplicates(items: Suggestion[]) { - const allKeys = (items.map(items => items.key)); - if ((new Set(allKeys)).size !== allKeys.length) { - // Code from: https://stackoverflow.com/questions/840781/get-all-non-unique-values-i-e-duplicate-more-than-one-occurrence-in-an-array - const count = keys => - keys.reduce((a, b) => - Object.assign(a, {[b]: (a[b] || 0) + 1}), {}); - const duplicates = dict => - Object.keys(dict).filter((a) => dict[a] > 1); - vscode.window.showInformationMessage(`Duplicate key(s): ${duplicates(count(allKeys))}`); - } + getEntry(key: string): Suggestion | undefined { + const suggestions = this.updateAll(); + const entry = suggestions.find((elm) => elm.key === key); + return entry; + } + + private deParenthesis(str: string) { + return str.replace(/{+([^\\{}]+)}+/g, "$1"); + } + + private updateAll(bibFiles?: string[]): Suggestion[] { + let suggestions: Suggestion[] = []; + // From bib files + if (bibFiles === undefined) { + bibFiles = Object.keys(this.bibEntries); } + bibFiles.forEach((file) => { + suggestions = suggestions.concat(this.bibEntries[file]); + }); + this.checkForDuplicates(suggestions); + return suggestions; + } - forgetParsedBibItems(bibPath: string) { - this.extension.log(`Forgetting parsed bib entries for ${bibPath}`); - delete this.bibEntries[bibPath]; + checkForDuplicates(items: Suggestion[]) { + const allKeys = items.map((items) => items.key); + if (new Set(allKeys).size !== allKeys.length) { + // Code from: https://stackoverflow.com/questions/840781/get-all-non-unique-values-i-e-duplicate-more-than-one-occurrence-in-an-array + const count = (keys) => + keys.reduce((a, b) => Object.assign(a, { [b]: (a[b] || 0) + 1 }), {}); + const duplicates = (dict) => Object.keys(dict).filter((a) => dict[a] > 1); + vscode.window.showInformationMessage( + `Duplicate key(s): ${duplicates(count(allKeys))}` + ); } + } + + forgetParsedBibItems(bibPath: string) { + this.extension.log(`Forgetting parsed bib entries for ${bibPath}`); + delete this.bibEntries[bibPath]; + } } diff --git a/src/providers/completer/crossref.ts b/src/providers/completer/crossref.ts index 4af1815..b35488d 100644 --- a/src/providers/completer/crossref.ts +++ b/src/providers/completer/crossref.ts @@ -20,245 +20,243 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import { dirname, join } from 'path'; -import * as vscode from 'vscode'; -import { Extension } from '../../extension'; +import { dirname, join } from "path"; +import * as vscode from "vscode"; +import { Extension } from "../../extension"; const foreSearchChars = 800; const tblPrintLines = 3; interface ParseContext { - lineMatch: RegExpExecArray; - type: string; - label: string; - doc: vscode.TextDocument; + lineMatch: RegExpExecArray; + type: string; + label: string; + doc: vscode.TextDocument; } export class Crossref { - extension: Extension; - lineRegex: RegExp; - regexes: { - fig: RegExp; - eq: RegExp; - sec: RegExp; - tbl: RegExp; - lst: RegExp; - }; + extension: Extension; + lineRegex: RegExp; + regexes: { + fig: RegExp; + eq: RegExp; + sec: RegExp; + tbl: RegExp; + lst: RegExp; + }; - constructor(extension: Extension) { - this.extension = extension; - this.lineRegex = /.*\{.*#(fig|tbl|eq|sec|lst):(\w+).*\}.*/g; - this.regexes = { - fig: /!\[(.*)\]\((.+)\)\{/, - eq: /\$\$\s*\{/, - sec: /#*\s*(.*)\s*\{/, - tbl: /:\s*(.*)\s*\{/, - lst: /```.*\{/, - }; - } + constructor(extension: Extension) { + this.extension = extension; + this.lineRegex = /.*\{.*#(fig|tbl|eq|sec|lst):(\w+).*\}.*/g; + this.regexes = { + fig: /!\[(.*)\]\((.+)\)\{/, + eq: /\$\$\s*\{/, + sec: /#*\s*(.*)\s*\{/, + tbl: /:\s*(.*)\s*\{/, + lst: /```.*\{/, + }; + } - private parseFig(ctx: ParseContext): vscode.CompletionItem { - let match = this.regexes.fig.exec(ctx.lineMatch.toString()); - if (!match) return null; - const title = match[1]; - const documentation = new vscode.MarkdownString( - `![](${join(dirname(ctx.doc.fileName), match[2])}|"width=300")` - ); - return { - label: `${ctx.type}:${ctx.label}`, - detail: title, - documentation, - kind: vscode.CompletionItemKind.Reference, - }; - } + private parseFig(ctx: ParseContext): vscode.CompletionItem { + let match = this.regexes.fig.exec(ctx.lineMatch.toString()); + if (!match) return null; + const title = match[1]; + const documentation = new vscode.MarkdownString( + `![](${join(dirname(ctx.doc.fileName), match[2])}|"width=300")` + ); + return { + label: `${ctx.type}:${ctx.label}`, + detail: title, + documentation, + kind: vscode.CompletionItemKind.Reference, + }; + } - private parseSec(ctx: ParseContext): vscode.CompletionItem { - let match = this.regexes.sec.exec(ctx.lineMatch.toString()); - if (!match) return null; - const title = match[1]; - return { - label: `${ctx.type}:${ctx.label}`, - detail: title, - kind: vscode.CompletionItemKind.Reference, - }; - } + private parseSec(ctx: ParseContext): vscode.CompletionItem { + let match = this.regexes.sec.exec(ctx.lineMatch.toString()); + if (!match) return null; + const title = match[1]; + return { + label: `${ctx.type}:${ctx.label}`, + detail: title, + kind: vscode.CompletionItemKind.Reference, + }; + } - private parseTbl(ctx: ParseContext): vscode.CompletionItem { - let match = this.regexes.tbl.exec(ctx.lineMatch.toString()); - if (!match) return null; - const title = match[1]; - let documentation = new vscode.MarkdownString(''); - const doc = ctx.doc.getText(); - const searchStart = Math.max(ctx.lineMatch.index - foreSearchChars, 0); - const sig = doc - .substring(searchStart, ctx.lineMatch.index) - .lastIndexOf('|:-'); - if (sig > 0) { - const begin = doc.lastIndexOf('\n\n', sig + searchStart); - let cursor = begin; - // +2 = with header & splitter - for (let i = 0; i < tblPrintLines + 2; i++) { - const pos = doc.indexOf('\n', cursor + 1); - if (pos < 0) break; - else cursor = pos; - } - documentation = new vscode.MarkdownString( - doc.substring(begin + 2, cursor) - ); - } - return { - label: `${ctx.type}:${ctx.label}`, - detail: title, - documentation, - kind: vscode.CompletionItemKind.Reference, - }; + private parseTbl(ctx: ParseContext): vscode.CompletionItem { + let match = this.regexes.tbl.exec(ctx.lineMatch.toString()); + if (!match) return null; + const title = match[1]; + let documentation = new vscode.MarkdownString(""); + const doc = ctx.doc.getText(); + const searchStart = Math.max(ctx.lineMatch.index - foreSearchChars, 0); + const sig = doc + .substring(searchStart, ctx.lineMatch.index) + .lastIndexOf("|:-"); + if (sig > 0) { + const begin = doc.lastIndexOf("\n\n", sig + searchStart); + let cursor = begin; + // +2 = with header & splitter + for (let i = 0; i < tblPrintLines + 2; i++) { + const pos = doc.indexOf("\n", cursor + 1); + if (pos < 0) break; + else cursor = pos; + } + documentation = new vscode.MarkdownString( + doc.substring(begin + 2, cursor) + ); } + return { + label: `${ctx.type}:${ctx.label}`, + detail: title, + documentation, + kind: vscode.CompletionItemKind.Reference, + }; + } - private parseLstNormalMatch( - ctx: ParseContext, - _: RegExpExecArray - ): vscode.CompletionItem { - const doc = ctx.doc.getText(); - const end = doc.indexOf("```", ctx.lineMatch.index + 1); + private parseLstNormalMatch( + ctx: ParseContext, + _: RegExpExecArray + ): vscode.CompletionItem { + const doc = ctx.doc.getText(); + const end = doc.indexOf("```", ctx.lineMatch.index + 1); - // try to parse language - const langMatch = /\{.*\.(\w+).*\}/.exec(ctx.lineMatch.toString()); - const lang = langMatch ? langMatch[1] : ""; + // try to parse language + const langMatch = /\{.*\.(\w+).*\}/.exec(ctx.lineMatch.toString()); + const lang = langMatch ? langMatch[1] : ""; - // try to parse caption - const titleMatch = /\{.*caption="(.+)".*\}/.exec(ctx.lineMatch.toString()); - let title = titleMatch ? titleMatch[1] : ctx.label; - let documentation = new vscode.MarkdownString(""); + // try to parse caption + const titleMatch = /\{.*caption="(.+)".*\}/.exec(ctx.lineMatch.toString()); + let title = titleMatch ? titleMatch[1] : ctx.label; + let documentation = new vscode.MarkdownString(""); - if (end > 0) { - documentation = new vscode.MarkdownString( - "``` " + - lang + - " " + - doc.substring(ctx.lineMatch.index + ctx.lineMatch[0].length, end) + - "\n```" - ); - } - return { - label: `${ctx.type}:${ctx.label}`, - documentation, - detail: title, - kind: vscode.CompletionItemKind.Reference, - }; + if (end > 0) { + documentation = new vscode.MarkdownString( + "``` " + + lang + + " " + + doc.substring(ctx.lineMatch.index + ctx.lineMatch[0].length, end) + + "\n```" + ); } + return { + label: `${ctx.type}:${ctx.label}`, + documentation, + detail: title, + kind: vscode.CompletionItemKind.Reference, + }; + } - private parseLstTableMatch( - ctx: ParseContext, - match: RegExpExecArray - ): vscode.CompletionItem { - const doc = ctx.doc.getText(); - const searchStart = Math.max(ctx.lineMatch.index - foreSearchChars, 0); - - let documentation = new vscode.MarkdownString(""); - const title = match[1]; + private parseLstTableMatch( + ctx: ParseContext, + match: RegExpExecArray + ): vscode.CompletionItem { + const doc = ctx.doc.getText(); + const searchStart = Math.max(ctx.lineMatch.index - foreSearchChars, 0); - const sig = doc - .substring(searchStart, ctx.lineMatch.index) - .lastIndexOf("```"); - if (sig > 0) { - const begin = doc.lastIndexOf("\n\n", sig + searchStart); - let cursor = begin; - // +2 = with header & splitter - for (let i = 0; i < tblPrintLines + 2; i++) { - const pos = doc.indexOf("\n", cursor + 1); - if (pos < 0) break; - else cursor = pos; - } - documentation = new vscode.MarkdownString( - doc.substring(begin + 2, cursor) - ); - } + let documentation = new vscode.MarkdownString(""); + const title = match[1]; - return { - label: `${ctx.type}:${ctx.label}`, - documentation: documentation, - detail: title, - kind: vscode.CompletionItemKind.Reference, - }; + const sig = doc + .substring(searchStart, ctx.lineMatch.index) + .lastIndexOf("```"); + if (sig > 0) { + const begin = doc.lastIndexOf("\n\n", sig + searchStart); + let cursor = begin; + // +2 = with header & splitter + for (let i = 0; i < tblPrintLines + 2; i++) { + const pos = doc.indexOf("\n", cursor + 1); + if (pos < 0) break; + else cursor = pos; + } + documentation = new vscode.MarkdownString( + doc.substring(begin + 2, cursor) + ); } - private parseLst(ctx: ParseContext): vscode.CompletionItem { - const normalMatch = this.regexes.lst.exec(ctx.lineMatch.toString()); - if (normalMatch) { - return this.parseLstNormalMatch(ctx, normalMatch); - } - const tableMatch = this.regexes.tbl.exec(ctx.lineMatch.toString()); - if (tableMatch) { - return this.parseLstTableMatch(ctx, tableMatch); - } - return null; + return { + label: `${ctx.type}:${ctx.label}`, + documentation: documentation, + detail: title, + kind: vscode.CompletionItemKind.Reference, + }; + } + + private parseLst(ctx: ParseContext): vscode.CompletionItem { + const normalMatch = this.regexes.lst.exec(ctx.lineMatch.toString()); + if (normalMatch) { + return this.parseLstNormalMatch(ctx, normalMatch); + } + const tableMatch = this.regexes.tbl.exec(ctx.lineMatch.toString()); + if (tableMatch) { + return this.parseLstTableMatch(ctx, tableMatch); } + return null; + } - private parseEq(ctx: ParseContext): vscode.CompletionItem { - let match = this.regexes.eq.exec(ctx.lineMatch.toString()); - if (!match) return null; - const doc = ctx.doc.getText(); - const matchIndex = ctx.lineMatch.index + match.index; - const searchStart = Math.max(matchIndex - foreSearchChars, 0); - const begin = - doc.substring(searchStart, matchIndex).lastIndexOf('$$') + - searchStart; + private parseEq(ctx: ParseContext): vscode.CompletionItem { + let match = this.regexes.eq.exec(ctx.lineMatch.toString()); + if (!match) return null; + const doc = ctx.doc.getText(); + const matchIndex = ctx.lineMatch.index + match.index; + const searchStart = Math.max(matchIndex - foreSearchChars, 0); + const begin = + doc.substring(searchStart, matchIndex).lastIndexOf("$$") + searchStart; - if (begin < 0) { - return null; - } - return { - label: `${ctx.type}:${ctx.label}`, - detail: ctx.label, - documentation: new vscode.MarkdownString(doc.substring(begin + 2, matchIndex)), - kind: vscode.CompletionItemKind.Reference, - }; + if (begin < 0) { + return null; } + return { + label: `${ctx.type}:${ctx.label}`, + detail: ctx.label, + documentation: new vscode.MarkdownString( + doc.substring(begin + 2, matchIndex) + ), + kind: vscode.CompletionItemKind.Reference, + }; + } - provide(args?: { document: vscode.TextDocument }): vscode.CompletionItem[] { - const mode: string = vscode.workspace.getConfiguration('PandocCiter').get( - 'CrossRefMode', - 'full' - ); - if (mode === 'none') return []; + provide(args?: { document: vscode.TextDocument }): vscode.CompletionItem[] { + const mode: string = vscode.workspace + .getConfiguration("PandocCiter") + .get("CrossRefMode", "full"); + if (mode === "none") return []; - const targets = []; - let match: RegExpExecArray | null; - const parsers = { - fig: this.parseFig.bind(this), - sec: this.parseSec.bind(this), - tbl: this.parseTbl.bind(this), - lst: this.parseLst.bind(this), - eq: this.parseEq.bind(this), - }; - while ( - (match = this.lineRegex.exec(args.document.getText())) !== null - ) { - const type = match[1]; - const label = match[2]; - switch (mode) { - case 'full': - const parser = parsers[type]; - if (parser) { - let item = parser({ - lineMatch: match, - type, - label, - doc: args.document, - }); - if (item) { - targets.push(item); - } - } - break; - case 'minimal': - targets.push({ - label: `${match[1]}:${match[2]}`, - kind: vscode.CompletionItemKind.Reference, - }); - break; + const targets = []; + let match: RegExpExecArray | null; + const parsers = { + fig: this.parseFig.bind(this), + sec: this.parseSec.bind(this), + tbl: this.parseTbl.bind(this), + lst: this.parseLst.bind(this), + eq: this.parseEq.bind(this), + }; + while ((match = this.lineRegex.exec(args.document.getText())) !== null) { + const type = match[1]; + const label = match[2]; + switch (mode) { + case "full": + const parser = parsers[type]; + if (parser) { + let item = parser({ + lineMatch: match, + type, + label, + doc: args.document, + }); + if (item) { + targets.push(item); } - } - return targets; + } + break; + case "minimal": + targets.push({ + label: `${match[1]}:${match[2]}`, + kind: vscode.CompletionItemKind.Reference, + }); + break; + } } + return targets; + } } diff --git a/src/providers/completion.ts b/src/providers/completion.ts index 56d1375..fe54dd2 100644 --- a/src/providers/completion.ts +++ b/src/providers/completion.ts @@ -24,58 +24,71 @@ // SOFTWARE. ////////////////////////////////////////////////////////////////////////////////////////// -import * as vscode from 'vscode'; +import * as vscode from "vscode"; -import {Extension} from '../extension'; -import {Citation} from './completer/citation'; -import { Crossref } from './completer/crossref'; +import { Extension } from "../extension"; +import { Citation } from "./completer/citation"; +import { Crossref } from "./completer/crossref"; export class Completer implements vscode.CompletionItemProvider { - extension: Extension; - citation: Citation; - crossref: Crossref; + extension: Extension; + citation: Citation; + crossref: Crossref; - constructor(extension: Extension) { - this.extension = extension; - this.citation = new Citation(extension); - this.crossref = new Crossref(extension); - } + constructor(extension: Extension) { + this.extension = extension; + this.citation = new Citation(extension); + this.crossref = new Crossref(extension); + } - provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken, context:vscode.CompletionContext) : - vscode.CompletionItem[] | undefined { - // This was used to terminate unecessary autompletion early, but might be causing suggestions to not appear for some users - // const invoker = document.lineAt(position.line).text[position.character-1]; - // if (invoker !== '@') {return;} - const line = document.lineAt(position.line).text.substring(0, position.character).trim().split(" "); - const trigger = line[line.length-1]; - const suggestions = this.completion(trigger).concat(this.completionCrossref(trigger, document)); - this.extension.log(`Showing ${suggestions.length} suggestions`); - if (suggestions.length > 0) { - const configuration = vscode.workspace.getConfiguration('PandocCiter'); - if ((configuration.get('ViewType') as string) === 'browser') { - setTimeout(() => this.citation.browser(), 10); - return; - } - // current crossref do not support browser mode - return suggestions; - } + provideCompletionItems( + document: vscode.TextDocument, + position: vscode.Position, + _token: vscode.CancellationToken, + context: vscode.CompletionContext + ): vscode.CompletionItem[] | undefined { + // This was used to terminate unecessary autompletion early, but might be causing suggestions to not appear for some users + // const invoker = document.lineAt(position.line).text[position.character-1]; + // if (invoker !== '@') {return;} + const line = document + .lineAt(position.line) + .text.substring(0, position.character) + .trim() + .split(" "); + const trigger = line[line.length - 1]; + const suggestions = this.completion(trigger).concat( + this.completionCrossref(trigger, document) + ); + this.extension.log(`Showing ${suggestions.length} suggestions`); + if (suggestions.length > 0) { + const configuration = vscode.workspace.getConfiguration("PandocCiter"); + if ((configuration.get("ViewType") as string) === "browser") { + setTimeout(() => this.citation.browser(), 10); return; + } + // current crossref do not support browser mode + return suggestions; } + return; + } - completion(line: string) : vscode.CompletionItem[] { - let reg = /(?:^|[ ;\[-])\@([^\]\s]*)/; - let provider = this.citation; - - const result = line.match(reg); - let suggestions: vscode.CompletionItem[] = []; - if (result) { - suggestions = provider.provide(); - } + completion(line: string): vscode.CompletionItem[] { + let reg = /(?:^|[ ;\[-])\@([^\]\s]*)/; + let provider = this.citation; - return suggestions; + const result = line.match(reg); + let suggestions: vscode.CompletionItem[] = []; + if (result) { + suggestions = provider.provide(); } - completionCrossref(line: string, doc: vscode.TextDocument) : vscode.CompletionItem[] { - return this.crossref.provide({document: doc}); - } + return suggestions; + } + + completionCrossref( + line: string, + doc: vscode.TextDocument + ): vscode.CompletionItem[] { + return this.crossref.provide({ document: doc }); + } } diff --git a/src/providers/definition.ts b/src/providers/definition.ts index fa9f3cb..a45412e 100644 --- a/src/providers/definition.ts +++ b/src/providers/definition.ts @@ -1,22 +1,28 @@ -import * as vscode from 'vscode'; -import { Extension } from '../extension'; +import * as vscode from "vscode"; +import { Extension } from "../extension"; export class DefinitionProvider implements vscode.DefinitionProvider { - extension: Extension; + extension: Extension; - constructor(extension: Extension) { - this.extension = extension - } + constructor(extension: Extension) { + this.extension = extension; + } - provideDefinition(document: vscode.TextDocument, position: vscode.Position): vscode.Location | undefined { - // a cite key is an @ symbol followed by word characters (letters or numbers) - const keyRange = document.getWordRangeAtPosition(position, /(?<=@)[\w\p{L}\p{M}]+/u); - if (keyRange){ - const citeKey = document.getText(keyRange); - const cite = this.extension.completer.citation.getEntry(citeKey); - if (cite) { - return new vscode.Location(vscode.Uri.file(cite.file), cite.position); - } - } - } + provideDefinition( + document: vscode.TextDocument, + position: vscode.Position + ): vscode.Location | undefined { + // a cite key is an @ symbol followed by word characters (letters or numbers) + const keyRange = document.getWordRangeAtPosition( + position, + /(?<=@)[\w\p{L}\p{M}]+/u + ); + if (keyRange) { + const citeKey = document.getText(keyRange); + const cite = this.extension.completer.citation.getEntry(citeKey); + if (cite) { + return new vscode.Location(vscode.Uri.file(cite.file), cite.position); + } + } + } } diff --git a/src/providers/hover.ts b/src/providers/hover.ts index 5731790..a17508b 100644 --- a/src/providers/hover.ts +++ b/src/providers/hover.ts @@ -1,26 +1,33 @@ -import * as vscode from 'vscode' -import { Extension } from '../extension'; +import * as vscode from "vscode"; +import { Extension } from "../extension"; export class HoverProvider implements vscode.HoverProvider { - extension: Extension; + extension: Extension; - constructor(extension: Extension) { - this.extension = extension; - } + constructor(extension: Extension) { + this.extension = extension; + } - public async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - // a cite key is an @ symbol followed by word characters (letters or numbers) - const keyRange = document.getWordRangeAtPosition(position, /(?<=@)[\w\p{L}\p{M}]+/u); - if (keyRange){ - const citeKey = document.getText(keyRange); - const cite = this.extension.completer.citation.getEntry(citeKey); - if (cite) { - let hoverMarkdownText = cite.documentation || cite.detail; - hoverMarkdownText = hoverMarkdownText.replace(/\n/g, ' \n'); // need double space then a newline to actually get a newline in markdown - if (hoverMarkdownText) { - return new vscode.Hover(hoverMarkdownText) - } - } - } - } + public async provideHover( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): Promise { + // a cite key is an @ symbol followed by word characters (letters or numbers) + const keyRange = document.getWordRangeAtPosition( + position, + /(?<=@)[\w\p{L}\p{M}]+/u + ); + if (keyRange) { + const citeKey = document.getText(keyRange); + const cite = this.extension.completer.citation.getEntry(citeKey); + if (cite) { + let hoverMarkdownText = cite.documentation || cite.detail; + hoverMarkdownText = hoverMarkdownText.replace(/\n/g, " \n"); // need double space then a newline to actually get a newline in markdown + if (hoverMarkdownText) { + return new vscode.Hover(hoverMarkdownText); + } + } + } + } } diff --git a/syntaxes/citations.json b/syntaxes/citations.json index a735916..a0f54a8 100644 --- a/syntaxes/citations.json +++ b/syntaxes/citations.json @@ -1,31 +1,31 @@ { - "injectionSelector": ["L:text.html.markdown", "L:source.pweave.md"], - "patterns": [ - { - "include": "#pandoc-citationA" - }, - { - "include": "#pandoc-citationB" - } - ], - "repository": { - "pandoc-citationA":{ - "match": "(?<=^|\\s|\\[)(-?@(.*?))(?=$|[\\s\\r\\n\\]{},~#%\\\\'\"=\\(\\)])", - "name": "string.other.link.description.markdown.citation" - }, - "pandoc-citationB":{ - "captures": { - "2": { - "name": "string.other.link.description.markdown.citation" - } - }, - "match": "(\\[)(-?@(.*?))(?=$|[\\s\\r\\n\\]{},~#%\\\\'\"=\\(\\)])", - "name": "meta.paragraph.markdown" - }, - "pandoc-crossref":{ - "match": "\\@(fig|tbl|sec|eq|lst):\\w+", - "name": "string.other.link.description.markdown.citation" + "injectionSelector": ["L:text.html.markdown", "L:source.pweave.md"], + "patterns": [ + { + "include": "#pandoc-citationA" + }, + { + "include": "#pandoc-citationB" + } + ], + "repository": { + "pandoc-citationA": { + "match": "(?<=^|\\s|\\[)(-?@(.*?))(?=$|[\\s\\r\\n\\]{},~#%\\\\'\"=\\(\\)])", + "name": "string.other.link.description.markdown.citation" + }, + "pandoc-citationB": { + "captures": { + "2": { + "name": "string.other.link.description.markdown.citation" } + }, + "match": "(\\[)(-?@(.*?))(?=$|[\\s\\r\\n\\]{},~#%\\\\'\"=\\(\\)])", + "name": "meta.paragraph.markdown" }, - "scopeName": "pandoc-citation" + "pandoc-crossref": { + "match": "\\@(fig|tbl|sec|eq|lst):\\w+", + "name": "string.other.link.description.markdown.citation" + } + }, + "scopeName": "pandoc-citation" } diff --git a/tsconfig.json b/tsconfig.json index 8a1d847..fab3dad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,11 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "outDir": "out", - "lib": [ - "es6" - ], - "sourceMap": true, - "rootDir": "src" - }, - "exclude": [ - "node_modules", - ".vscode-test" - ] -} \ No newline at end of file + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": ["es6"], + "sourceMap": true, + "rootDir": "src" + }, + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/tslint.json b/tslint.json index 2bd680d..2407102 100644 --- a/tslint.json +++ b/tslint.json @@ -1,15 +1,12 @@ { - "rules": { - "no-string-throw": true, - "no-unused-expression": true, - "no-duplicate-variable": true, - "curly": true, - "class-name": true, - "semicolon": [ - true, - "always" - ], - "triple-equals": true - }, - "defaultSeverity": "warning" -} \ No newline at end of file + "rules": { + "no-string-throw": true, + "no-unused-expression": true, + "no-duplicate-variable": true, + "curly": true, + "class-name": true, + "semicolon": [true, "always"], + "triple-equals": true + }, + "defaultSeverity": "warning" +}