tvl-depot/users/Profpatsch/lyric/extension/src/extension.ts
Profpatsch 02713e054f refactor(users/Profpatsch/lyric/ext): move command into fn
Change-Id: I2d38455cdf881e03a390d129f9cee3f9eeca485d
Reviewed-on: https://cl.tvl.fyi/c/depot/+/12548
Tested-by: BuildkiteCI
Reviewed-by: Profpatsch <mail@profpatsch.de>
2024-10-05 13:49:38 +00:00

238 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as vscode from 'vscode';
import * as net from 'net';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(...registerCheckLineTimestamp(context));
context.subscriptions.push(
vscode.commands.registerCommand('extension.jumpToLrcPosition', jumpToLrcPosition),
);
}
function jumpToLrcPosition() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No active editor found.');
return;
}
const ext = new Ext(editor);
const position = editor.selection.active;
const res = ext.getTimestampFromLine(position);
if (!res) {
return;
}
const { milliseconds, seconds } = res;
// Prepare JSON command to send to the socket
const jsonCommand = {
command: ['seek', seconds, 'absolute'],
};
const socket = new net.Socket();
const socketPath = process.env.HOME + '/tmp/mpv-socket';
socket.connect(socketPath, () => {
socket.write(JSON.stringify(jsonCommand));
socket.write('\n');
vscode.window.showInformationMessage(
`Sent command to jump to [${formatTimestamp(milliseconds)}].`,
);
socket.end();
});
socket.on('error', err => {
vscode.window.showErrorMessage(`Failed to send command: ${err.message}`);
});
}
// If the difference to the timestamp on the next line is larger than 10 seconds, underline the next line and show a warning message on hover
export function registerCheckLineTimestamp(_context: vscode.ExtensionContext) {
const changesToCheck: Set<vscode.TextDocument> = new Set();
const everSeen = new Set<vscode.TextDocument>();
return [
vscode.workspace.onDidChangeTextDocument(e => {
changesToCheck.add(e.document);
if (vscode.window.activeTextEditor?.document === e.document) {
doEditorChecks(vscode.window.activeTextEditor, everSeen, changesToCheck);
}
}),
vscode.workspace.onDidOpenTextDocument(e => {
changesToCheck.add(e);
everSeen.add(e);
if (vscode.window.activeTextEditor?.document === e) {
doEditorChecks(vscode.window.activeTextEditor, everSeen, changesToCheck);
}
}),
vscode.window.onDidChangeActiveTextEditor(editor => {
if (editor) {
doEditorChecks(editor, everSeen, changesToCheck);
}
}),
vscode.window.onDidChangeVisibleTextEditors(editors => {
for (const editor of editors) {
doEditorChecks(editor, everSeen, changesToCheck);
}
}),
];
}
function doEditorChecks(
editor: vscode.TextEditor,
everSeen: Set<vscode.TextDocument>,
changesToCheck: Set<vscode.TextDocument>,
) {
const ext = new Ext(editor);
const document = editor.document;
if (!everSeen.has(editor.document)) {
changesToCheck.add(editor.document);
everSeen.add(editor.document);
}
if (!changesToCheck.has(document)) {
return;
}
changesToCheck.delete(document);
const from = 0;
const to = document.lineCount - 1;
for (let line = from; line <= to; line++) {
const warnings: string[] = [];
const timeDiff = timeDifferenceTooLarge(ext, line);
if (timeDiff !== undefined) {
warnings.push(timeDiff);
}
const nextTimestampSmaller = nextLineTimestampSmallerThanCurrent(ext, line);
if (nextTimestampSmaller !== undefined) {
warnings.push(nextTimestampSmaller);
}
for (const warning of warnings) {
global_manageWarnings.setWarning(document, line, warning);
}
// unset any warnings if this doesnt apply anymore
if (warnings.length === 0) {
global_manageWarnings.setWarning(document, line);
}
}
}
/** Warn if the difference to the timestamp on the next line is larger than 10 seconds */
function timeDifferenceTooLarge(ext: Ext, line: number): string | undefined {
const timeDifference = ext.getTimeDifferenceToNextLineTimestamp(
new vscode.Position(line, 0),
);
if (
!timeDifference ||
timeDifference.thisLineIsEmpty ||
timeDifference.difference <= 10000
) {
return;
}
return `Time difference to next line is ${formatTimestamp(timeDifference.difference)}`;
}
/** Warn if the timestamp on the next line is smaller or equal to the current timestamp */
function nextLineTimestampSmallerThanCurrent(ext: Ext, line: number): string | undefined {
const timeDifference = ext.getTimeDifferenceToNextLineTimestamp(
new vscode.Position(line, 0),
);
if (!timeDifference) {
return;
}
if (timeDifference.difference == 0) {
return `The timestamp to the next line is not increasing`;
}
if (timeDifference.difference < 0) {
return `The timestamp to the next line is decreasing`;
}
}
class Ext {
constructor(public editor: vscode.TextEditor) {}
getTimeDifferenceToNextLineTimestamp(position: vscode.Position) {
const thisLineTimestamp = this.getTimestampFromLine(position);
const nextLineTimestamp = this.getTimestampFromLine(
position.with({ line: position.line + 1 }),
);
if (!thisLineTimestamp || !nextLineTimestamp) {
return;
}
return {
difference: nextLineTimestamp.milliseconds - thisLineTimestamp.milliseconds,
thisLineIsEmpty: thisLineTimestamp.text.trim() === '',
};
}
getTimestampFromLine(position: vscode.Position) {
const document = this.editor.document;
const lineText = document.lineAt(position.line).text;
// Extract timestamp [mm:ss.ms] from the current line
const match = lineText.match(/\[(\d+:\d+\.\d+)\](.*)/);
if (!match) {
return;
}
const [, timestamp, text] = match!;
const milliseconds = parseTimestamp(timestamp);
const seconds = milliseconds / 1000;
return { milliseconds, seconds, text };
}
}
function parseTimestamp(timestamp: string): number {
// Parse [mm:ss.ms] format into milliseconds
const [min, sec] = timestamp.split(':');
const minutes = parseInt(min, 10);
const seconds = parseFloat(sec);
return minutes * 60 * 1000 + seconds * 1000;
}
function formatTimestamp(ms: number): string {
// Format milliseconds back into [mm:ss.ms]
const minutes = Math.floor(ms / 60000);
ms %= 60000;
const seconds = (ms / 1000).toFixed(2);
return `${String(minutes).padStart(2, '0')}:${seconds}`;
}
class ManageWarnings {
private warnings: Map<number, string> = new Map();
private diagnostics: vscode.DiagnosticCollection;
constructor() {
this.diagnostics = vscode.languages.createDiagnosticCollection();
}
/** Set a warning message on a line in a document, if null then unset */
setWarning(document: vscode.TextDocument, line: number, message?: string) {
if (message !== undefined) {
this.warnings.set(line, message);
} else {
this.warnings.delete(line);
}
this.updateDiagnostics(document);
}
private updateDiagnostics(document: vscode.TextDocument) {
const mkWarning = (line: number, message: string) => {
const lineRange = document.lineAt(line).range;
return new vscode.Diagnostic(lineRange, message, vscode.DiagnosticSeverity.Warning);
};
const diagnostics: vscode.Diagnostic[] = [];
for (const [line, message] of this.warnings) {
diagnostics.push(mkWarning(line, message));
}
this.diagnostics.delete(document.uri);
this.diagnostics.set(document.uri, diagnostics);
}
}
const global_manageWarnings = new ManageWarnings();