From b42eea172b854fe637590c590752150e1f105eeb Mon Sep 17 00:00:00 2001 From: jakergrossman Date: Fri, 3 Dec 2021 22:26:35 -0600 Subject: [PATCH] Split command categories into separate files Further separate commands so that each category of command is in it's own file. The goal is to keep the number of callbacks defined for each command to a reasonable amount per file. --- README.md | 6 + src/accessNodeProvider.ts | 47 ---- src/commands.ts | 522 ----------------------------------- src/commands/access.ts | 68 +++++ src/commands/commandEntry.ts | 10 + src/commands/hub.ts | 174 ++++++++++++ src/commands/index.ts | 5 + src/commands/nav.ts | 116 ++++++++ src/commands/text.ts | 168 +++++++++++ src/extension.ts | 16 +- src/hubManager.ts | 17 +- 11 files changed, 576 insertions(+), 573 deletions(-) delete mode 100644 src/accessNodeProvider.ts delete mode 100644 src/commands.ts create mode 100755 src/commands/access.ts create mode 100755 src/commands/commandEntry.ts create mode 100755 src/commands/hub.ts create mode 100755 src/commands/index.ts create mode 100755 src/commands/nav.ts create mode 100755 src/commands/text.ts diff --git a/README.md b/README.md index ef81dc4..95bbb0b 100644 --- a/README.md +++ b/README.md @@ -121,5 +121,11 @@ Development Host](https://code.visualstudio.com/api/advanced-topics/extension-ho --- +If you get an error about a `NODE_MODULE_VERSION` incompatibility or that a file is not a Windows executable, +this is likely the error described above. Run `npm i -g electron-rebuild` if you have not done so and follow the +directions above. + +--- + See the Visual Studio Code [getting started](https://code.visualstudio.com/api/get-started/your-first-extension) API page if you need more help. diff --git a/src/accessNodeProvider.ts b/src/accessNodeProvider.ts deleted file mode 100644 index 9b0db83..0000000 --- a/src/accessNodeProvider.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as vscode from 'vscode'; - -// list of all actions -let actions: AccessAction[] = []; - -class AccessAction extends vscode.TreeItem { - constructor( - public readonly label: string, - public readonly command: vscode.Command - ) { - super(label, vscode.TreeItemCollapsibleState.None); - } -}; - -export default class AccessNodeProvider implements vscode.TreeDataProvider { - public getTreeItem(a: AccessAction): vscode.TreeItem { - return a; - } - - public async getChildren(): Promise { - if (actions.length === 0) { - // fetch and cache mind-reader options - let cmds: string[] = await vscode.commands.getCommands(true); // get non-builtin commands - cmds = cmds.filter(x => x.startsWith('mind-reader')); // filter mind-reader commands - - cmds.forEach(c => { - let humanReadable = c.replace(/^mind-reader\./, ''); // strip extensions name - - // Convert camelCaseText to Title Case Text - humanReadable = humanReadable.replace(/([A-Z])/g, ' $1'); - humanReadable = humanReadable.charAt(0).toUpperCase() + humanReadable.slice(1); - - // add item to actions - actions.push(new AccessAction( - humanReadable, - { - title: humanReadable, - command: c, - tooltip: humanReadable - } - )); - }); - } - - return Promise.resolve(actions); - } -} diff --git a/src/commands.ts b/src/commands.ts deleted file mode 100644 index 2970d2a..0000000 --- a/src/commands.ts +++ /dev/null @@ -1,522 +0,0 @@ -import * as vscode from 'vscode'; -import * as pl from './pylex'; -import * as path from 'path'; -import * as fs from 'fs'; - -import HubManager from './hubManager'; - -/** - * @type {Object} Command // Command to register with the VS Code Extension API - * @prop {string} command // Name of the command; e.g., 'mind-reader.selectTheme' - * @prop {callback} callback // Callback to register when `command` is invoked - */ -export type CommandEntry = { - name: string, - callback: () => void -}; - -// Accessibility Commands -export const accessCommands: CommandEntry[] = [ - { - name: 'mind-reader.selectTheme', - - // callbacks can be inlined... - callback: () => vscode.commands.executeCommand('workbench.action.selectTheme'), - }, - - { - name: 'mind-reader.increaseFontScale', - callback: increaseFontScale, // ...or factored out into separate functions below - }, - - { - name: 'mind-reader.decreaseFontScale', - callback: decreaseFontScale, - }, - - { - name: 'mind-reader.resetFontScale', - callback: resetFontScale, - }, - - { - name: 'mind-reader.increaseEditorScale', - callback: increaseEditorScale, - }, - - { - name: 'mind-reader.decreaseEditorScale', - callback: decreaseEditorScale, - }, - - { - name: 'mind-reader.resetEditorScale', - callback: resetEditorScale, - }, - - { - name: 'mind-reader.getIndent', - callback: getIndent, - }, - - { - name: 'mind-reader.runLineContext', - callback: runLineContext, - }, - - { - name: 'mind-reader.runCursorContext', - callback: runCursorContext - } -]; - -export const navCommands: CommandEntry[] = [ - { - name: 'mind-reader.openWebview', - callback: openWebview, - }, - - { - name: 'mind-reader.openKeyBindWin', - callback: () => openKeyBindWin('Windows') - }, - { - name: 'mind-reader.openKeyBindMac', - callback: () => openKeyBindWin('Mac'), - }, - - //Navigation Keys...... - { - name: 'mind-reader.showAllSymbols', - callback: () => vscode.commands.executeCommand('workbench.action.showAllSymbols'), - }, - - { - name: 'mind-reader.gotoLine', - callback: () => vscode.commands.executeCommand('workbench.action.gotoLine'), - }, - - { - name: 'mind-reader.quickOpen', - callback: () => vscode.commands.executeCommand('workbench.action.quickOpen'), - }, - - { - name: 'mind-reader.gotoSymbol', - callback: () => vscode.commands.executeCommand('workbench.action.gotoSymbol'), - }, - - { - name: 'mind-reader.showProblems', - callback: () => vscode.commands.executeCommand('workbench.actions.view.problems'), - }, - - { - name: 'mind-reader.nextInFiles', - callback: () => vscode.commands.executeCommand('editor.action.marker.nextInFiles'), - }, - - { - name: 'mind-reader.prevInFiles', - callback: () => vscode.commands.executeCommand('editor.action.marker.prevInFiles'), - }, - - { - name: 'mind-reader.showCommands', - callback: () => vscode.commands.executeCommand('workbench.action.showCommands'), - }, - - { - name: 'mind-reader.quickOpenPreviousRecentlyUsedEditorInGroup', - callback: () => vscode.commands.executeCommand('workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'), - }, - - { - name: 'mind-reader.navigateBack', - callback: () => vscode.commands.executeCommand('workbench.action.navigateBack'), - }, - - { - name: 'mind-reader.getQuickInputBack', - callback: () => vscode.commands.executeCommand('workbench.action.quickInputBack'), - }, - - { - name: 'mind-reader.navigateForward', - callback: () => vscode.commands.executeCommand('workbench.action.navigateForward'), - }, -]; - -export const hubCommands: CommandEntry[] = [ - { - name: 'mind-reader.connectHub', - callback: connectHub - }, - - { - name: 'mind-reader.disconnectHub', - callback: disconnectHub - }, - - { - name: 'mind-reader.uploadCurrentFile', - callback: uploadCurrentFile - }, - - { - name: 'mind-reader.runProgram', - callback: runProgram - }, - - { - name: 'mind-reader.stopExecution', - callback: stopExecution - }, - - { - name: 'mind-reader.deleteProgram', - callback: deleteProgram - }, -]; - -// COMMAND CALLBACK IMPLEMENTATIONS - -function increaseFontScale(): void { - vscode.commands.executeCommand('editor.action.fontZoomIn'); -} - -function decreaseFontScale(): void { - vscode.commands.executeCommand('editor.action.fontZoomOut'); -} - -function resetFontScale(): void { - vscode.commands.executeCommand('editor.action.fontZoomReset'); -} - -function increaseEditorScale(): void { - vscode.commands.executeCommand('workbench.action.zoomIn'); -} - -function decreaseEditorScale(): void { - vscode.commands.executeCommand('workbench.action.zoomOut'); -} - -function resetEditorScale(): void { - vscode.commands.executeCommand('workbench.action.zoomReset'); -} - -function openWebview(): void { - //vscode.commands.executeCommand('workbench.action.zoomOut'); - const panel = vscode.window.createWebviewPanel( - 'mindReader', // Identifies the type of the webview. Used internally - 'Mind Reader', // Title of the panel displayed to the user - vscode.ViewColumn.One, // Editor column to show the new webview panel in. - {} - ); // Webview options. More on these later. - - panel.webview.html = getWebviewContent('media/html/main.html'); -} - -function getWebviewContent(filepath: string) { - return fs.readFileSync(filepath, {encoding: 'utf-8'}); -} - -function openKeyBindWin(os: 'Mac' | 'Windows'): void { - //vscode.commands.executeCommand('workbench.action.zoomOut'); - const panel = vscode.window.createWebviewPanel( - 'mindReader', // Identifies the type of the webview. Used internally - 'MR Key Bindings', // Title of the panel displayed to the user - vscode.ViewColumn.One, // Editor column to show the new webview panel in. - {} - ); // Webview options. More on these later. - - if (os === 'Windows') { - panel.webview.html = getWebviewContent('media/html/winkeys.html'); - } else if (os === 'Mac') { - panel.webview.html = getWebviewContent('media/html/mackeys.html'); - } -} - -function getIndent(): void { - let editor = vscode.window.activeTextEditor; - if(editor) - { - let lineNum = editor.selection.active.line + 1; - let textLine = editor.document.lineAt(lineNum - 1); - if(textLine.isEmptyOrWhitespace) - { - vscode.window.showInformationMessage("Line number " + lineNum.toString() + " Is Empty"); - } - else - { - // Grab tab format from open document - let tabFmt = { - size: editor.options.tabSize as number, - hard: !editor.options.insertSpaces - }; - let i = pl.Lexer.getIndent(textLine.text, tabFmt); - vscode.window.showInformationMessage("Line Number " + lineNum.toString() + " Indentation " + i.toString()); - } - } - else{ - vscode.window.showErrorMessage('No document currently active'); - } - -} - -function runLineContext(): void { - let editor = vscode.window.activeTextEditor; - if (editor){ - // current text and line number - let editorText = editor.document.getText(); - let line = editor.selection.active.line; - - // get tab info settings - let size = parseInt(editor.options.tabSize as string); - let hard = !editor.options.insertSpaces; - - // initialize parser - let parser = new pl.Parser(editorText, {size, hard}); - parser.parse(); - - let context = parser.context(line); - - // build text - let contentString = createContextString(context, line); - vscode.window.showInformationMessage(contentString); - } else { - vscode.window.showErrorMessage('No document currently active'); - } -} - -function createContextString(context: pl.LexNode[], line: number): string { - if (context.length < 1) { - throw new Error('Cannot create context string for empty context'); - } - - let contextString = 'Line ' + (line+1); // 1 based - if (context[0].token && context[0].token.attr) { - contextString += ': ' + context[0].token.type.toString() + ' ' + context[0].token.attr.toString(); - } - for (let i = 1; i < context.length; i++) { - let node = context[i]; - if (node.label === 'root') { - // root - contextString += ' in the Document Root'; - continue; - } - - if (node.token!.type !== pl.PylexSymbol.EMPTY && - node.token!.type !== pl.PylexSymbol.INDENT) { - contextString += ' inside ' + node.token!.type.toString(); - if (node.token!.attr) { - contextString += ' ' + node.token!.attr.toString(); - } - } - } - return contextString; -} - -// find up to `n` words around the cursor, where `n` is -// the value of `#mindReader.reader.contextWindow` -function runCursorContext(): void { - let editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showErrorMessage('RunCursorContext: No Active Editor'); - return; - } - - const cursorPos: vscode.Position = editor.selection.active; - const text: string = editor.document.lineAt(cursorPos).text; - const windowSize: number = vscode.workspace.getConfiguration('mindReader').get('reader.contextWindow')!; - - let trimmedText = text.trimStart(); // trim leading whitespace - let leadingWS = text.length - trimmedText.length; // # of characters of leading whitespace - trimmedText = trimmedText.trimEnd(); // trim trailing whitespace - let pos = leadingWS; - let maxPos = text.length; - - // clamp cursor start/end to new range - let col = cursorPos.character; // effective column of the cursor position - if (col < leadingWS) { - // move effective start to first non-whitespace character in the line - col = leadingWS; - } else if (col > leadingWS + trimmedText.length - 1) { - // move effective end to last non-whitespace character in the line - col = leadingWS + trimmedText.length - 1; - } - - // generate list of space separate words with range data (start, end) - // TODO: can find user position to be done in one pass - let spaceWords: {word: string, start: number, end: number}[] = []; - while (pos < maxPos && trimmedText.length > 0) { - let word = trimmedText.replace(/ .*/, ''); - spaceWords.push({word, start: pos, end: pos+word.length}); - - // remove processed word from trimmed text - const oldText = trimmedText; - trimmedText = trimmedText.replace(/[^ ]+/, '').trimStart(); - - // update pos to start of next word - pos += oldText.length - trimmedText.length; - } - - // find word the user is in - let contextStart: number = -1, contextEnd: number = -1; - for (let i = 0; i < spaceWords.length; i++) { - if (col >= spaceWords[i].start && col <= spaceWords[i].end) { - // found the word - contextStart = Math.max(0, i - windowSize); // clamp start index - contextEnd = Math.min(spaceWords.length, i + windowSize + 1); // clamp end index - - // construct cursor context string - let contextString = ''; - for (let i = contextStart; i < contextEnd; i++) { - contextString += spaceWords[i].word + ' '; - } - - // output cursor context string - vscode.window.showInformationMessage(contextString); - - return; - } - } -} - -// Current connected hub -let hub: HubManager | null = null; - -// TODO: port option -async function connectHub(): Promise { - if (hub && hub.isOpen()) { - vscode.window.showWarningMessage('LEGO Hub is already connected, reconnecting...'); - disconnectHub(); - } - - try { - const ports = await HubManager.queryPorts(); - - if (ports.length === 0) { - vscode.window.showErrorMessage('No ports found. Is the LEGO Hub connected?'); - return; - } - - let portPath: string | undefined = vscode.workspace.getConfiguration('mindReader.connection').get('portPath'); - - if (!portPath) { - let slots: vscode.QuickPickItem[] = []; - for (const port of ports) { - slots.push({ label: port.path }); - } - - let picked = await vscode.window.showQuickPick(slots); - - if (!picked) { - return; - } - - portPath = picked.label; - } - hub = await HubManager.create(portPath); - vscode.window.showInformationMessage('LEGO Hub connected'); - } catch (err) { - vscode.window.showErrorMessage('Could not connect to LEGO Hub'); - } -} - -async function disconnectHub(): Promise { - if (!hub || !hub.isOpen()) { - vscode.window.showErrorMessage('LEGO Hub is not connected'); - return; - } - - await hub.close(); - hub = null; - vscode.window.showInformationMessage('LEGO Hub disconnected'); -} - -async function uploadCurrentFile(): Promise { - if (!hub || !hub.isOpen()) { - vscode.window.showErrorMessage('LEGO Hub is not connected!'); - return; - } - - if (!vscode.window.activeTextEditor) { - vscode.window.showErrorMessage('No active text editor'); - return; - } - - const currentFilePath = vscode.window.activeTextEditor.document.fileName; - - if (currentFilePath) { - // construct quickpick - const slots: vscode.QuickPickItem[] = []; - for (let i = 0; i < 10; i++) { - slots.push({ label: i.toString() }); - } - const slotID = await vscode.window.showQuickPick(slots, { canPickMany: false }); - - if (!slotID) { - return; - } - - // TODO: progress bar? - vscode.window.showInformationMessage('Uploading current file'); - await hub.uploadFile(currentFilePath, parseInt(slotID.label), path.basename(currentFilePath)); - vscode.window.showInformationMessage(path.basename(currentFilePath) + ' uploaded to slot ' + slotID.label); - } -} - -// TODO: find empty slots -async function runProgram(): Promise { - if (!hub || !hub.isOpen()) { - vscode.window.showErrorMessage('LEGO Hub is not connected!'); - return; - } - - const slots: vscode.QuickPickItem[] = []; - // construct quickpick - for (let i = 0; i < 10; i++) { - slots.push({ label: i.toString() }); - } - const slotID = await vscode.window.showQuickPick(slots, { canPickMany: false }); - - if (!slotID) { - return; - } - - vscode.window.showInformationMessage('Running program ' + slotID.label); - await hub.programExecute(parseInt(slotID.label)); -} - -async function stopExecution(): Promise { - if (!hub || !hub.isOpen()) { - vscode.window.showErrorMessage('LEGO Hub is not connected!'); - return; - } - - await hub.programTerminate(); - vscode.window.showInformationMessage('Execution stopped'); -} - -// TODO: find slots from status -async function deleteProgram(): Promise { - if (!hub || !hub.isOpen()) { - vscode.window.showErrorMessage('LEGO Hub is not connected!'); - return; - } - - const slots: vscode.QuickPickItem[] = []; - // construct quickpick - for (let i = 0; i < 10; i++) { - slots.push({ label: i.toString() }); - } - const slotID = await vscode.window.showQuickPick(slots, { canPickMany: false }); - - if (!slotID) { - return; - } - - await hub.deleteProgram(parseInt(slotID.label)); - vscode.window.showInformationMessage('Deleted program ' + slotID.label); -} diff --git a/src/commands/access.ts b/src/commands/access.ts new file mode 100755 index 0000000..8c05327 --- /dev/null +++ b/src/commands/access.ts @@ -0,0 +1,68 @@ +import * as vscode from 'vscode'; +import { CommandEntry } from './commandEntry'; + +// Accessibility Commands +export const accessCommands: CommandEntry[] = [ + { + name: 'mind-reader.selectTheme', + + // callbacks can be inlined... + callback: () => vscode.commands.executeCommand('workbench.action.selectTheme'), + }, + + { + name: 'mind-reader.increaseFontScale', + callback: increaseFontScale, // ...or factored out into separate functions below + }, + + { + name: 'mind-reader.decreaseFontScale', + callback: decreaseFontScale, + }, + + { + name: 'mind-reader.resetFontScale', + callback: resetFontScale, + }, + + { + name: 'mind-reader.increaseEditorScale', + callback: increaseEditorScale, + }, + + { + name: 'mind-reader.decreaseEditorScale', + callback: decreaseEditorScale, + }, + + { + name: 'mind-reader.resetEditorScale', + callback: resetEditorScale, + }, +]; + + +function increaseFontScale(): void { + vscode.commands.executeCommand('editor.action.fontZoomIn'); +} + +function decreaseFontScale(): void { + vscode.commands.executeCommand('editor.action.fontZoomOut'); +} + +function resetFontScale(): void { + vscode.commands.executeCommand('editor.action.fontZoomReset'); +} + +function increaseEditorScale(): void { + vscode.commands.executeCommand('workbench.action.zoomIn'); +} + +function decreaseEditorScale(): void { + vscode.commands.executeCommand('workbench.action.zoomOut'); +} + +function resetEditorScale(): void { + vscode.commands.executeCommand('workbench.action.zoomReset'); +} + diff --git a/src/commands/commandEntry.ts b/src/commands/commandEntry.ts new file mode 100755 index 0000000..8461836 --- /dev/null +++ b/src/commands/commandEntry.ts @@ -0,0 +1,10 @@ +/** + * @type {Object} Command // Command to register with the VS Code Extension API + * @prop {string} command // Name of the command; e.g., 'mind-reader.selectTheme' + * @prop {callback} callback // Callback to register when `command` is invoked + */ +export type CommandEntry = { + name: string, + callback: () => void +}; + diff --git a/src/commands/hub.ts b/src/commands/hub.ts new file mode 100755 index 0000000..1b8bd03 --- /dev/null +++ b/src/commands/hub.ts @@ -0,0 +1,174 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import HubManager from '../hubManager'; + +import { CommandEntry } from './commandEntry'; + +export const hubCommands: CommandEntry[] = [ + { + name: 'mind-reader.connectHub', + callback: connectHub + }, + + { + name: 'mind-reader.disconnectHub', + callback: disconnectHub + }, + + { + name: 'mind-reader.uploadCurrentFile', + callback: uploadCurrentFile + }, + + { + name: 'mind-reader.runProgram', + callback: runProgram + }, + + { + name: 'mind-reader.stopExecution', + callback: stopExecution + }, + + { + name: 'mind-reader.deleteProgram', + callback: deleteProgram + }, +]; + +// Current connected hub +let hub: HubManager | null = null; + +async function connectHub(): Promise { + if (hub && hub.isOpen()) { + vscode.window.showWarningMessage('LEGO Hub is already connected, reconnecting...'); + disconnectHub(); + } + + try { + const ports = await HubManager.queryPorts(); + + if (ports.length === 0) { + vscode.window.showErrorMessage('No ports found. Is the LEGO Hub connected?'); + return; + } + + let portPath: string | undefined = vscode.workspace.getConfiguration('mindReader.connection').get('portPath'); + + if (!portPath) { + let slots: vscode.QuickPickItem[] = []; + for (const port of ports) { + slots.push({ label: port.path }); + } + + let picked = await vscode.window.showQuickPick(slots); + + if (!picked) { + return; + } + + portPath = picked.label; + } + hub = await HubManager.create(portPath); + vscode.window.showInformationMessage('LEGO Hub connected'); + } catch (err) { + vscode.window.showErrorMessage('Could not connect to LEGO Hub'); + } +} + +async function disconnectHub(): Promise { + if (!hub || !hub.isOpen()) { + vscode.window.showErrorMessage('LEGO Hub is not connected'); + return; + } + + await hub.close(); + hub = null; + vscode.window.showInformationMessage('LEGO Hub disconnected'); +} + +async function uploadCurrentFile(): Promise { + if (!hub || !hub.isOpen()) { + vscode.window.showErrorMessage('LEGO Hub is not connected!'); + return; + } + + if (!vscode.window.activeTextEditor) { + vscode.window.showErrorMessage('No active text editor'); + return; + } + + const currentFilePath = vscode.window.activeTextEditor.document.fileName; + + if (currentFilePath) { + // construct quickpick + const slots: vscode.QuickPickItem[] = []; + for (let i = 0; i < 10; i++) { + slots.push({ label: i.toString() }); + } + const slotID = await vscode.window.showQuickPick(slots, { canPickMany: false }); + + if (!slotID) { + return; + } + + // TODO: progress bar? + vscode.window.showInformationMessage('Uploading current file'); + await hub.uploadFile(currentFilePath, parseInt(slotID.label), path.basename(currentFilePath)); + vscode.window.showInformationMessage(path.basename(currentFilePath) + ' uploaded to slot ' + slotID.label); + } +} + +// TODO: find empty slots +async function runProgram(): Promise { + if (!hub || !hub.isOpen()) { + vscode.window.showErrorMessage('LEGO Hub is not connected!'); + return; + } + + const slots: vscode.QuickPickItem[] = []; + // construct quickpick + for (let i = 0; i < 10; i++) { + slots.push({ label: i.toString() }); + } + const slotID = await vscode.window.showQuickPick(slots, { canPickMany: false }); + + if (!slotID) { + return; + } + + vscode.window.showInformationMessage('Running program ' + slotID.label); + await hub.programExecute(parseInt(slotID.label)); +} + +async function stopExecution(): Promise { + if (!hub || !hub.isOpen()) { + vscode.window.showErrorMessage('LEGO Hub is not connected!'); + return; + } + + await hub.programTerminate(); + vscode.window.showInformationMessage('Execution stopped'); +} + +// TODO: find slots from status +async function deleteProgram(): Promise { + if (!hub || !hub.isOpen()) { + vscode.window.showErrorMessage('LEGO Hub is not connected!'); + return; + } + + const slots: vscode.QuickPickItem[] = []; + // construct quickpick + for (let i = 0; i < 10; i++) { + slots.push({ label: i.toString() }); + } + const slotID = await vscode.window.showQuickPick(slots, { canPickMany: false }); + + if (!slotID) { + return; + } + + await hub.deleteProgram(parseInt(slotID.label)); + vscode.window.showInformationMessage('Deleted program ' + slotID.label); +} diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100755 index 0000000..512519c --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,5 @@ +export{ CommandEntry } from './commandEntry'; +export{ accessCommands } from './access'; +export{ textCommands } from './text'; +export{ hubCommands } from './hub'; +export{ navCommands } from './nav'; \ No newline at end of file diff --git a/src/commands/nav.ts b/src/commands/nav.ts new file mode 100755 index 0000000..f694e40 --- /dev/null +++ b/src/commands/nav.ts @@ -0,0 +1,116 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; + +import { CommandEntry } from './commandEntry'; + +export const navCommands: CommandEntry[] = [ + { + name: 'mind-reader.openWebview', + callback: openWebview, + }, + + { + name: 'mind-reader.openKeyBindWin', + callback: () => openKeyBindWin('Windows') + }, + { + name: 'mind-reader.openKeyBindMac', + callback: () => openKeyBindWin('Mac'), + }, + + //Navigation Keys...... + { + name: 'mind-reader.showAllSymbols', + callback: () => vscode.commands.executeCommand('workbench.action.showAllSymbols'), + }, + + { + name: 'mind-reader.gotoLine', + callback: () => vscode.commands.executeCommand('workbench.action.gotoLine'), + }, + + { + name: 'mind-reader.quickOpen', + callback: () => vscode.commands.executeCommand('workbench.action.quickOpen'), + }, + + { + name: 'mind-reader.gotoSymbol', + callback: () => vscode.commands.executeCommand('workbench.action.gotoSymbol'), + }, + + { + name: 'mind-reader.showProblems', + callback: () => vscode.commands.executeCommand('workbench.actions.view.problems'), + }, + + { + name: 'mind-reader.nextInFiles', + callback: () => vscode.commands.executeCommand('editor.action.marker.nextInFiles'), + }, + + { + name: 'mind-reader.prevInFiles', + callback: () => vscode.commands.executeCommand('editor.action.marker.prevInFiles'), + }, + + { + name: 'mind-reader.showCommands', + callback: () => vscode.commands.executeCommand('workbench.action.showCommands'), + }, + + { + name: 'mind-reader.quickOpenPreviousRecentlyUsedEditorInGroup', + callback: () => vscode.commands.executeCommand('workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'), + }, + + { + name: 'mind-reader.navigateBack', + callback: () => vscode.commands.executeCommand('workbench.action.navigateBack'), + }, + + { + name: 'mind-reader.getQuickInputBack', + callback: () => vscode.commands.executeCommand('workbench.action.quickInputBack'), + }, + + { + name: 'mind-reader.navigateForward', + callback: () => vscode.commands.executeCommand('workbench.action.navigateForward'), + }, +]; + +// COMMAND CALLBACK IMPLEMENTATIONS +function openWebview(): void { + //vscode.commands.executeCommand('workbench.action.zoomOut'); + const panel = vscode.window.createWebviewPanel( + 'mindReader', // Identifies the type of the webview. Used internally + 'Mind Reader', // Title of the panel displayed to the user + vscode.ViewColumn.One, // Editor column to show the new webview panel in. + {} + ); // Webview options. More on these later. + + panel.webview.html = getWebviewContent('media/html/main.html'); +} + +function getWebviewContent(filepath: string) { + return fs.readFileSync(filepath, {encoding: 'utf-8'}); +} + +function openKeyBindWin(os: 'Mac' | 'Windows'): void { + //vscode.commands.executeCommand('workbench.action.zoomOut'); + const panel = vscode.window.createWebviewPanel( + 'mindReader', // Identifies the type of the webview. Used internally + 'MR Key Bindings', // Title of the panel displayed to the user + vscode.ViewColumn.One, // Editor column to show the new webview panel in. + {} + ); // Webview options. More on these later. + + if (os === 'Windows') { + panel.webview.html = getWebviewContent('media/html/winkeys.html'); + } else if (os === 'Mac') { + panel.webview.html = getWebviewContent('media/html/mackeys.html'); + } +} + + diff --git a/src/commands/text.ts b/src/commands/text.ts new file mode 100755 index 0000000..8e3beaf --- /dev/null +++ b/src/commands/text.ts @@ -0,0 +1,168 @@ +import * as vscode from 'vscode'; +import * as pl from '../pylex'; + +import { CommandEntry } from './commandEntry'; + +export const textCommands: CommandEntry[] = [ + { + name: 'mind-reader.getIndent', + callback: getIndent, + }, + + { + name: 'mind-reader.runLineContext', + callback: runLineContext, + }, + + { + name: 'mind-reader.runCursorContext', + callback: runCursorContext + } +] + +function getIndent(): void { + let editor = vscode.window.activeTextEditor; + if(editor) + { + let lineNum = editor.selection.active.line + 1; + let textLine = editor.document.lineAt(lineNum - 1); + if(textLine.isEmptyOrWhitespace) + { + vscode.window.showInformationMessage("Line number " + lineNum.toString() + " Is Empty"); + } + else + { + // Grab tab format from open document + let tabFmt = { + size: editor.options.tabSize as number, + hard: !editor.options.insertSpaces + }; + let i = pl.Lexer.getIndent(textLine.text, tabFmt); + vscode.window.showInformationMessage("Line Number " + lineNum.toString() + " Indentation " + i.toString()); + } + } + else{ + vscode.window.showErrorMessage('No document currently active'); + } + +} + +function runLineContext(): void { + let editor = vscode.window.activeTextEditor; + if (editor){ + // current text and line number + let editorText = editor.document.getText(); + let line = editor.selection.active.line; + + // get tab info settings + let size = parseInt(editor.options.tabSize as string); + let hard = !editor.options.insertSpaces; + + // initialize parser + let parser = new pl.Parser(editorText, {size, hard}); + parser.parse(); + + let context = parser.context(line); + + // build text + let contentString = createContextString(context, line); + vscode.window.showInformationMessage(contentString); + } else { + vscode.window.showErrorMessage('No document currently active'); + } +} + +function createContextString(context: pl.LexNode[], line: number): string { + if (context.length < 1) { + throw new Error('Cannot create context string for empty context'); + } + + let contextString = 'Line ' + (line+1); // 1 based + if (context[0].token && context[0].token.attr) { + contextString += ': ' + context[0].token.type.toString() + ' ' + context[0].token.attr.toString(); + } + for (let i = 1; i < context.length; i++) { + let node = context[i]; + if (node.label === 'root') { + // root + contextString += ' in the Document Root'; + continue; + } + + if (node.token!.type !== pl.PylexSymbol.EMPTY && + node.token!.type !== pl.PylexSymbol.INDENT) { + contextString += ' inside ' + node.token!.type.toString(); + if (node.token!.attr) { + contextString += ' ' + node.token!.attr.toString(); + } + } + } + return contextString; +} + +// find up to `n` words around the cursor, where `n` is +// the value of `#mindReader.reader.contextWindow` +function runCursorContext(): void { + let editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage('RunCursorContext: No Active Editor'); + return; + } + + const cursorPos: vscode.Position = editor.selection.active; + const text: string = editor.document.lineAt(cursorPos).text; + const windowSize: number = vscode.workspace.getConfiguration('mindReader').get('reader.contextWindow')!; + + let trimmedText = text.trimStart(); // trim leading whitespace + let leadingWS = text.length - trimmedText.length; // # of characters of leading whitespace + trimmedText = trimmedText.trimEnd(); // trim trailing whitespace + let pos = leadingWS; + let maxPos = text.length; + + // clamp cursor start/end to new range + let col = cursorPos.character; // effective column of the cursor position + if (col < leadingWS) { + // move effective start to first non-whitespace character in the line + col = leadingWS; + } else if (col > leadingWS + trimmedText.length - 1) { + // move effective end to last non-whitespace character in the line + col = leadingWS + trimmedText.length - 1; + } + + // generate list of space separate words with range data (start, end) + // TODO: can find user position to be done in one pass + let spaceWords: {word: string, start: number, end: number}[] = []; + while (pos < maxPos && trimmedText.length > 0) { + let word = trimmedText.replace(/ .*/, ''); + spaceWords.push({word, start: pos, end: pos+word.length}); + + // remove processed word from trimmed text + const oldText = trimmedText; + trimmedText = trimmedText.replace(/[^ ]+/, '').trimStart(); + + // update pos to start of next word + pos += oldText.length - trimmedText.length; + } + + // find word the user is in + let contextStart: number = -1, contextEnd: number = -1; + for (let i = 0; i < spaceWords.length; i++) { + if (col >= spaceWords[i].start && col <= spaceWords[i].end) { + // found the word + contextStart = Math.max(0, i - windowSize); // clamp start index + contextEnd = Math.min(spaceWords.length, i + windowSize + 1); // clamp end index + + // construct cursor context string + let contextString = ''; + for (let i = contextStart; i < contextEnd; i++) { + contextString += spaceWords[i].word + ' '; + } + + // output cursor context string + vscode.window.showInformationMessage(contextString); + + return; + } + } +} + diff --git a/src/extension.ts b/src/extension.ts index ebcdb92..5496408 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,12 @@ import * as vscode from 'vscode'; import * as pl from './pylex'; -import { accessCommands, hubCommands, navCommands } from './commands'; +import { + accessCommands, + hubCommands, + navCommands, + textCommands +} from './commands'; import CommandNodeProvider from './commandNodeProvider'; import Logger from './log'; @@ -18,7 +23,12 @@ export function activate(context: vscode.ExtensionContext) { parser.parse('Beep Boop'); - let allCommands = accessCommands.concat(hubCommands).concat(navCommands); + const allCommands = [ + accessCommands, + hubCommands, + navCommands, + textCommands + ].flat(1); // Register Commands allCommands.forEach(command => { @@ -29,7 +39,7 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(disposable); }); - let accessProvider = new CommandNodeProvider(accessCommands); + let accessProvider = new CommandNodeProvider([accessCommands, textCommands].flat(1)); vscode.window.registerTreeDataProvider('accessActions', accessProvider); let hubProvider = new CommandNodeProvider(hubCommands); diff --git a/src/hubManager.ts b/src/hubManager.ts index 522f532..8d5bf7b 100644 --- a/src/hubManager.ts +++ b/src/hubManager.ts @@ -4,6 +4,21 @@ import * as fs from 'fs'; import { logger } from './extension'; +/** + * Communication with the Hub takes place using a modified version of the JSON RPC 1.0 protocol: + * https://en.wikipedia.org/wiki/JSON-RPC + * + * It is mostly the same, except that the field names are abbreviated to the first letter. So, + * + * FIELD -> ABBREVIATION + * ------ -> ---------- + * method -> m + * params -> p + * id -> i + * result -> r + * error -> e + */ + /** * @type RPCRequest an RPC request message * @@ -21,7 +36,7 @@ type RPCRequest = { * @type RPCResponse an RPC response message * * @prop {any?} `'r'` RPC response body - * @prop {Object?} `'e'` RPC error body + * @prop {Object?} `'e'` RPC error body (base64 encoded) * @prop {string|null} `'i'` required response ID. */ type RPCResponse = {