Decoration and command
This commit is contained in:
parent
d3452351e4
commit
55c1c6e1ff
7 changed files with 246 additions and 57 deletions
16
CHANGELOG.md
Normal file
16
CHANGELOG.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Change Log
|
||||
|
||||
<!-- Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. -->
|
||||
|
||||
## [0.1.1]
|
||||
### Added
|
||||
|
||||
- Added a decorator which shows the links to Zotero on hovering the citerefs.
|
||||
- Added commands which can be accessed using the command palette and bound with a keybinding.
|
||||
- Configuration options to enable & disable CodeLens or Decorator
|
||||
|
||||
## [0.1.1]
|
||||
### Added
|
||||
- Initial release
|
||||
- CodeLens to open PDF's based on citekey
|
||||
- CodeLens to select item in Zotero based on citekey
|
49
package.json
49
package.json
|
@ -18,20 +18,59 @@
|
|||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage:markdown"
|
||||
"onLanguage:markdown",
|
||||
"onCommand:zoterolens.showInZotero",
|
||||
"onCommand:zoterolens.openZoteroPDF"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "zoterolens.showInZotero",
|
||||
"title": "Select item in Zotero by citeref"
|
||||
"title": "Open Zotero item by citeref"
|
||||
},
|
||||
{
|
||||
"command": "zoterolens.openZoteroPDF",
|
||||
"title": "Open PDF attached in Zotero by citeref."
|
||||
"title": "Open Zotero PDF attachment by citeref."
|
||||
}
|
||||
]
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "zoterolens.showInZotero",
|
||||
"when": "editorLangId == markdown"
|
||||
},
|
||||
{
|
||||
"command": "zoterolens.openZoteroPDF",
|
||||
"when": "editorLangId == markdown"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "ZoteroLens",
|
||||
"properties": {
|
||||
"zoterolens.lens.enabled": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"markdownDescription": "Show references in a 'lens', an additional line above the paragraph.",
|
||||
"examples": [
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"zoterolens.decoration.enabled": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"markdownDescription": "Show links to @cite-key references in a hover over the @cite-key.",
|
||||
"examples": [
|
||||
true,
|
||||
false
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
|
@ -54,4 +93,4 @@
|
|||
"mocha": "^9.1.3",
|
||||
"typescript": "^4.5.4"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,40 @@
|
|||
import { exec } from "child_process";
|
||||
import { IncomingMessage, request } from "http";
|
||||
import { Reference } from "./zoteroCodeLensProvider";
|
||||
import { window } from "vscode";
|
||||
import { findReferences, Reference } from "./reference";
|
||||
|
||||
|
||||
async function showInZotero(reference: Reference) {
|
||||
openUrl("zotero://select/items/@" + reference.citekey);
|
||||
// when called as command using keyboard, reference argument is undefined
|
||||
// in that case, try to find one under the current cursor.
|
||||
function resolveReferenceArgument(reference: Reference | undefined): Reference|undefined {
|
||||
if (reference === undefined) {
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
console.log("No active editor to run command");
|
||||
return;
|
||||
}
|
||||
const position = editor.selection.active;
|
||||
const referencesAtCursor = findReferences(editor.document).filter(ref => ref.range.contains(position));
|
||||
if (referencesAtCursor.length < 1) {
|
||||
window.showWarningMessage("No reference at cursor position.");
|
||||
return;
|
||||
}
|
||||
return referencesAtCursor[0];
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
|
||||
async function openZoteroPDF(reference: Reference) {
|
||||
async function showInZotero(reference?: Reference) {
|
||||
const ref = resolveReferenceArgument(reference);
|
||||
if (!ref) { return; }
|
||||
openUrl("zotero://select/items/@" + ref.citekey);
|
||||
}
|
||||
|
||||
async function openZoteroPDF(reference?: Reference) {
|
||||
const ref = resolveReferenceArgument(reference);
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
const req = request("http://127.0.0.1:23119/better-bibtex/json-rpc", {
|
||||
'method': 'POST',
|
||||
'headers': {
|
||||
|
@ -30,7 +57,7 @@ async function openZoteroPDF(reference: Reference) {
|
|||
if (result['path'].toLowerCase().endsWith('.pdf')) {
|
||||
// TODO add ?page=nr to open given page
|
||||
|
||||
const pagenr = reference.pagenr !== null ? "?page=" + reference.pagenr : "";
|
||||
const pagenr = ref.pagenr !== null ? "?page=" + ref.pagenr : "";
|
||||
openUrl(result['open'] + pagenr);
|
||||
break; // only open a single attachement
|
||||
}
|
||||
|
@ -39,7 +66,7 @@ async function openZoteroPDF(reference: Reference) {
|
|||
}
|
||||
});
|
||||
|
||||
req.end('{"jsonrpc": "2.0", "method": "item.attachments", "params": ["' + reference.citekey + '"]}', 'binary');
|
||||
req.end('{"jsonrpc": "2.0", "method": "item.attachments", "params": ["' + ref.citekey + '"]}', 'binary');
|
||||
}
|
||||
|
||||
function openUrl(url: string) {
|
||||
|
|
85
src/decorations.ts
Normal file
85
src/decorations.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { window, OverviewRulerLane, DecorationOptions, Uri, MarkdownString, Range, workspace, ExtensionContext } from "vscode";
|
||||
import { findReferences } from "./reference";
|
||||
|
||||
let timeout: NodeJS.Timer | undefined = undefined;
|
||||
|
||||
// create a decorator type that we use to decorate small numbers
|
||||
const referenceDecorationType = window.createTextEditorDecorationType({
|
||||
borderWidth: '2px',
|
||||
borderStyle: 'none none solid none',
|
||||
overviewRulerColor: 'blue',
|
||||
overviewRulerLane: OverviewRulerLane.Right,
|
||||
light: {
|
||||
// this color will be used in light color themes
|
||||
borderColor: 'darkblue'
|
||||
},
|
||||
dark: {
|
||||
// this color will be used in dark color themes
|
||||
borderColor: 'lightblue'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let activeEditor = window.activeTextEditor;
|
||||
|
||||
function updateDecorations() {
|
||||
if (!activeEditor) {
|
||||
return;
|
||||
}
|
||||
const regEx = /\d+/g;
|
||||
const text = activeEditor.document.getText();
|
||||
const decorations: DecorationOptions[] = [];
|
||||
|
||||
const references = findReferences(activeEditor.document);
|
||||
|
||||
for(let reference of references) {
|
||||
const uriArguments = `?${encodeURIComponent(JSON.stringify([reference]))}`;
|
||||
const pdfCommandUri = Uri.parse(`command:zoterolens.openZoteroPDF`+uriArguments);
|
||||
const viewCommandUri = Uri.parse(`command:zoterolens.showInZotero`+uriArguments);
|
||||
const contents = new MarkdownString(`${reference.citekey}\n\n[View in Zotero](${viewCommandUri}) | [Open PDF](${pdfCommandUri})`);
|
||||
|
||||
// To enable command URIs in Markdown content, you must set the `isTrusted` flag.
|
||||
// When creating trusted Markdown string, make sure to properly sanitize all the
|
||||
// input content so that only expected command URIs can be executed
|
||||
contents.isTrusted = true;
|
||||
|
||||
const decoration = { range: reference.range, hoverMessage: contents };
|
||||
decorations.push(decoration);
|
||||
}
|
||||
|
||||
activeEditor.setDecorations(referenceDecorationType, decorations);
|
||||
}
|
||||
|
||||
function triggerUpdateDecorations(throttle = false) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
if (throttle) {
|
||||
timeout = setTimeout(updateDecorations, 500);
|
||||
} else {
|
||||
updateDecorations();
|
||||
}
|
||||
}
|
||||
|
||||
export function activateDecorations(context: ExtensionContext) {
|
||||
|
||||
if (activeEditor) {
|
||||
triggerUpdateDecorations();
|
||||
}
|
||||
|
||||
window.onDidChangeActiveTextEditor(editor => {
|
||||
activeEditor = editor;
|
||||
if (editor) {
|
||||
triggerUpdateDecorations();
|
||||
}
|
||||
}, null, context.subscriptions);
|
||||
|
||||
workspace.onDidChangeTextDocument(event => {
|
||||
console.log(event.contentChanges);
|
||||
if (activeEditor && event.document === activeEditor.document) {
|
||||
triggerUpdateDecorations(true);
|
||||
}
|
||||
}, null, context.subscriptions);
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { commands, ExtensionContext, languages } from "vscode";
|
||||
import { commands, ExtensionContext, languages, HoverProvider, TextDocument, CancellationToken, Hover, MarkdownString, Position, ProviderResult, Uri, DecorationOptions, OverviewRulerLane, Range, window, workspace, WorkspaceConfiguration } from "vscode";
|
||||
import { ZoteroCodeLensProvider } from "./zoteroCodeLensProvider";
|
||||
import { openZoteroPDF, showInZotero } from "./commands";
|
||||
import { activateDecorations } from "./decorations";
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
// Register the command
|
||||
|
@ -21,17 +22,27 @@ export function activate(context: ExtensionContext) {
|
|||
scheme: "file"
|
||||
};
|
||||
|
||||
// Register our CodeLens provider
|
||||
let codeLensProviderDisposable = languages.registerCodeLensProvider(
|
||||
docSelector,
|
||||
new ZoteroCodeLensProvider()
|
||||
);
|
||||
|
||||
// Push the command and CodeLens provider to the context so it can be disposed of later
|
||||
context.subscriptions.push(commandDisposable2);
|
||||
context.subscriptions.push(commandDisposable3);
|
||||
context.subscriptions.push(codeLensProviderDisposable);
|
||||
const workspaceConfig: WorkspaceConfiguration = workspace.getConfiguration('zoterolens');
|
||||
const enabledLens: any = workspaceConfig.get('lens.enabled');
|
||||
const enabledDecoration: any = workspaceConfig.get('decoration.enabled');
|
||||
|
||||
// Register our CodeLens provider
|
||||
if (enabledLens) {
|
||||
let codeLensProviderDisposable = languages.registerCodeLensProvider(
|
||||
docSelector,
|
||||
new ZoteroCodeLensProvider()
|
||||
);
|
||||
|
||||
// Push the command and CodeLens provider to the context so it can be disposed of later
|
||||
context.subscriptions.push(commandDisposable2);
|
||||
context.subscriptions.push(commandDisposable3);
|
||||
context.subscriptions.push(codeLensProviderDisposable);
|
||||
}
|
||||
if (enabledDecoration) {
|
||||
activateDecorations(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
export function deactivate() { }
|
41
src/reference.ts
Normal file
41
src/reference.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import {
|
||||
CodeLensProvider,
|
||||
TextDocument,
|
||||
CodeLens,
|
||||
Range,
|
||||
Command
|
||||
} from "vscode";
|
||||
|
||||
export interface Reference {
|
||||
document: TextDocument;
|
||||
citekey: string;
|
||||
pagenr: number | null;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
// 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
|
||||
export function 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;
|
||||
}
|
|
@ -6,21 +6,16 @@ import {
|
|||
Range,
|
||||
Command
|
||||
} from "vscode";
|
||||
|
||||
export interface Reference {
|
||||
document: TextDocument;
|
||||
citekey: string;
|
||||
pagenr: number | null;
|
||||
range: Range;
|
||||
}
|
||||
import { findReferences } from "./reference";
|
||||
|
||||
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<CodeLens[]> {
|
||||
|
||||
const references = this.findReferences(document);
|
||||
const references = findReferences(document);
|
||||
|
||||
// some sorting and filtering to have a nicer lens
|
||||
return references
|
||||
// sort by line, citekey, pagenr
|
||||
.sort((a, b) => {
|
||||
|
@ -96,31 +91,6 @@ export class ZoteroCodeLensProvider implements CodeLensProvider {
|
|||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue