diff --git a/data/bibtex-entries.json b/data/bibtex-entries.json index 2d6959ad3..68f1c6cc7 100644 --- a/data/bibtex-entries.json +++ b/data/bibtex-entries.json @@ -18,6 +18,13 @@ "title", "author" ], + "conference": [ + "author", + "title", + "booktitle", + "editor", + "year" + ], "inbook": [ "author", "editor", diff --git a/data/bibtex-optional-entries.json b/data/bibtex-optional-entries.json new file mode 100644 index 000000000..e742b6470 --- /dev/null +++ b/data/bibtex-optional-entries.json @@ -0,0 +1,392 @@ +{ + "article": [ + "addendum", + "annotator", + "commentator", + "doi", + "editor", + "eid", + "eprint", + "eprintclass", + "eprinttype", + "issn", + "issue", + "issuesubtitle", + "issuetitle", + "journalsubtitle", + "language", + "month", + "note", + "number", + "origlanguage", + "pages", + "pubstate", + "series", + "subtitle", + "titleaddon", + "translator", + "url", + "urldate", + "version", + "volume" + ], + "book": [ + "addendum", + "afterword", + "annotator", + "chapter", + "commentator", + "doi", + "edition", + "editor", + "eprint", + "eprintclass", + "eprinttype", + "foreword", + "introduction", + "isbn", + "language", + "location", + "mainsubtitle", + "maintitle", + "maintitleaddon", + "month", + "note", + "number", + "origlanguage", + "pages", + "pagetotal", + "part", + "publisher", + "pubstate", + "series", + "subtitle", + "titleaddon", + "translator", + "url", + "urldate", + "volume", + "volumes" + ], + "booklet": [ + "addendum", + "adress", + "chapter", + "doi", + "eprint", + "eprintclass", + "eprinttype", + "howpublished", + "language", + "location", + "month", + "note", + "pages", + "pagetotal", + "pubstate", + "subtitle", + "titleaddon", + "type", + "url", + "urldate" + ], + "conference": [ + "addendum", + "address", + "booksubtitle", + "booktitleaddon", + "chapter", + "doi", + "editor", + "eprint", + "eprintclass", + "eprinttype", + "eventdate", + "eventtitle", + "eventtitleaddon", + "isbn", + "language", + "location", + "mainsubtitle", + "maintitle", + "maintitleaddon", + "month", + "note", + "note.", + "number", + "organization", + "pages", + "part", + "publisher", + "pubstate", + "series", + "subtitle", + "titleaddon", + "url", + "urldate", + "venue", + "volume", + "volumes" + ], + "inbook": [ + "addendum", + "afterword", + "annotator", + "bookauthor", + "booksubtitle", + "booktitleaddon", + "chapter", + "commentator", + "doi", + "edition", + "editor", + "eprint", + "eprintclass", + "eprinttype", + "foreword", + "introduction", + "isbn", + "language", + "location", + "mainsubtitle", + "maintitle", + "maintitleaddon", + "month", + "note", + "number", + "origlanguage", + "pages", + "part", + "publisher", + "pubstate", + "series", + "subtitle", + "titleaddon", + "translator", + "type", + "url", + "urldate", + "volume", + "volumes" + ], + "incollection": [ + "addendum", + "address", + "afterword", + "annotator", + "booksubtitle", + "booktitleaddon", + "chapter", + "commentator", + "doi", + "edition", + "editor", + "eprint", + "eprintclass", + "eprinttype", + "foreword", + "introduction", + "isbn", + "language", + "location", + "mainsubtitle", + "maintitle", + "maintitleaddon", + "month", + "note", + "number", + "origlanguage", + "pages", + "part", + "publisher", + "pubstate", + "series", + "subtitle", + "titleaddon", + "translator", + "type", + "url", + "urldate", + "volume", + "volumes" + ], + "inproceedings": [ + "addendum", + "address", + "booksubtitle", + "booktitleaddon", + "chapter", + "doi", + "editor", + "eprint", + "eprintclass", + "eprinttype", + "eventdate", + "eventtitle", + "eventtitleaddon", + "isbn", + "language", + "location", + "mainsubtitle", + "maintitle", + "maintitleaddon", + "month", + "note", + "number", + "organization", + "pages", + "part", + "publisher", + "pubstate", + "series", + "subtitle", + "titleaddon", + "url", + "urldate", + "venue", + "volume", + "volumes" + ], + "manual": [ + "addendum", + "address", + "chapter", + "doi", + "edition", + "eprint", + "eprintclass", + "eprinttype", + "isbn", + "language", + "location", + "month", + "note", + "number", + "organization", + "pages", + "pagetotal", + "publisher", + "pubstate", + "series", + "subtitle", + "titleaddon", + "type", + "url", + "urldate", + "version", + "year" + ], + "masterthesis": [ + "address", + "month", + "note", + "type" + ], + "misc": [ + "addendum", + "doi", + "eprint", + "eprintclass", + "eprinttype", + "howpublished", + "language", + "location", + "month", + "note", + "organization", + "pubstate", + "subtitle", + "titleaddon", + "type", + "url", + "urldate", + "version", + "year" + ], + "phdthesis": [ + "addendum", + "address", + "chapter", + "doi", + "eprint", + "eprintclass", + "eprinttype", + "isbn", + "language", + "location", + "month", + "note", + "pages", + "pagetotal", + "pubstate", + "subtitle", + "titleaddon", + "type", + "url", + "urldate" + ], + "proceedings": [ + "addendum", + "address", + "chapter", + "doi", + "editor", + "eprint", + "eprintclass", + "eprinttype", + "eventdate", + "eventtitle", + "eventtitleaddon", + "isbn", + "language", + "location", + "mainsubtitle", + "maintitle", + "maintitleaddon", + "month", + "note", + "number", + "organization", + "pages", + "pagetotal", + "part", + "publisher", + "pubstate", + "series", + "subtitle", + "titleaddon", + "url", + "urldate", + "venue", + "volume", + "volumes" + ], + "techreport": [ + "addendum", + "address", + "chapter", + "doi", + "eprint", + "eprintclass", + "eprinttype", + "isrn", + "language", + "location", + "month", + "note", + "number", + "pages", + "pagetotal", + "pubstate", + "subtitle", + "titleaddon", + "type", + "url", + "urldate", + "version" + ], + "unpublished": [ + "month", + "year" + ] +} \ No newline at end of file diff --git a/src/providers/bibtexcompletion.ts b/src/providers/bibtexcompletion.ts index 4e8736897..03c6371fd 100644 --- a/src/providers/bibtexcompletion.ts +++ b/src/providers/bibtexcompletion.ts @@ -6,7 +6,8 @@ import {Extension} from '../main' export class BibtexCompleter implements vscode.CompletionItemProvider { extension: Extension - private entries: vscode.CompletionItem[] = [] + private entryItems: vscode.CompletionItem[] = [] + private optFieldItems: {[key: string]: vscode.CompletionItem[]} = {} constructor(extension: Extension) { this.extension = extension @@ -19,7 +20,8 @@ export class BibtexCompleter implements vscode.CompletionItemProvider { } loadDefaultItems() { - const defaultEntries = fs.readFileSync(`${this.extension.extensionRoot}/data/bibtex-entries.json`, {encoding: 'utf8'}) + const entries = JSON.parse(fs.readFileSync(`${this.extension.extensionRoot}/data/bibtex-entries.json`, {encoding: 'utf8'})) + const optFields = JSON.parse(fs.readFileSync(`${this.extension.extensionRoot}/data/bibtex-optional-entries.json`, {encoding: 'utf8'})) const entriesReplacements = vscode.workspace.getConfiguration('latex-workshop').get('intellisense.bibtexJSON.replace') as {[key: string]: string[]} const config = vscode.workspace.getConfiguration('latex-workshop') const leftright = config.get('bibtex-format.surround') === 'Curly braces' ? [ '{', '}' ] : [ '"', '"'] @@ -32,22 +34,42 @@ export class BibtexCompleter implements vscode.CompletionItemProvider { sort: config.get('bibtex-format.sortby') as string[] } - const entries = JSON.parse(defaultEntries) + const maxLengths: {[key: string]: number} = this.computeMaxLengths(entries, optFields) const entriesList: string[] = [] Object.keys(entries).forEach(entry => { if (entry in entriesList) { return } if (entry in entriesReplacements) { - this.entries.push(this.entryToCompletion(entry, entriesReplacements[entry], bibtexFormat)) + this.entryItems.push(this.entryToCompletion(entry, entriesReplacements[entry], bibtexFormat, maxLengths)) } else { - this.entries.push(this.entryToCompletion(entry, entries[entry], bibtexFormat)) + this.entryItems.push(this.entryToCompletion(entry, entries[entry], bibtexFormat, maxLengths)) } entriesList.push(entry) }) + Object.keys(optFields).forEach(entry => { + this.optFieldItems[entry] = this.fieldsToCompletion(entry, optFields[entry], bibtexFormat, maxLengths) + }) } - entryToCompletion(itemName: string, itemFields: string[], config: bibtexUtils.BibtexFormatConfig): vscode.CompletionItem { + computeMaxLengths(entries: {[key: string]: string[]}, optFields: {[key: string]: string[]}): {[key: string]: number} { + const maxLengths: {[key: string]: number} = {} + Object.keys(entries).forEach(key => { + let maxFieldLength = 0 + entries[key].forEach(field => { + maxFieldLength = Math.max(maxFieldLength, field.length) + }) + if (key in optFields) { + optFields[key].forEach(field => { + maxFieldLength = Math.max(maxFieldLength, field.length) + }) + } + maxLengths[key] = maxFieldLength + }) + return maxLengths + } + + entryToCompletion(itemName: string, itemFields: string[], config: bibtexUtils.BibtexFormatConfig, maxLengths: {[key: string]: number}): vscode.CompletionItem { const suggestion: vscode.CompletionItem = new vscode.CompletionItem(itemName, vscode.CompletionItemKind.Snippet) suggestion.detail = itemName suggestion.documentation = `Add a @${itemName} entry` @@ -55,15 +77,10 @@ export class BibtexCompleter implements vscode.CompletionItemProvider { // The following code is copied from bibtexutils.ts:bibtexFormat // Find the longest field name in entry - let maxFieldLength = 0 - itemFields.forEach(field => { - maxFieldLength = Math.max(maxFieldLength, field.length) - }) - let s: string = itemName + '{${0:key}' itemFields.forEach(field => { s += ',\n' + config.tab + (config.case === 'lowercase' ? field : field.toUpperCase()) - s += ' '.repeat(maxFieldLength - field.length) + ' = ' + s += ' '.repeat(maxLengths[itemName] - field.length) + ' = ' s += config.left + `$${count}` + config.right count++ }) @@ -72,15 +89,46 @@ export class BibtexCompleter implements vscode.CompletionItemProvider { return suggestion } + fieldsToCompletion(itemName: string, fields: string[], config: bibtexUtils.BibtexFormatConfig, maxLengths: {[key: string]: number}): vscode.CompletionItem[] { + const suggestions: vscode.CompletionItem[] = [] + fields.forEach(field => { + const suggestion: vscode.CompletionItem = new vscode.CompletionItem(field, vscode.CompletionItemKind.Snippet) + suggestion.detail = field + suggestion.documentation = `Add ${field} = ${config.left}${config.right}` + suggestion.insertText = new vscode.SnippetString(`${field}` + ' '.repeat(maxLengths[itemName] - field.length) + ` = ${config.left}$1${config.right},`) + suggestions.push(suggestion) + }) + return suggestions + } + provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken, _context: vscode.CompletionContext): Promise { return new Promise((resolve, _reject) => { const currentLine = document.lineAt(position.line).text + const prevLine = document.lineAt(position.line - 1).text if (currentLine.match(/@[a-zA-Z]*$/)) { - resolve(this.entries) + // Complete an entry name + resolve(this.entryItems) return + } else if (currentLine.match(/^\s*[a-zA-Z]*/) && prevLine.match(/(?:@[a-zA-Z]{)|(?:["}0-9],\s*$)/)) { + // Add optional fields + const optFields = this.provideOptFields(document, position) + resolve(optFields) } resolve() }) } + provideOptFields(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] { + const pattern = /^\s*@([a-zA-Z]+)\{(?:[^,]*,)?\s$/m + const content = document.getText(new vscode.Range(new vscode.Position(0, 0), position)) + const reversedContent = content.replace(/(\r\n)|\r/g, '\n').split('\n').reverse().join('\n') + const match = reversedContent.match(pattern) + if (match) { + const entryType = match[1].toLowerCase() + if (entryType in this.optFieldItems) { + return this.optFieldItems[entryType] + } + } + return [] + } }