diff --git a/.gitignore b/.gitignore index 7e5e08c..2c9c7ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ out node_modules - +.DS_Store diff --git a/.vscode/launch.json b/.vscode/launch.json index 4938f32..0a21ddc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,16 +1,20 @@ // A launch configuration that compiles the extension and then opens it inside a new window { - "version": "0.1.0", + "version": "0.2.0", "configurations": [ { "name": "Launch Extension", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceRoot}"], + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}" + ], "stopOnEntry": false, "sourceMaps": true, - "outDir": "${workspaceRoot}/out/src", + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ], "preLaunchTask": "npm" }, { @@ -18,10 +22,15 @@ "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test"], + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}", + "--extensionTestsPath=${workspaceRoot}/out/test" + ], "stopOnEntry": false, "sourceMaps": true, - "outDir": "${workspaceRoot}/out/test", + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ], "preLaunchTask": "npm" } ] diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 337860a..411891c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -5,26 +5,24 @@ // ${fileDirname}: the current opened file's dirname // ${fileExtname}: the current opened file's extension // ${cwd}: the current working directory of the spawned process - // A task runner that calls a custom npm script that compiles the extension. { - "version": "0.1.0", - + "version": "2.0.0", // we want to run npm "command": "npm", - // the command is a shell script "isShellCommand": true, - // show the output window only if unrecognized errors occur. "showOutput": "silent", - // we run the custom script "compile" as defined in package.json - "args": ["run", "compile", "--loglevel", "silent"], - + "args": [ + "run", + "compile", + "--loglevel", + "silent" + ], // The tsc compiler is started in watching mode - "isWatching": true, - + "isBackground": true, // use the standard tsc in watch mode problem matcher to find compile problems in the output. "problemMatcher": "$tsc-watch" } diff --git a/README.md b/README.md index 10e263f..25e79ed 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,15 @@ Jumpy settings can be configured by adding entries into your `settings.json` (Fi `"jumpy.lightThemeForeground"`: Text color of Jumpy decoration in light themes +`"jumpy.codeCreatingMode"`: Mode of creating code lenght + +* Fixed: Always the same length. And you can set code length. +* Variable: According to the target number, code length is variable. + +`"jumpy.codeChars"`: Characters used to generate code + +`"jumpy.fixedCodeLength"`: [Fixed mode only] Fixed code length, need to greater than 1 + ## Support [Create an issue](https://github.com/wmaurer/vscode-jumpy/issues) diff --git a/package.json b/package.json index 03b70fd..867c227 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,25 @@ "type": "string", "default": "white", "description": "Text color of Jumpy decoration in light themes" + }, + "jumpy.codeCreatingMode": { + "type": "string", + "default": "Fixed", + "description": "Mode of creating code length (Fixed or Variable)", + "enum": [ + "Fixed", + "Variable" + ] + }, + "jumpy.codeChars": { + "type": "string", + "default": "abcdefghijklmnopqrstuvwxyz", + "description": "Characters used to generate code" + }, + "jumpy.fixedCodeLength": { + "type": "number", + "default": 2, + "description": "[Fixed mode only] Fixed code length, need to greater than 1" } } } diff --git a/src/extension.ts b/src/extension.ts index 8364630..63de680 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,9 +3,11 @@ import * as vscode from 'vscode'; import { Decoration, - createCodeArray, + createCharArray, + createFixedCodeArray, + createVariableCodeArray, createDataUriCaches, - getCodeIndex, + getCodeIndices, getLines, createTextEditorDecorationType, createDecorationOptions, @@ -13,48 +15,75 @@ import { import { JumpyPosition, JumpyFn, jumpyWord, jumpyLine } from './jumpy-positions'; export function activate(context: vscode.ExtensionContext) { - const codeArray = createCodeArray(); - - // decorations, based on configuration - const editorConfig = vscode.workspace.getConfiguration('editor'); - const configuration = vscode.workspace.getConfiguration('jumpy'); + enum CodeType { + Fixed, + Variable + }; - let fontFamily = configuration.get('fontFamily'); - fontFamily = fontFamily || editorConfig.get('fontFamily'); + // configrations + let fontFamily: string = ""; + let fontSize: number = 1; + let darkDecoration: Decoration = null; + let lightDecoration: Decoration = null; - let fontSize = configuration.get('fontSize'); - fontSize = fontSize || editorConfig.get('fontSize') - 1; + let codeType: CodeType = CodeType.Fixed; + let codeChars: string = ""; + let fixedCodeLength: number = 1; - const colors = { - darkBgColor: configuration.get('darkThemeBackground'), - darkFgColor: configuration.get('darkThemeForeground'), - lightBgColor: configuration.get('lightThemeBackground'), - lightFgColor: configuration.get('lightThemeForeground'), - }; + loadConfiguration(); - const darkDecoration = { - bgColor: colors.darkBgColor, - fgColor: colors.darkFgColor, - fontFamily: fontFamily, - fontSize: fontSize, - }; - const lightDecoration = { - bgColor: colors.lightBgColor, - fgColor: colors.lightFgColor, - fontFamily: fontFamily, - fontSize: fontSize, - }; + // property + let positions: JumpyPosition[] = []; + let isJumpyMode: boolean = false; + let keysOfCode: string[] = []; - createDataUriCaches(codeArray, darkDecoration, lightDecoration); + let charArray: string[] = []; + let codeArray: string[] = []; + const decorationType = createTextEditorDecorationType(); - const decorationTypeOffset2 = createTextEditorDecorationType(darkDecoration); - const decorationTypeOffset1 = createTextEditorDecorationType(darkDecoration); + charArray = createCharArray(codeChars); + if (codeType == CodeType.Fixed) { + codeArray = createFixedCodeArray(charArray, fixedCodeLength); + createDataUriCaches(codeArray, darkDecoration, lightDecoration); + } - let positions: JumpyPosition[] = null; - let firstLineNumber = 0; - let isJumpyMode: boolean = false; setJumpyMode(false); - let firstKeyOfCode: string = null; + + console.log(fontFamily) + + function loadConfiguration() { + const editorConfig = vscode.workspace.getConfiguration('editor'); + const configuration = vscode.workspace.getConfiguration('jumpy'); + + fontFamily = configuration.get('fontFamily'); + fontFamily = fontFamily || editorConfig.get('fontFamily'); + fontSize = configuration.get('fontSize'); + fontSize = fontSize || editorConfig.get('fontSize') - 1; + + const colors = { + darkBgColor: configuration.get('darkThemeBackground'), + darkFgColor: configuration.get('darkThemeForeground'), + lightBgColor: configuration.get('lightThemeBackground'), + lightFgColor: configuration.get('lightThemeForeground'), + }; + + darkDecoration = { + bgColor: colors.darkBgColor, + fgColor: colors.darkFgColor, + fontFamily: fontFamily, + fontSize: fontSize, + }; + lightDecoration = { + bgColor: colors.lightBgColor, + fgColor: colors.lightFgColor, + fontFamily: fontFamily, + fontSize: fontSize, + }; + + codeType = CodeType[configuration.get('codeCreatingMode')]; + codeChars = configuration.get('codeChars'); + fixedCodeLength = Math.max(configuration.get('fixedCodeLength'), 1); + } function setJumpyMode(value: boolean) { isJumpyMode = value; @@ -65,50 +94,35 @@ export function activate(context: vscode.ExtensionContext) { const editor = vscode.window.activeTextEditor; const getLinesResult = getLines(editor); - positions = jumpyFn(codeArray.length, getLinesResult.firstLineNumber, getLinesResult.lines, regexp); - - const decorationsOffset2 = positions - .map( - (position, i) => - position.charOffset == 1 - ? null - : createDecorationOptions( - position.line, - position.character, - position.character + 2, - context, - codeArray[i], - ), - ) - .filter(x => !!x); - - const decorationsOffset1 = positions - .map( - (position, i) => - position.charOffset == 2 - ? null - : createDecorationOptions( - position.line, - position.character, - position.character + 2, - context, - codeArray[i], - ), - ) - .filter(x => !!x); - - editor.setDecorations(decorationTypeOffset2, decorationsOffset2); - editor.setDecorations(decorationTypeOffset1, decorationsOffset1); + const maxDecorations = (codeType == CodeType.Fixed) ? codeArray.length : -1; + positions = jumpyFn(maxDecorations, getLinesResult.firstLineNumber, getLinesResult.lines, regexp); + + if (codeType == CodeType.Variable) { + codeArray = createVariableCodeArray(charArray, positions.length); + createDataUriCaches(codeArray, darkDecoration, lightDecoration); + } + + const decorations = positions + .map((position, i) => + createDecorationOptions( + position.line, + position.character, + codeArray[i] + ) + ); + console.log(decorations) + + editor.setDecorations(decorationType, decorations); setJumpyMode(true); - firstKeyOfCode = null; + keysOfCode = []; } function exitJumpyMode() { const editor = vscode.window.activeTextEditor; setJumpyMode(false); - editor.setDecorations(decorationTypeOffset2, []); - editor.setDecorations(decorationTypeOffset1, []); + + editor.setDecorations(decorationType, []); } const jumpyWordDisposable = vscode.commands.registerCommand('extension.jumpy-word', () => { @@ -136,22 +150,39 @@ export function activate(context: vscode.ExtensionContext) { const editor = vscode.window.activeTextEditor; const text: string = args.text; - if (text.search(/[a-z]/i) === -1) { + let regexp = new RegExp('[' + charArray.join("") + ']', 'i'); + if (text.search(regexp) === -1) { exitJumpyMode(); return; } - if (!firstKeyOfCode) { - firstKeyOfCode = text; + keysOfCode.push(text); + const indices: number[] = getCodeIndices(positions.length, codeArray, keysOfCode.join("").toLowerCase()); + + // invalid char + if (indices.length == 0) { + exitJumpyMode(); return; } - const code = firstKeyOfCode + text; - const position = positions[getCodeIndex(code.toLowerCase())]; + // not unique + if (indices.length != 1) { + const decorations = indices + .map((index, i) => + createDecorationOptions( + positions[index].line, + positions[index].character, + codeArray[index] + ) + ) + + editor.setDecorations(decorationType, decorations); + return; + } - editor.setDecorations(decorationTypeOffset2, []); - editor.setDecorations(decorationTypeOffset1, []); + editor.setDecorations(decorationType, []); + const position = positions[indices[0]]; vscode.window.activeTextEditor.selection = new vscode.Selection( position.line, position.character, @@ -173,6 +204,17 @@ export function activate(context: vscode.ExtensionContext) { const didChangeActiveTextEditorDisposable = vscode.window.onDidChangeActiveTextEditor(event => exitJumpyMode()); context.subscriptions.push(didChangeActiveTextEditorDisposable); + + const didChangeConfigurationDisposable = vscode.workspace.onDidChangeConfiguration(event => { + loadConfiguration(); + + charArray = createCharArray(codeChars); + if (codeType == CodeType.Fixed) { + codeArray = createFixedCodeArray(charArray, fixedCodeLength); + createDataUriCaches(codeArray, darkDecoration, lightDecoration); + } + }); + context.subscriptions.push(didChangeConfigurationDisposable); } -export function deactivate() {} +export function deactivate() { } diff --git a/src/jumpy-positions.ts b/src/jumpy-positions.ts index 0aa5ada..33dcefe 100644 --- a/src/jumpy-positions.ts +++ b/src/jumpy-positions.ts @@ -1,7 +1,6 @@ export interface JumpyPosition { line: number; character: number; - charOffset: number; } export interface JumpyFn { @@ -14,16 +13,14 @@ export function jumpyWord( lines: string[], regexp: RegExp, ): JumpyPosition[] { - let positionIndex = 0; const positions: JumpyPosition[] = []; - for (let i = 0; i < lines.length && positionIndex < maxDecorations; i++) { + for (let i = 0; i < lines.length && isValidIndex(positions.length, maxDecorations); i++) { let lineText = lines[i]; let word: RegExpExecArray; - while (!!(word = regexp.exec(lineText)) && positionIndex < maxDecorations) { + while (!!(word = regexp.exec(lineText)) && isValidIndex(positions.length, maxDecorations)) { positions.push({ line: i + firstLineNumber, character: word.index, - charOffset: 2, }); } } @@ -36,16 +33,19 @@ export function jumpyLine( lines: string[], regexp: RegExp, ): JumpyPosition[] { - let positionIndex = 0; const positions: JumpyPosition[] = []; - for (let i = 0; i < lines.length && positionIndex < maxDecorations; i++) { + for (let i = 0; i < lines.length && isValidIndex(positions.length, maxDecorations); i++) { if (!lines[i].match(regexp)) { positions.push({ line: i + firstLineNumber, character: 0, - charOffset: lines[i].length == 1 ? 1 : 2, }); } } return positions; } + +function isValidIndex(index: number, max: number): boolean { + if (max < 0) return true; + return (index < max); +}; diff --git a/src/jumpy-vscode.ts b/src/jumpy-vscode.ts index f912443..91c859e 100644 --- a/src/jumpy-vscode.ts +++ b/src/jumpy-vscode.ts @@ -1,22 +1,46 @@ import * as vscode from 'vscode'; -const plusMinusLines = 60; -const numCharCodes = 26; - -export function createCodeArray(): string[] { - const codeArray = new Array(numCharCodes * numCharCodes); - let codeIndex = 0; - for (let i = 0; i < numCharCodes; i++) { - for (let j = 0; j < numCharCodes; j++) { - codeArray[codeIndex++] = String.fromCharCode(97 + i) + String.fromCharCode(97 + j); - } - } - return codeArray; -} - let darkDataUriCache: { [index: string]: vscode.Uri } = {}; let lightDataUriCache: { [index: string]: vscode.Uri } = {}; +export function createCharArray(chars: string): string[] { + return chars.split("").filter(function (x, i, self) { + return self.indexOf(x) === i; + }); +} + +export function createFixedCodeArray(chars: string[], length: number): string[] { + let createArray = function (arr: string[], word: string, d: number) { + d -= 1; + chars.forEach(function (val: string) { + if (d <= 0) { + arr.push(word + val) + } + else { + createArray(arr, word + val, d); + } + }); + return arr; + }; + + return createArray([], '', length);; +}; + +// Returns a list of hint strings which will uniquely identify the given number of links.The hint strings may be of different lengths. +// https://github.com/philc/vimium +export function createVariableCodeArray(chars: string[], count: number): string[] { + let hints = [""]; + let offset = 0; + while ((hints.length - offset < count) || (hints.length == 1)) { + let hint = hints[offset++]; + chars.forEach(function (val: string) { + hints.push(hint + val); + }); + } + + return hints.slice(offset, offset + count); +} + export interface Decoration { bgColor: string; fgColor: string; @@ -26,26 +50,30 @@ export interface Decoration { } export function createDataUriCaches(codeArray: string[], darkDecoration: Decoration, lightDecoration: Decoration) { + darkDataUriCache = {} + lightDataUriCache = {} codeArray.forEach(code => (darkDataUriCache[code] = getSvgDataUri(code, darkDecoration))); codeArray.forEach(code => (lightDataUriCache[code] = getSvgDataUri(code, lightDecoration))); } -export function getCodeIndex(code: string): number { - return (code.charCodeAt(0) - 97) * numCharCodes + code.charCodeAt(1) - 97; +export function getCodeIndices(positionCount: number, codeArray: string[], code: string): number[] { + let regexp = new RegExp('^' + code + '.*', 'i'); + return codeArray + .slice(0, positionCount) + .map((val, i) => (val.search(regexp) == -1) ? null : i) + .filter(x => x != null); } export function getLines(editor: vscode.TextEditor): { firstLineNumber: number; lines: string[] } { const document = editor.document; - const activePosition = editor.selection.active; + const range = editor.visibleRanges; - const startLine = activePosition.line < plusMinusLines ? 0 : activePosition.line - plusMinusLines; - const endLine = - document.lineCount - activePosition.line < plusMinusLines - ? document.lineCount - : activePosition.line + plusMinusLines; + // get current visible lines + const startLine = Math.max(range[0].start.line - 1, 0); + const endLine = Math.min(range[0].end.line + 1, document.lineCount - 1); const lines: string[] = []; - for (let i = startLine; i < endLine; i++) { + for (let i = startLine; i <= endLine; i++) { lines.push(document.lineAt(i).text); } @@ -55,36 +83,27 @@ export function getLines(editor: vscode.TextEditor): { firstLineNumber: number; }; } -export function createTextEditorDecorationType(dec: Decoration) { - const width = dec.fontSize + 6; - const left = -width + 2; - +export function createTextEditorDecorationType(): vscode.TextEditorDecorationType { return vscode.window.createTextEditorDecorationType({ - after: { - margin: `0 0 0 ${left}px`, - height: '${dec.fontSize}px', - width: `${width}px`, + before: { + // this is very tricky... + margin: `0 0 0 0px; position: relative; z-index: 99`, + width: `0px` }, }); } -export function createDecorationOptions( - line: number, - startCharacter: number, - endCharacter: number, - context: vscode.ExtensionContext, - code: string, -): vscode.DecorationOptions { +export function createDecorationOptions(line: number, start: number, code: string): vscode.DecorationOptions { return { - range: new vscode.Range(line, startCharacter, line, endCharacter), + range: new vscode.Range(line, start, line, start + code.length), renderOptions: { dark: { - after: { + before: { contentIconPath: darkDataUriCache[code], }, }, light: { - after: { + before: { contentIconPath: lightDataUriCache[code], }, }, @@ -93,14 +112,12 @@ export function createDecorationOptions( } function getSvgDataUri(code: string, dec: Decoration) { - const width = dec.fontSize + 6; - - // prettier-ignore - let svg = ``; - // prettier-ignore - svg += ``; - // prettier-ignore - svg += ``; + const width = (dec.fontSize / 2.0) * code.length + 4; + const height = dec.fontSize + 2; + + let svg = ``; + svg += ``; + svg += ``; svg += code; svg += ``;