From 28d15ba488760b948f47edcf6b319ec30da731c9 Mon Sep 17 00:00:00 2001 From: Hiroki Tokunaga Date: Sun, 28 Jan 2024 14:29:11 +0900 Subject: [PATCH] Implement jumping to messages that need to be update / support messages with plural forms / Refactoring (#31) --- README.md | 2 + package.json | 12 ++ src/focusing.ts | 40 +++++ src/lib.ts | 35 ++++ src/lines.ts | 16 ++ src/message.ts | 99 +++++++++++ src/message_parser.ts | 189 ++++++++++++++++++++ src/message_type.ts | 15 ++ src/moving.ts | 11 ++ src/provide_definition.ts | 35 ++++ src/regex.ts | 11 ++ src/vscgettext.ts | 303 ++++++-------------------------- test/extension.test.ts | 332 +++++++++++++++++++++--------------- test/inputfiles/messages.po | 24 +++ 14 files changed, 738 insertions(+), 386 deletions(-) create mode 100644 src/focusing.ts create mode 100644 src/lib.ts create mode 100644 src/lines.ts create mode 100644 src/message.ts create mode 100644 src/message_parser.ts create mode 100644 src/message_type.ts create mode 100644 src/moving.ts create mode 100644 src/provide_definition.ts create mode 100644 src/regex.ts diff --git a/README.md b/README.md index eb45d98..9110d58 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,5 @@ Source tmLanguage file: https://github.com/textmate/gettext.tmbundle/blob/master * `vscgettext.moveToPreviousUntranslated`: Move focus to previous untranslated message (`alt+shift+n`) * `vscgettext.moveToNextFuzzy`: Move focus to next fuzzy message (`alt+f`) * `vscgettext.moveToPreviousFuzzy`: Move focus to previous fuzzy message (`alt+shift+f`) +* `vscgettext.moveToNextUntranslatedOrFuzzy`: Move focus to next untranslated or fuzzy message (`alt+u`) +* `vscgettext.moveToPreviousUntranslatedOrFuzzy`: Move focus to previous untranslated or fuzzy message (`alt+shift+u`) diff --git a/package.json b/package.json index 491357b..434370c 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "onCommand:vscgettext.moveToPreviousUntranslated", "onCommand:vscgettext.moveToNextFuzzy", "onCommand:vscgettext.moveToPreviousFuzzy", + "onCommand:vscgettext.moveToNextUntranslatedOrFuzzy", + "onCommand:vscgettext.moveToPreviousUntranslatedOrFuzzy", "onLanguage:po", "onLanguage:pot", "onLanguage:potx" @@ -74,6 +76,16 @@ "command": "vscgettext.moveToPreviousFuzzy", "key": "alt+shift+f", "when": "editorTextFocus && resourceLangId == po" + }, + { + "command": "vscgettext.moveToNextUntranslatedOrFuzzy", + "key": "alt+u", + "when": "editorTextFocus && resourceLangId == po" + }, + { + "command": "vscgettext.moveToPreviousUntranslatedOrFuzzy", + "key": "alt+shift+u", + "when": "editorTextFocus && resourceLangId == po" } ] }, diff --git a/src/focusing.ts b/src/focusing.ts new file mode 100644 index 0000000..267314c --- /dev/null +++ b/src/focusing.ts @@ -0,0 +1,40 @@ +import * as vscode from "vscode"; +import { Message } from "./message_type"; +import { moveCursorTo } from "./moving"; + +export function focusOnNextTarget( + editor: vscode.TextEditor, + nextTargetFunc: ( + editor: vscode.TextDocument, + lineno: number, + backwards: boolean + ) => Message, + backwards = false +) { + const position = editor.selection.active; + const message = nextTargetFunc(editor.document, position.line, backwards); + if (message !== null) { + focusOnMessage(editor, message); + } +} + +function focusOnMessage(editor: vscode.TextEditor, message: Message) { + const [line, character] = msgstrPosition(message); + const position = moveCursorTo(editor, line, character); + editor.revealRange( + new vscode.Range(position, position), + vscode.TextEditorRevealType.InCenterIfOutsideViewport + ); +} + +function msgstrPosition(message: Message): [number, number] { + for (const [index, line] of message.msgstrPluralLine.entries()) { + if (!message.msgstrPlural[index] || message.isfuzzy) { + return index === 0 + ? [line, 11] + : [line, 11 + Math.floor(Math.log10(index))]; + } + } + + return [message.msgstrLine, 8]; +} diff --git a/src/lib.ts b/src/lib.ts new file mode 100644 index 0000000..cecf4f2 --- /dev/null +++ b/src/lib.ts @@ -0,0 +1,35 @@ +import * as vscode from "vscode"; +import { + nextFuzzyMessage, + nextUntranslatedMessage, + nextUntranslatedOrFuzzyMessage, +} from "./message"; +import { focusOnNextTarget } from "./focusing"; + +export function moveToNextUntranslatedMessage(editor: vscode.TextEditor) { + focusOnNextTarget(editor, nextUntranslatedMessage); +} + +export function moveToPreviousUntranslatedMessage(editor: vscode.TextEditor) { + focusOnNextTarget(editor, nextUntranslatedMessage, true); +} + +export function moveToNextFuzzyMessage(editor: vscode.TextEditor) { + focusOnNextTarget(editor, nextFuzzyMessage); +} + +export function moveToPreviousFuzzyMessage(editor: vscode.TextEditor) { + focusOnNextTarget(editor, nextFuzzyMessage, true); +} + +export function moveToNextUntranslatedOrFuzzyMessage( + editor: vscode.TextEditor +) { + focusOnNextTarget(editor, nextUntranslatedOrFuzzyMessage); +} + +export function moveToPreviousUntranslatedOrFuzzyMessage( + editor: vscode.TextEditor +) { + focusOnNextTarget(editor, nextUntranslatedOrFuzzyMessage, true); +} diff --git a/src/lines.ts b/src/lines.ts new file mode 100644 index 0000000..b75f1ca --- /dev/null +++ b/src/lines.ts @@ -0,0 +1,16 @@ +import * as vscode from "vscode"; + +export function* documentLines(document: vscode.TextDocument, startline = 0) { + for (let lineno = startline; lineno < document.lineCount; lineno++) { + yield document.lineAt(lineno); + } +} + +export function* backwardDocumentLines( + document: vscode.TextDocument, + startline = document.lineCount - 1 +) { + for (let lineno = startline; lineno >= 0; lineno--) { + yield document.lineAt(lineno); + } +} diff --git a/src/message.ts b/src/message.ts new file mode 100644 index 0000000..cd85ed3 --- /dev/null +++ b/src/message.ts @@ -0,0 +1,99 @@ +import * as vscode from "vscode"; +import { MessageParser } from "./message_parser"; +import { Message } from "./message_type"; +import { documentLines, backwardDocumentLines } from "./lines"; + +export function nextUntranslatedMessage( + document: vscode.TextDocument, + lineno: number, + backwards = false +): Message { + return nextMessageWithCondition(document, lineno, isUntranslated, backwards); +} + +export function nextFuzzyMessage( + document: vscode.TextDocument, + lineno: number, + backwards = false +): Message { + return nextMessageWithCondition( + document, + lineno, + (message: Message) => message.isfuzzy, + backwards + ); +} + +export function nextUntranslatedOrFuzzyMessage( + document: vscode.TextDocument, + lineno: number, + backwards = false +): Message { + return nextMessageWithCondition( + document, + lineno, + (message: Message) => isUntranslated(message) || message.isfuzzy, + backwards + ); +} + +function nextMessageWithCondition( + document: vscode.TextDocument, + lineno: number, + condition: Function, + backwards = false +): Message { + let message = parseMessage(document, lineno); + const getMessage = backwards ? previousMessage : nextMessage; + while (message !== null) { + message = getMessage(document, message); + if (message && condition(message)) { + return message; + } + } + return null; +} + +function nextMessage( + document: vscode.TextDocument, + currentMessage: Message +): Message { + for (const line of documentLines(document, currentMessage.lastline + 1)) { + if (line.text && !line.text.trim().startsWith("#")) { + return parseMessage(document, line.lineNumber); + } + } + return null; +} + +function previousMessage( + document: vscode.TextDocument, + currentMessage: Message +): Message { + for (const line of backwardDocumentLines( + document, + currentMessage.firstline - 1 + )) { + if (line.text && !line.text.trim().startsWith("#")) { + return parseMessage(document, line.lineNumber); + } + } + return null; +} + +function parseMessage(document: vscode.TextDocument, lineno: number): Message { + return new MessageParser(document, lineno).parse(); +} + +function isUntranslated(message: Message): boolean { + return !isTranslated(message); +} + +function isTranslated(message: Message): boolean { + return ( + (message.msgstr && message.msgstr !== "") || + (message.msgstrPlural && + message.msgstrPlural.length > 0 && + message.msgstrPlural.every((msgstr) => msgstr !== "")) + ); +} diff --git a/src/message_parser.ts b/src/message_parser.ts new file mode 100644 index 0000000..014962a --- /dev/null +++ b/src/message_parser.ts @@ -0,0 +1,189 @@ +import * as vscode from "vscode"; +import { Message } from "./message_type"; +import { documentLines, backwardDocumentLines } from "./lines"; +import { + fuzzyRgx, + msgctxtStartRgx, + msgidStartRgx, + msgidPluralStartRgx, + msgstrStartRgx, + msgstrPluralStartRgx, + continuationLineRgx, +} from "./regex"; + +export class MessageParser { + document: vscode.TextDocument; + currentline: number; + message: Message; + + public constructor(document: vscode.TextDocument, currentline: number) { + this.document = document; + this.currentline = currentline; + this.message = { + msgid: null, + msgidLine: null, + msgidPlural: null, + msgidPluralLine: null, + msgstr: null, + msgstrLine: null, + msgstrPlural: null, + msgstrPluralLine: null, + msgctxt: null, + msgctxtLine: null, + firstline: null, + lastline: null, + isfuzzy: false, + }; + } + + public parse(): Message { + const firstline = this.currentMessageStart(); + if (firstline === null) { + return null; + } + + this.message.firstline = firstline.lineNumber; + this.message.lastline = firstline.lineNumber; + + this.parseComments(); + this.parseMsgctxt(); + this.parseMsgid(); + this.parseMsgidPlural(); + this.parseMsgstr(); + this.parseMsgstrPlural(); + + return this.message; + } + + private parseComments() { + for (const line of documentLines(this.document, this.message.lastline)) { + if (!line.text.trim().startsWith("#")) { + return; + } + + this.message.lastline++; + + if (fuzzyRgx.test(line.text)) { + this.message.isfuzzy = true; + } + } + } + + private parseMsgctxt() { + this.message.msgctxt = ""; + this.parseWithKey( + "msgctxt", + (value) => (this.message.msgctxt += value), + (value) => (this.message.msgctxtLine = value) + ); + } + + private parseMsgid() { + this.message.msgid = ""; + this.parseWithKey( + "msgid", + (value) => (this.message.msgid += value), + (value) => (this.message.msgidLine = value) + ); + } + + private parseMsgidPlural() { + this.message.msgidPlural = ""; + this.parseWithKey( + "msgid_plural", + (value) => (this.message.msgidPlural += value), + (value) => (this.message.msgidPluralLine = value) + ); + } + + private parseMsgstr() { + this.message.msgstr = ""; + this.parseWithKey( + "msgstr", + (value) => (this.message.msgstr += value), + (value) => (this.message.msgstrLine = value) + ); + } + + private parseMsgstrPlural() { + this.message.msgstrPlural = []; + this.message.msgstrPluralLine = []; + + for (let index = 0; ; index++) { + const key = `msgstr[${index}]`; + const lastline = this.message.lastline; + + this.parseWithKey( + key, + (value) => { + this.message.msgstrPlural[index] ||= ""; + this.message.msgstrPlural[index] += value; + }, + (value) => (this.message.msgstrPluralLine[index] = value) + ); + + if (this.message.lastline === lastline) { + break; + } + } + } + + private parseWithKey( + key: string, + valueSetter: (value: string) => void, + lineNumberSetter: (value: number) => void + ) { + key = key.replace(/[\[\]]/g, "\\$&"); + const regex = new RegExp(`^${key}\\s+"(.*)"$`); + + const firstLine = this.document.lineAt(this.message.lastline); + + if (!regex.test(firstLine.text)) { + return; + } + + valueSetter(regex.exec(firstLine.text)[1]); + lineNumberSetter(firstLine.lineNumber); + this.message.lastline++; + + for (const line of documentLines(this.document, this.message.lastline)) { + if (continuationLineRgx.test(line.text)) { + valueSetter(continuationLineRgx.exec(line.text)[1]); + this.message.lastline++; + } else { + return; + } + } + } + + private currentMessageStart(): vscode.TextLine { + let startLine: vscode.TextLine | null = null; + + // go backwards to msgid definition + for (const line of backwardDocumentLines(this.document, this.currentline)) { + if (line.text === "" && startLine !== null) { + // we hit a msgstr but we already hit a msgid definition, it means + // that we've reached another message definition, return the line of + // the msgid hit. + return startLine; + } + + const isComment = line.text && line.text.trim().startsWith("#"); + + if ( + isComment || + msgctxtStartRgx.test(line.text) || + msgidStartRgx.test(line.text) || + msgstrStartRgx.test(line.text) || + msgidPluralStartRgx.test(line.text) || + msgstrPluralStartRgx.test(line.text) + ) { + startLine = line; + } + } + + // if we've reached the beginning of the file, msgidLine won't have been set + // and we'll return null in that case. + return startLine; + } +} diff --git a/src/message_type.ts b/src/message_type.ts new file mode 100644 index 0000000..edaac1f --- /dev/null +++ b/src/message_type.ts @@ -0,0 +1,15 @@ +export type Message = { + msgid: string; + msgidLine: number; + msgidPlural: string; + msgidPluralLine: number; + msgstr: string; + msgstrLine: number; + msgstrPlural: string[]; + msgstrPluralLine: number[]; + msgctxt: string; + msgctxtLine: number; + firstline: number; + lastline: number; + isfuzzy: boolean; +}; diff --git a/src/moving.ts b/src/moving.ts new file mode 100644 index 0000000..54afcbf --- /dev/null +++ b/src/moving.ts @@ -0,0 +1,11 @@ +import * as vscode from "vscode"; + +export function moveCursorTo( + editor: vscode.TextEditor, + lineno: number, + colno = 0 +): vscode.Position { + const position = new vscode.Position(lineno, colno); + editor.selection = new vscode.Selection(position, position); + return position; +} diff --git a/src/provide_definition.ts b/src/provide_definition.ts new file mode 100644 index 0000000..875a8a6 --- /dev/null +++ b/src/provide_definition.ts @@ -0,0 +1,35 @@ +import * as vscode from "vscode"; + +export default function ( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken +) { + // Extract the line text and check if it is a comment with a source file reference + const poLine = document.lineAt(position).text; + if (poLine.startsWith("#: ") && poLine.split(" ").length > 1) { + const sourceLocation = poLine.split(" ")[1].split(":"); + if (sourceLocation.length === 2) { + // Get the source file path + const sourceFile = sourceLocation[0]; + // Get the line number (vscode uses 0-based line numbers) + const sourceLine: number = +sourceLocation[1] - 1; + // Find files in the workspace matching the source file + return vscode.workspace.findFiles(sourceFile).then((value) => { + if (!value) { + return; + } + + // Set the source position to the 0th character of the line number + const sourcePosition = new vscode.Position(sourceLine, 0); + + // Set the position for every matching file in the workspace + const locations: vscode.Location[] = []; + value.forEach((uri) => { + locations.push(new vscode.Location(uri, sourcePosition)); + }); + return locations; + }); + } + } +} diff --git a/src/regex.ts b/src/regex.ts new file mode 100644 index 0000000..bd55004 --- /dev/null +++ b/src/regex.ts @@ -0,0 +1,11 @@ +export const fuzzyRgx = /^#, fuzzy/; +export const msgctxtStartRgx = regexWithKey("msgctxt"); +export const msgidStartRgx = regexWithKey("msgid"); +export const msgidPluralStartRgx = regexWithKey("msgid_plural"); +export const msgstrStartRgx = regexWithKey("msgstr"); +export const msgstrPluralStartRgx = regexWithKey("msgstr\\[\\d+\\]"); +export const continuationLineRgx = /^"(.*?)\s*"$/; + +function regexWithKey(key: string) { + return new RegExp(`^${key}\\s+"(.*?)"\\s*$`); +} diff --git a/src/vscgettext.ts b/src/vscgettext.ts index 44e275d..3343f89 100644 --- a/src/vscgettext.ts +++ b/src/vscgettext.ts @@ -2,262 +2,59 @@ * This module is the main entry point of vscode-gettext extension */ -import * as vscode from 'vscode'; - -const msgidStartRgx = /^msgid\s+"(.*?)"\s*$/; -const msgstrStartRgx = /^msgstr\s+"(.*?)"\s*$/; -const msgctxtStartRgx = /^msgctxt\s+"(.*?)"\s*$/; -const continuationLineRgx = /^"(.*?)\s*"$/; -const fuzzyRgx = /^#, fuzzy/; - -interface IMessage { - msgid: string; - msgidLine: number; - msgstr: string; - msgstrLine: number; - msgctxt: string; - msgctxtLine: number; - firstline: number; - lastline: number; - isfuzzy: boolean; -} - -export function moveCursorTo(editor: vscode.TextEditor, lineno: number, colno = 0): vscode.Position { - const position = new vscode.Position(lineno, colno); - editor.selection = new vscode.Selection(position, position); - return position; -} - -function focusOnMessage(editor: vscode.TextEditor, message: IMessage) { - const position = moveCursorTo(editor, message.msgstrLine, 8); - editor.revealRange( - new vscode.Range(position, position), - vscode.TextEditorRevealType.InCenterIfOutsideViewport - ); -} - -function* documentLines(document: vscode.TextDocument, startline = 1) { - for (let lineno = startline; lineno < document.lineCount; lineno++) { - yield document.lineAt(lineno); - } -} - -function* backwardDocumentLines(document: vscode.TextDocument, startline = document.lineCount - 1) { - for (let lineno = startline; lineno >= 0; lineno--) { - yield document.lineAt(lineno); - } -} - -function nextMessage(document: vscode.TextDocument, currentMessage: IMessage): IMessage { - for (const line of documentLines(document, currentMessage.lastline + 1)) { - if (line.text && !line.text.trim().startsWith('#')) { - return currentMessageDefinition(document, line.lineNumber); - } - } - return null; -} - -function previousMessage(document: vscode.TextDocument, currentMessage: IMessage): IMessage { - for (const line of backwardDocumentLines(document, currentMessage.firstline - 1)) { - if (line.text && !line.text.trim().startsWith('#')) { - return currentMessageDefinition(document, line.lineNumber); - } - } - return null; -} - -function currentMessageStart(document: vscode.TextDocument, currentLine: number): vscode.TextLine { - let startLine = null; - - // Comments (optional), msgctxt (optional), msgid, and msgstr appear in this order. - - // go backwards to msgid definition - for (const line of backwardDocumentLines(document, currentLine)) { - const isComment = line.text && line.text.trim().startsWith('#'); - - if (msgstrStartRgx.test(line.text) && startLine !== null) { - // we hit a msgstr but we already hit a msgid definition, it means - // that we've reached another message definition, return the line of - // the msgid hit. - return startLine; - } - - if (isComment - || msgctxtStartRgx.test(line.text) - || msgidStartRgx.test(line.text) - || msgstrStartRgx.test(line.text)) { - startLine = line; - continue; - } - } - // if we've reached the beginning of the file, msgidLine won't have been set - // and we'll return null in that case. - return startLine; -} - -export function currentMessageDefinition(document: vscode.TextDocument, currentline: number): IMessage { - const firstline = currentMessageStart(document, currentline); - if (firstline === null) { - return null; - } - - let currentProperty; - const message: IMessage = { - msgid: null, - msgidLine: null, - msgstr: null, - msgstrLine: null, - msgctxt: null, - msgctxtLine: null, - firstline: firstline.lineNumber, - lastline: firstline.lineNumber, - isfuzzy: false, - }; - - for (const line of documentLines(document, message.firstline)) { - if (fuzzyRgx.test(line.text)) { - if (message.msgid !== null) { - break; - } else { - message.isfuzzy = true; - } - } else if (line.text.trim().startsWith('#') && message.msgid !== null) { - break; - } else if (msgctxtStartRgx.test(line.text)) { - if (message.msgctxt !== null || message.msgid !== null) { - break; - } else { - message.msgctxt = msgctxtStartRgx.exec(line.text)[1]; - message.msgctxtLine = line.lineNumber; - currentProperty = 'msgctxt'; - } - } else if (msgidStartRgx.test(line.text)) { - if (message.msgid !== null) { - break; // we are now on the next message, definition is over - } else { - message.msgid = msgidStartRgx.exec(line.text)[1]; - message.msgidLine = line.lineNumber; - currentProperty = 'msgid'; - } - } else if (msgstrStartRgx.test(line.text)) { - message.msgstr = msgstrStartRgx.exec(line.text)[1]; - message.msgstrLine = line.lineNumber; - currentProperty = 'msgstr'; - } else if (continuationLineRgx.test(line.text)) { - message[currentProperty] += continuationLineRgx.exec(line.text)[1]; - } - message.lastline++; - } - - message.lastline--; // last line is the one before the next message definition - - return message; -} - -function nextUntranslatedMessage(document: vscode.TextDocument, lineno: number, backwards = false): IMessage { - let message = currentMessageDefinition(document, lineno); - const messageIterator = backwards ? previousMessage : nextMessage; - while (message !== null) { - message = messageIterator(document, message); - if (message && !message.msgstr) { - return message; - } - } - return null; -} - -function nextFuzzyMessage(document: vscode.TextDocument, lineno: number, backwards = false): IMessage { - let message = currentMessageDefinition(document, lineno); - const messageIterator = backwards ? previousMessage : nextMessage; - while (message !== null) { - message = messageIterator(document, message); - if (message && message.isfuzzy) { - return message; - } - } - return null; -} - -function focusOnNextUntranslated(editor: vscode.TextEditor, backwards = false) { - const position = editor.selection.active; - const message = nextUntranslatedMessage(editor.document, position.line, backwards); - if (message !== null) { - focusOnMessage(editor, message); - } -} - -function focusOnNextFuzzy(editor: vscode.TextEditor, backwards = false) { - const position = editor.selection.active; - const message = nextFuzzyMessage(editor.document, position.line, backwards); - if (message !== null) { - focusOnMessage(editor, message); - } -} - -export function moveToNextUntranslatedMessage(editor: vscode.TextEditor) { - focusOnNextUntranslated(editor); -} - -export function moveToPreviousUntranslatedMessage(editor: vscode.TextEditor) { - focusOnNextUntranslated(editor, true); -} - -export function moveToNextFuzzyMessage(editor: vscode.TextEditor) { - focusOnNextFuzzy(editor); -} - -export function moveToPreviousFuzzyMessage(editor: vscode.TextEditor) { - focusOnNextFuzzy(editor, true); -} - -export function provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) { - // Extract the line text and check if it is a comment with a source file reference - const poLine = document.lineAt(position).text; - if (poLine.startsWith('#: ') && poLine.split(' ').length > 1) { - const sourceLocation = poLine.split(' ')[1].split(':'); - if (sourceLocation.length === 2) { - // Get the source file path - const sourceFile = sourceLocation[0]; - // Get the line number (vscode uses 0-based line numbers) - const sourceLine: number = (+sourceLocation[1]) - 1; - // Find files in the workspace matching the source file - return vscode.workspace.findFiles(sourceFile).then(value => { - if (!value) { - return; - } - - // Set the source position to the 0th character of the line number - const sourcePosition = new vscode.Position(sourceLine, 0); - - // Set the position for every matching file in the workspace - const locations: vscode.Location[] = []; - value.forEach(uri => { - locations.push(new vscode.Location(uri, sourcePosition)); - }); - return locations; - }); - } - } -} +import * as vscode from "vscode"; +import { + moveToNextUntranslatedMessage, + moveToPreviousUntranslatedMessage, + moveToNextFuzzyMessage, + moveToPreviousFuzzyMessage, + moveToNextUntranslatedOrFuzzyMessage, + moveToPreviousUntranslatedOrFuzzyMessage, +} from "./lib"; +import provideDefinition from "./provide_definition"; export function activate(context: vscode.ExtensionContext) { - context.subscriptions.push( - vscode.commands.registerTextEditorCommand('vscgettext.moveToNextUntranslated', moveToNextUntranslatedMessage) - ); - context.subscriptions.push( - vscode.commands.registerTextEditorCommand('vscgettext.moveToPreviousUntranslated', moveToPreviousUntranslatedMessage) - ); - context.subscriptions.push( - vscode.commands.registerTextEditorCommand('vscgettext.moveToNextFuzzy', moveToNextFuzzyMessage) - ); - context.subscriptions.push( - vscode.commands.registerTextEditorCommand('vscgettext.moveToPreviousFuzzy', moveToPreviousFuzzyMessage) - ); - context.subscriptions.push( - vscode.languages.registerDefinitionProvider('po', { provideDefinition }) - ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + "vscgettext.moveToNextUntranslated", + moveToNextUntranslatedMessage + ) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + "vscgettext.moveToPreviousUntranslated", + moveToPreviousUntranslatedMessage + ) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + "vscgettext.moveToNextFuzzy", + moveToNextFuzzyMessage + ) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + "vscgettext.moveToPreviousFuzzy", + moveToPreviousFuzzyMessage + ) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + "vscgettext.moveToNextUntranslatedOrFuzzy", + moveToNextUntranslatedOrFuzzyMessage + ) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + "vscgettext.moveToPreviousUntranslatedOrFuzzy", + moveToPreviousUntranslatedOrFuzzyMessage + ) + ); + context.subscriptions.push( + vscode.languages.registerDefinitionProvider("po", { provideDefinition }) + ); } export function deactivate() { - // deactivate extension + // deactivate extension } diff --git a/test/extension.test.ts b/test/extension.test.ts index 3539e31..9643d13 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -2,147 +2,213 @@ * vsgettext extension tests */ -import { assert } from 'chai'; -import { join as pathjoin } from 'path'; +import { assert } from "chai"; +import { join as pathjoin } from "path"; -import * as vscode from 'vscode'; -import * as vscgettext from '../src/vscgettext'; +import * as vscode from "vscode"; +import * as vscgettext from "../src/lib"; +import { moveCursorTo } from "../src/moving"; function inputpath(basename: string): string { - return pathjoin(__dirname, '..', '..', 'test', 'inputfiles', basename); + return pathjoin(__dirname, "..", "..", "test", "inputfiles", basename); } -function assertCursorAt(editor: vscode.TextEditor, lineno: number, colno: number) { - const selection = editor.selection.active; - const actualPos = {lineno: selection.line, colno: selection.character}; - assert.deepEqual(actualPos, {lineno, colno}); +function assertCursorAt( + editor: vscode.TextEditor, + lineno: number, + colno: number +) { + const selection = editor.selection.active; + const actualPos = { lineno: selection.line, colno: selection.character }; + assert.deepEqual(actualPos, { lineno, colno }); } function openFile(path): Thenable { - return vscode.workspace.openTextDocument(path).then(document => { - return vscode.window.showTextDocument(document); - }); + return vscode.workspace.openTextDocument(path).then((document) => { + return vscode.window.showTextDocument(document); + }); } -suite('vscode-gettext tests', () => { - test('move to helper', done => { - openFile(inputpath('messages.po')).then(editor => { - vscgettext.moveCursorTo(editor, 10, 0); - assertCursorAt(editor, 10, 0); - }).then(done, done); - }); - - test('message definition parsing', done => { - openFile(inputpath('messages.po')).then(editor => { - const message = vscgettext.currentMessageDefinition(editor.document, 15); - assert.deepEqual(message, { - msgid: 'msgid1', - msgidLine: 15, - msgstr: 'message 1', - msgstrLine: 16, - msgctxt: null, - msgctxtLine: null, - firstline: 15, - lastline: 17, - isfuzzy: false, - }); - }).then(done, done); - }); - - test('message definition parsing with msgctx', done => { - openFile(inputpath('messages.po')).then(editor => { - const message = vscgettext.currentMessageDefinition(editor.document, 27); - assert.deepEqual(message, { - msgid: 'agent_type', - msgidLine: 27, - msgstr: '', - msgstrLine: 28, - msgctxt: 'CorporateBody', - msgctxtLine: 26, - firstline: 26, - lastline: 29, - isfuzzy: false, - }); - }).then(done, done); - }); - - test('message definition with msgid on multiple lines', done => { - openFile(inputpath('messages.po')).then(editor => { - const message = vscgettext.currentMessageDefinition(editor.document, 31); - assert.deepEqual(message, { - msgid: 'Another message on several lines.', - msgidLine: 30, - msgstr: 'translation on several lines too.', - msgstrLine: 32, - msgctxt: null, - msgctxtLine: null, - firstline: 30, - lastline: 34, - isfuzzy: false, - }); - }).then(done, done); - }); - - test('editor jumps not next untranslated msgstr', done => { - openFile(inputpath('messages.po')).then(editor => { - // put the cursor somewhere in the file - vscgettext.moveCursorTo(editor, 10, 0); - // move to next untranslated message and check new position - vscgettext.moveToNextUntranslatedMessage(editor); - assertCursorAt(editor, 20, 8); - // now move again to make sure we don't stay on current message - vscgettext.moveToNextUntranslatedMessage(editor); - assertCursorAt(editor, 24, 8); - }).then(done, done); - }); - - test('editor doesn\'t move when on the last message of the file', done => { - openFile(inputpath('messages.po')).then(editor => { - // put the cursor somewhere in the file - vscgettext.moveCursorTo(editor, 42, 0); - // move to next untranslated message and check new position - vscgettext.moveToNextUntranslatedMessage(editor); - assertCursorAt(editor, 42, 0); - }).then(done, done); - }); - - test('jump to previous untranslated message', done => { - openFile(inputpath('messages.po')).then(editor => { - // put the cursor somewhere in the file - vscgettext.moveCursorTo(editor, 37, 0); - // move to next untranslated message and check new position - vscgettext.moveToPreviousUntranslatedMessage(editor); - assertCursorAt(editor, 28, 8); - }).then(done, done); - }); - - test('editor doesn\'t move when on the first message of the file', done => { - openFile(inputpath('messages.po')).then(editor => { - // put the cursor somewhere in the file - vscgettext.moveCursorTo(editor, 10, 0); - // move to next untranslated message and check new position - vscgettext.moveToPreviousUntranslatedMessage(editor); - assertCursorAt(editor, 10, 0); - }).then(done, done); - }); - - test('jump to the previous fuzzy message', done => { - openFile(inputpath('messages.po')).then(editor => { - // put the cursor somewhere in the file - vscgettext.moveCursorTo(editor, 42, 0); - // move to next fuzzy message and check new position - vscgettext.moveToPreviousFuzzyMessage(editor); - assertCursorAt(editor, 38, 8); - }).then(done, done); - }); - - test('jump to the next fuzzy message', done => { - openFile(inputpath('messages.po')).then(editor => { - // put the cursor somewhere in the file - vscgettext.moveCursorTo(editor, 10, 0); - // move to next fuzzy message and check new position - vscgettext.moveToNextFuzzyMessage(editor); - assertCursorAt(editor, 38, 8); - }).then(done, done); - }); +suite("Helper functions", () => { + test("`moveCursorTo` moves the cursor to the correct position", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + moveCursorTo(editor, 10, 0); + assertCursorAt(editor, 10, 0); + }) + .then(done, done); + }); +}); + +suite("vscode-gettext tests", () => { + test("editor jumps not next untranslated msgstr", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor somewhere in the file + moveCursorTo(editor, 10, 0); + // move to next untranslated message and check new position + vscgettext.moveToNextUntranslatedMessage(editor); + assertCursorAt(editor, 20, 8); + // now move again to make sure we don't stay on current message + vscgettext.moveToNextUntranslatedMessage(editor); + assertCursorAt(editor, 24, 8); + }) + .then(done, done); + }); + + test("editor doesn't move when on the last message of the file", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor somewhere in the file + moveCursorTo(editor, 59, 0); + // move to next untranslated message and check new position + vscgettext.moveToNextUntranslatedMessage(editor); + assertCursorAt(editor, 59, 0); + }) + .then(done, done); + }); + + test("jump to previous untranslated message", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor somewhere in the file + moveCursorTo(editor, 37, 0); + // move to next untranslated message and check new position + vscgettext.moveToPreviousUntranslatedMessage(editor); + assertCursorAt(editor, 28, 8); + }) + .then(done, done); + }); + + test("editor doesn't move when on the first message of the file", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor somewhere in the file + moveCursorTo(editor, 10, 0); + // move to next untranslated message and check new position + vscgettext.moveToPreviousUntranslatedMessage(editor); + assertCursorAt(editor, 10, 0); + }) + .then(done, done); + }); + + test("jump to the previous fuzzy message", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor somewhere in the file + moveCursorTo(editor, 42, 0); + // move to next fuzzy message and check new position + vscgettext.moveToPreviousFuzzyMessage(editor); + assertCursorAt(editor, 38, 8); + }) + .then(done, done); + }); + + test("jump to the next fuzzy message", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor somewhere in the file + moveCursorTo(editor, 10, 0); + // move to next fuzzy message and check new position + vscgettext.moveToNextFuzzyMessage(editor); + assertCursorAt(editor, 38, 8); + }) + .then(done, done); + }); + + test("jump to the next untranslated singular message", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor somewhere in the file + moveCursorTo(editor, 48, 0); + // move to next untranslated singular message and check new position + vscgettext.moveToNextUntranslatedMessage(editor); + assertCursorAt(editor, 51, 11); + }) + .then(done, done); + }); + + test("jump to the previous untranslated singular message", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor somewhere in the file + moveCursorTo(editor, 54, 0); + // move to next untranslated singular message and check new position + vscgettext.moveToPreviousUntranslatedMessage(editor); + assertCursorAt(editor, 51, 11); + }) + .then(done, done); + }); + + test("jump to the next untranslated plural message", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor on the first untranslated plural message + moveCursorTo(editor, 53, 0); + // move to next untranslated plural message and check new position + vscgettext.moveToNextUntranslatedMessage(editor); + assertCursorAt(editor, 57, 11); + }) + .then(done, done); + }); + + test("jump to the previous untranslated plural message", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor on the last untranslated plural message + moveCursorTo(editor, 63, 0); + // move to next untranslated plural message and check new position + vscgettext.moveToPreviousUntranslatedMessage(editor); + assertCursorAt(editor, 57, 11); + }) + .then(done, done); + }); + + test("jump to the next fuzzy singular message", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor on the first fuzzy singular message + moveCursorTo(editor, 53, 0); + // move to next fuzzy singular message and check new position + vscgettext.moveToNextFuzzyMessage(editor); + assertCursorAt(editor, 63, 11); + }) + .then(done, done); + }); + + test("jump to the previous fuzzy singular message", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor on the last fuzzy singular message + moveCursorTo(editor, 67, 0); + // move to next fuzzy singular message and check new position + vscgettext.moveToPreviousFuzzyMessage(editor); + assertCursorAt(editor, 63, 11); + }) + .then(done, done); + }); + + test("jump to the next untranslated or fuzzy message (untranslated)", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor on the first untranslated message + moveCursorTo(editor, 10, 0); + // move to next untranslated or fuzzy message and check new position + vscgettext.moveToNextUntranslatedOrFuzzyMessage(editor); + assertCursorAt(editor, 20, 8); + }) + .then(done, done); + }); + + test("jump to the next untranslated or fuzzy message (fuzzy)", (done) => { + openFile(inputpath("messages.po")) + .then((editor) => { + // put the cursor on the first fuzzy message + moveCursorTo(editor, 33, 0); + // move to next untranslated or fuzzy message and check new position + vscgettext.moveToNextUntranslatedOrFuzzyMessage(editor); + assertCursorAt(editor, 38, 8); + }) + .then(done, done); + }); }); diff --git a/test/inputfiles/messages.po b/test/inputfiles/messages.po index 73bcd26..18716db 100644 --- a/test/inputfiles/messages.po +++ b/test/inputfiles/messages.po @@ -38,6 +38,30 @@ msgstr "" msgid "new text" msgstr "old translation" +msgid "foo" +msgid_plural "foos" +msgstr[0] "foo" +msgstr[1] "foos" + msgctxt "Person" msgid "agent_type" msgstr "" + +msgid "apple" +msgid_plural "apples" +msgstr[0] "" +msgstr[1] "" + +msgid "banana" +msgid_plural "bananas" +msgstr[0] "banano" +msgstr[1] "" + +#, fuzzy +#| msgid "anime" +msgid "manga" +msgid_plural "mangas" +msgstr[0] "animeo" + +msgid "msg" +msgstr "msgstr"