Skip to content

Expose state #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"version": "0.7.0",
"engines": {
"vscode": "^1.53.0"
"vscode": "^1.60.0"
},
"categories": [
"Other"
Expand All @@ -37,9 +37,22 @@
{
"command": "command-server.runCommand",
"title": "Read command description from a file and execute the command"
},
{
"command": "command-server.getState",
"title": "Get state"
},
{
"command": "command-server.updateCoreState",
"title": "Update state based, on current when clause context"
}
],
"keybindings": [
{
"command": "command-server.runCommand",
"key": "ctrl+shift+f11",
"mac": "cmd+shift+f11"
},
{
"command": "command-server.runCommand",
"key": "ctrl+shift+f17",
Expand All @@ -49,6 +62,41 @@
"command": "command-server.runCommand",
"key": "ctrl+shift+alt+p",
"mac": "cmd+shift+alt+p"
},
{
"command": "command-server.updateCoreState",
"key": "ctrl+shift+f10",
"mac": "cmd+shift+f10",
"args": {
"core.focus": null
}
},
{
"command": "command-server.updateCoreState",
"key": "ctrl+shift+f10",
"mac": "cmd+shift+f10",
"when": "terminalFocus",
"args": {
"core.focus": "terminal"
}
},
{
"command": "command-server.updateCoreState",
"key": "ctrl+shift+f10",
"mac": "cmd+shift+f10",
"when": "editorTextFocus",
"args": {
"core.focus": "editor"
}
},
{
"command": "command-server.updateCoreState",
"key": "ctrl+shift+f10",
"mac": "cmd+shift+f10",
"when": "inQuickOpen",
"args": {
"core.focus": "quickOpen"
}
}
],
"configuration": {
Expand Down Expand Up @@ -88,7 +136,7 @@
"@types/mocha": "^8.0.4",
"@types/node": "^15.12.1",
"@types/rimraf": "^3.0.0",
"@types/vscode": "^1.53.0",
"@types/vscode": "^1.60.0",
"@typescript-eslint/eslint-plugin": "^4.9.0",
"@typescript-eslint/parser": "^4.9.0",
"eslint": "^7.15.0",
Expand Down
4 changes: 2 additions & 2 deletions src/commandRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as vscode from "vscode";
import { readRequest, writeResponse } from "./io";
import { getResponsePath } from "./paths";
import { any } from "./regex";
import { Request, Response } from "./types";
import { Request } from "./types";

export default class CommandRunner {
allowRegex!: RegExp;
Expand Down Expand Up @@ -102,7 +102,7 @@ export default class CommandRunner {
});
} catch (err) {
await writeResponse(responseFile, {
error: err.message,
error: (err as Error).message,
uuid,
warnings,
});
Expand Down
13 changes: 13 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,16 @@ export const STALE_TIMEOUT_MS = 60000;
// The amount of time that client is expected to wait for VSCode to perform a
// command, in seconds
export const VSCODE_COMMAND_TIMEOUT_MS = 3000;

export const FOCUS_KEY = "core.focus";
export const EDITOR_FOCUSED = "editor";
export const TERMINAL_FOCUSED = "terminal";
export const UNKNOWN_FOCUSED = null;

export const TERMINALS_KEY = "core.terminals";
export const ACTIVE_TERMINAL_KEY = "core.activeTerminal";
export const VISIBLE_EDITORS_KEY = "core.visibleEditors";
export const ACTIVE_EDITOR_KEY = "core.activeEditor";
export const FOCUSED_EDITOR_KEY = "core.focusedEditor";
export const FOCUSED_TERMINAL_KEY = "core.focusedTerminal";
export const WORKSPACE_FOLDERS_KEY = "core.workspaceFolders";
82 changes: 82 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,100 @@ import * as vscode from "vscode";

import { initializeCommunicationDir } from "./initializeCommunicationDir";
import CommandRunner from "./commandRunner";
import State from "./state";
import { updateCoreState, updateTerminalState } from "./updateCoreState";
import StateUpdateSignaler from "./stateUpdateSignaler";
import SignallingState from "./signallingState";

interface Api {
globalState: vscode.Memento;
workspaceState: vscode.Memento;
}

interface KeyDescription {
key: string;
}

export function activate(context: vscode.ExtensionContext) {
initializeCommunicationDir();

const commandRunner = new CommandRunner();

const stateUpdateSignaler = new StateUpdateSignaler();
const globalState = new State(context.globalState);
const workspaceState = new State(context.workspaceState);

let stateUpdaterPromise: Promise<void> | null = null;

context.subscriptions.push(
vscode.commands.registerCommand(
"command-server.runCommand",
commandRunner.runCommand
),
vscode.commands.registerCommand(
"command-server.updateCoreState",
async (extraState: Record<string, unknown>) => {
stateUpdaterPromise = (async () => {
for (const [key, value] of Object.entries(extraState)) {
await workspaceState.update(key, value);
}

await updateCoreState(workspaceState, globalState);
})();

await stateUpdaterPromise;

stateUpdaterPromise = null;
}
),
vscode.commands.registerCommand(
"command-server.getState",
async (keys: KeyDescription[]) => {
if (stateUpdaterPromise != null) {
await stateUpdaterPromise;
}

return Object.fromEntries(
keys.map(({ key }) => [
key,
{
newValue:
workspaceState.get(key, null) ?? globalState.get(key, null),
},
])
);
}
)
);

function updateTerminals() {
updateTerminalState(workspaceState, globalState);
}

context.subscriptions.push(
vscode.window.onDidOpenTerminal(updateTerminals),
vscode.window.onDidCloseTerminal(updateTerminals),
vscode.window.onDidChangeActiveTerminal(updateTerminals)
// vscode.window.onDidChangeVisibleTextEditors(updateVisibleEditors),
// vscode.workspace.onDidChangeTextDocument(updateVisibleEditors),
// vscode.workspace.onDidCloseTextDocument(updateDocuments),
// vscode.workspace.onDidOpenTextDocument(updateDocuments)
);

// NB: we only signal if externals update; we don't bother for our own
// updates because update will shortly be requested
const api: Api = {
workspaceState: new SignallingState(
workspaceState,
stateUpdateSignaler.signalStateUpdated
),
globalState: new SignallingState(
globalState,
stateUpdateSignaler.signalStateUpdated
),
};

return api;
}

// this method is called when your extension is deactivated
Expand Down
4 changes: 4 additions & 0 deletions src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ export function getRequestPath() {
export function getResponsePath() {
return join(getCommunicationDirPath(), "response.json");
}

export function getStateUpdatedSignalPath() {
return join(getCommunicationDirPath(), "stateUpdatedSignal");
}
21 changes: 21 additions & 0 deletions src/signallingState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Memento } from "vscode";

export default class SignallingState implements Memento {
constructor(
private storage: Memento,
private signalStateUpdated: (key: string) => unknown
) {}

get<T>(key: string, defaultValue?: T): T | undefined {
return this.storage.get(key, defaultValue);
}

keys(): readonly string[] {
return this.storage.keys();
}

async update(key: string, value: any): Promise<void> {
await this.storage.update(key, value);
this.signalStateUpdated(key);
}
}
26 changes: 26 additions & 0 deletions src/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Memento } from "vscode";

const STORAGE_KEY = "pokey.command-server.state";

function getKey(key: string) {
return `${STORAGE_KEY}.${key}`;
}

export default class State implements Memento {
constructor(private storage: Memento) {}

get<T>(key: string, defaultValue?: T): T | undefined {
return this.storage.get(getKey(key), defaultValue);
}

keys(): readonly string[] {
return this.storage
.keys()
.filter((key) => key.startsWith(STORAGE_KEY))
.map((key) => key.substring(STORAGE_KEY.length + 1));
}

async update(key: string, value: any): Promise<void> {
await this.storage.update(getKey(key), value);
}
}
12 changes: 12 additions & 0 deletions src/stateUpdateSignaler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getStateUpdatedSignalPath } from "./paths";
import touch from "./touch";

export default class StateUpdateSignaler {
constructor() {
this.signalStateUpdated = this.signalStateUpdated.bind(this);
}

async signalStateUpdated(key: string) {
await touch(getStateUpdatedSignalPath());
}
}
22 changes: 22 additions & 0 deletions src/touch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// From https://gist.githubusercontent.com/remarkablemark/17c9c6a22a41510b2edfa3041ccca95a/raw/b11f087313b4c6f32733989ee24d65e1b643f007/touch-promise.js

import { close, open, utimes } from "fs";

const touch = (path: string) => {
return new Promise<void>((resolve, reject) => {
const time = new Date();
utimes(path, time, time, (err) => {
if (err) {
return open(path, "w", (err, fd) => {
if (err) {
return reject(err);
}
close(fd, (err) => (err ? reject(err) : resolve()));
});
}
resolve();
});
});
};

export default touch;
84 changes: 84 additions & 0 deletions src/updateCoreState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Terminal, TextDocument, TextEditor, window, workspace } from "vscode";
import {
ACTIVE_EDITOR_KEY,
ACTIVE_TERMINAL_KEY,
EDITOR_FOCUSED,
FOCUSED_EDITOR_KEY,
FOCUSED_TERMINAL_KEY,
FOCUS_KEY,
TERMINALS_KEY,
TERMINAL_FOCUSED,
VISIBLE_EDITORS_KEY,
WORKSPACE_FOLDERS_KEY,
} from "./constants";
import State from "./state";

export async function updateCoreState(
workspaceState: State,
globalState: State
) {
const focus = workspaceState.get<string | null>(FOCUS_KEY);

const editors = window.visibleTextEditors.map(serializeTextEditor);

const activeEditor =
window.activeTextEditor == null
? null
: serializeTextEditor(window.activeTextEditor);

await workspaceState.update(VISIBLE_EDITORS_KEY, editors);
await workspaceState.update(ACTIVE_EDITOR_KEY, activeEditor);
await workspaceState.update(
FOCUSED_EDITOR_KEY,
focus === EDITOR_FOCUSED ? activeEditor : null
);
await workspaceState.update(
FOCUSED_TERMINAL_KEY,
focus === TERMINAL_FOCUSED
? workspaceState.get<string | null>(ACTIVE_TERMINAL_KEY)
: null
);

await workspaceState.update(
WORKSPACE_FOLDERS_KEY,
workspace.workspaceFolders?.map((folder) => ({
uri: folder.uri.toString(),
name: folder.name,
})) ?? []
);
}

export async function updateTerminalState(
workspaceState: State,
globalState: State
) {
const terminals = await Promise.all(window.terminals.map(serializeTerminal));
const activeTerminal =
window.activeTerminal == null
? null
: await serializeTerminal(window.activeTerminal);
await workspaceState.update(TERMINALS_KEY, terminals);
await workspaceState.update(ACTIVE_TERMINAL_KEY, activeTerminal);
}

function serializeTextEditor(textEditor: TextEditor) {
return { document: serializeDocument(textEditor.document) };
}

function serializeDocument(document: TextDocument) {
const { uri, isUntitled, isDirty, fileName, languageId, version } = document;
return {
uri: uri.toString(),
isUntitled,
isDirty,
fileName,
languageId,
version,
};
}

async function serializeTerminal(terminal: Terminal) {
const { name } = terminal;

return { name, processId: await terminal.processId };
}
Loading