diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..38a6e66 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,10 @@ +import * as vscode from 'vscode' + +export const getMaxCount = () => + vscode.workspace.getConfiguration('hoverlens').get('maximumCursorCount', 3) + +export const getDebounceUpdate = () => + vscode.workspace.getConfiguration('hoverlens').get('debounceUpdate', 50) + +export const getMaxShift = () => + vscode.workspace.getConfiguration('hoverlens').get('maximumShiftCount', 20) diff --git a/src/decoration.ts b/src/decoration.ts index d346517..7215b95 100644 --- a/src/decoration.ts +++ b/src/decoration.ts @@ -4,16 +4,21 @@ import { removeEmptyLines, toPlainText, } from './formatting' +import { getMaxCount, getMaxShift } from './config' -export async function getDecorations( +export type Decoration = { + editor: vscode.TextEditor + type: vscode.TextEditorDecorationType + line: number +} + +export const getDecorations = async ( editor: vscode.TextEditor, selections: readonly vscode.Selection[] -) { +) => { if (!selections.length) return [] - const maxCount = vscode.workspace - .getConfiguration('hoverlens') - .get('maximumCursorCount', 3) + const maxCount = getMaxCount() if (maxCount > 0 && selections.length > maxCount) return [] const positions = selections @@ -33,9 +38,10 @@ export async function getDecorations( hovers.map(toPlainText).map(removeEmptyLines).join('\n').split('\n') ) - const maxShift = vscode.workspace - .getConfiguration('hoverlens') - .get('maximumShiftCount', 20) + const maxShift = getMaxShift() + + const getLineLength = (line: number) => + editor.document.lineAt(line).range.end.character const layouts = positions.map((position, i) => { const paddings: number[] = [] @@ -67,26 +73,17 @@ export async function getDecorations( return texts }) - return layouts - .map((paddings, i) => - paddings.map( - (padding, j) => - [ - editor, - createDecorationType(texts[i][j], padding), - positions[i].line + j, - ] as const - ) - ) - .flat() - - function getLineLength(line: number) { - return editor.document.lineAt(line).range.end.character - } + return layouts.flatMap((paddings, i) => + paddings.map((padding, j) => ({ + editor, + type: createDecorationType(texts[i][j], padding), + line: positions[i].line + j, + })) + ) } -function createDecorationType(text: string, padding: number) { - return vscode.window.createTextEditorDecorationType({ +const createDecorationType = (text: string, padding: number) => + vscode.window.createTextEditorDecorationType({ after: { contentText: changeLeadingSpacesToNonBreaking( ' '.repeat(padding) + text @@ -97,4 +94,3 @@ function createDecorationType(text: string, padding: number) { isWholeLine: true, }) -} diff --git a/src/extension.ts b/src/extension.ts index 806ef87..231d789 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,41 +1,47 @@ import * as vscode from 'vscode' -import { getDecorations } from './decoration' +import { Decoration, getDecorations } from './decoration' import { debounce } from './utils' +import { getDebounceUpdate } from './config' -export function activate(context: vscode.ExtensionContext) { - let tokenSource: vscode.CancellationTokenSource - - let currentDecorations: Awaited> = [] - - const updateDecorations = debounce( - async (event: vscode.TextEditorSelectionChangeEvent) => { - tokenSource = new vscode.CancellationTokenSource() - - const token = tokenSource.token - const decorations = await getDecorations( - event.textEditor, - event.selections - ) - if (token.isCancellationRequested) return - - currentDecorations.forEach(([, type]) => type.dispose()) - - currentDecorations = decorations - currentDecorations.forEach(([editor, type, line]) => - editor.setDecorations(type, [ - new vscode.Selection(line, 0, line, 0), - ]) - ) - }, - () => - vscode.workspace - .getConfiguration('hoverlens') - .get('debounceUpdate', 50) - ) +let tokenSource: vscode.CancellationTokenSource | undefined +const startUpdate = () => { + tokenSource = new vscode.CancellationTokenSource() + return tokenSource.token +} +const cancelUpdate = () => tokenSource?.cancel() + +let currentDecorations: Decoration[] = [] +const setDecorations = (decorations: Decoration[]) => { + for (const { type } of currentDecorations) { + type.dispose() + } + + currentDecorations = decorations + + for (const { editor, type, line } of currentDecorations) { + editor.setDecorations(type, [new vscode.Selection(line, 0, line, 0)]) + } +} + +const updateDecorations = debounce( + async (event: vscode.TextEditorSelectionChangeEvent) => { + const token = startUpdate() + + const decorations = await getDecorations( + event.textEditor, + event.selections + ) + if (token.isCancellationRequested) return + + setDecorations(decorations) + }, + getDebounceUpdate +) +export const activate = (context: vscode.ExtensionContext) => { context.subscriptions.push( vscode.window.onDidChangeTextEditorSelection((event) => { - tokenSource?.cancel() + cancelUpdate() updateDecorations(event) }) ) diff --git a/src/formatting.ts b/src/formatting.ts index 6d7f0eb..2546f1e 100644 --- a/src/formatting.ts +++ b/src/formatting.ts @@ -1,28 +1,24 @@ import markdownToTxt from 'markdown-to-txt' import * as vscode from 'vscode' -export function toPlainText(hover: vscode.Hover) { - return hover.contents - .map((content) => { - if (content instanceof vscode.MarkdownString) { - return markdownToTxt(content.value) - } else { - return typeof content === 'string' - ? markdownToTxt(content) - : content.value - } - }) +export const toPlainText = (hover: vscode.Hover) => + hover.contents + .map((content) => + content instanceof vscode.MarkdownString + ? markdownToTxt(content.value) + : typeof content === 'string' + ? markdownToTxt(content) + : content.value + ) .join('\n') -} -export function removeEmptyLines(text: string) { - return text +export const removeEmptyLines = (text: string) => + text .split('\n') .filter((line) => !!line) .join('\n') -} -export function changeLeadingSpacesToNonBreaking(line: string) { +export const changeLeadingSpacesToNonBreaking = (line: string) => { let i = 0 for (; i < line.length; i++) { if (line[i] !== ' ') break diff --git a/src/utils.ts b/src/utils.ts index 3fe82f6..fec753e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,9 @@ -export function debounce unknown>( +export const debounce = unknown>( fn: T, getMs: () => number -) { +) => { let timer: NodeJS.Timeout + return (...args: Parameters) => { clearTimeout(timer)