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.
This commit is contained in:
jakergrossman 2021-12-03 22:26:35 -06:00
parent 94fda2de21
commit b42eea172b
11 changed files with 576 additions and 573 deletions

View File

@ -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.

View File

@ -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<AccessAction> {
public getTreeItem(a: AccessAction): vscode.TreeItem {
return a;
}
public async getChildren(): Promise<AccessAction[]> {
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);
}
}

View File

@ -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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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);
}

68
src/commands/access.ts Executable file
View File

@ -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');
}

10
src/commands/commandEntry.ts Executable file
View File

@ -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
};

174
src/commands/hub.ts Executable file
View File

@ -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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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);
}

5
src/commands/index.ts Executable file
View File

@ -0,0 +1,5 @@
export{ CommandEntry } from './commandEntry';
export{ accessCommands } from './access';
export{ textCommands } from './text';
export{ hubCommands } from './hub';
export{ navCommands } from './nav';

116
src/commands/nav.ts Executable file
View File

@ -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');
}
}

168
src/commands/text.ts Executable file
View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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 = {