mirror of
https://github.com/We-Dont-Byte/Mind_Reader.git
synced 2024-11-15 03:35:59 +00:00
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:
parent
94fda2de21
commit
b42eea172b
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
522
src/commands.ts
522
src/commands.ts
@ -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
68
src/commands/access.ts
Executable 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
10
src/commands/commandEntry.ts
Executable 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
174
src/commands/hub.ts
Executable 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
5
src/commands/index.ts
Executable 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
116
src/commands/nav.ts
Executable 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
168
src/commands/text.ts
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 = {
|
||||
|
Loading…
Reference in New Issue
Block a user