Skip to content
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

Front end and Zed support for new environment pane messages #297

Merged
merged 9 commits into from
Mar 17, 2023
205 changes: 190 additions & 15 deletions extensions/positron-zed/src/positronZedEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,28 @@ import { randomUUID } from 'crypto';
* ZedVar is a simple Zed variable.
*/
class ZedVariable {
// Zed variables do not currently support truncation.
public readonly truncated: boolean = false;
public readonly type_name;

constructor(
readonly name: string,
readonly value: string,
readonly kind: string
) { }
readonly kind: string,
readonly length: number,
readonly size: number,
) {
// The type name is the language-specific name for the variable's type.
// In Zed, the variable classes are named things like ZedNUMBER,
// ZedSTRING, etc.
this.type_name = `Zed${kind.toUpperCase()}`;

// The Zed language has a sample type named 'blob' that has its own Zed
// type, ZedBLOB, but is represented as a 'vector' in the environment.
if (this.kind === 'blob') {
this.kind = 'vector';
}
}
}

/**
Expand All @@ -41,12 +58,16 @@ export class ZedEnvironment {
constructor(readonly id: string,
private readonly zedVersion: string) {
// Create a few variables to start with
this._vars.set('z', new ZedVariable('z', 'zed1', 'string'));
this._vars.set('e', new ZedVariable('e', 'zed2', 'string'));
this._vars.set('d', new ZedVariable('d', 'zed3', 'string'));
this._vars.set('z', new ZedVariable('z', 'zed1', 'string', 4, 4));
this._vars.set('e', new ZedVariable('e', 'zed2', 'string', 4, 4));
this._vars.set('d', new ZedVariable('d', 'zed3', 'string', 4, 4));

// Create a Zed Version variable
this._vars.set('ZED_VERSION', new ZedVariable('ZED_VERSION', this.zedVersion, 'string'));
this._vars.set('ZED_VERSION', new ZedVariable('ZED_VERSION',
this.zedVersion,
'string',
this.zedVersion.length,
this.zedVersion.length));

setTimeout(() => {
// List the environment on the first tick after startup. There's no
Expand All @@ -69,43 +90,170 @@ export class ZedEnvironment {
case 'refresh':
this.emitFullList();
break;

// A request to clear the environment
case 'clear':
this.clearAllVars();
break;
}
}

/**
* Defines a number of variables at once.
*
* @param count The number of variables to define
* @param kind The kind of variable to define; defaults to 'string'
* @param kind The kind of variable to define; if not specified, a random kind will be chosen
*/
public defineVars(count: number, kind: string) {
// Select the kind of variable to define
const kindToUse = kind || 'string';

// Get the starting index for the new variables
const start = this._vars.size + 1;

// Ensure we don't collide with existing variables
// Begin building the list of new variables to send
const added = [];

for (let i = 0; i < count; i++) {
const name = `zed${start + i}`;
let kindToUse = kind;
if (!kind || kind === 'random') {
// Random: pick a random kind
kindToUse = ['string', 'number', 'vector', 'blob'][Math.floor(Math.random() * 4)];
}

const name = `${kindToUse}${start + i}`;
let value = '';

// Create a random value for the variable
let size = 0;
if (kindToUse === 'string') {
// Strings: use a random UUID
value = randomUUID();
size = value.length;
} else if (kindToUse === 'number') {
// Numbers: use a random number
value = Math.random().toString();
size = 4;
} else if (kindToUse === 'vector') {
// Vectors: Generate 5 random bytes
const bytes = [];
for (let i = 0; i < 5; i++) {
bytes.push(Math.floor(Math.random() * 256));
}
value = bytes.join(', ');
size = 5;
} else if (kindToUse === 'blob') {
size = Math.floor(Math.random() * 1024 * 1024);
value = `blob(${size} bytes)`;
} else {
// Everything else: use the counter
value = `value{start + i}`;
value = `value${start + i}`;
size = value.length;
}
this._vars.set(name, new ZedVariable(name, value, kindToUse));
const newZedVar = new ZedVariable(name, value, kindToUse, value.length, size);
added.push(newZedVar);

this._vars.set(name, newZedVar);
}

// Emit the new variables to the front end
this.emitFullList();
this.emitUpdate(added);
}

/**
* Updates some number of variables in the environment
*
* @param count The number of variables to update
* @returns The number of variables that were updated
*/
public updateVars(count: number): number {
// We can't update more variables than we have, so clamp the count to
// the number of variables in the environment.
if (count > this._vars.size) {
count = this._vars.size;
}

// Update the variables
const updated = [];
const randomKeys = this.selectRandomKeys(count);
for (const key of randomKeys) {
const oldVar = this._vars.get(key)!;
let value = '';
let size = 0;
// Create a random value for the variable
if (oldVar.kind === 'string') {
// Strings: replace 5 random characters with a hexadecimal digit
const chars = oldVar.value.split('');
for (let i = 0; i < 5; i++) {
const randomIndex = Math.floor(Math.random() * chars.length);
chars[randomIndex] = Math.floor(Math.random() * 16).toString(16);
}
value = chars.join('');
size = value.length;
} else if (oldVar.kind === 'number') {
// Numbers: just use a new random number
value = Math.random().toString();
size = 4;
} else if (oldVar.kind === 'vector') {
if (oldVar.value.startsWith('blob')) {
// Blobs are basically huge vectors. Randomly double or halve the size.
if (Math.random() < 0.5) {
size = oldVar.size * 2;
value = `blob(${size} bytes)`;
} else {
size = Math.floor(oldVar.size / 2);
value = `blob(${size} bytes)`;
}
} else {
// Vectors: replace 2 random bytes with new random bytes and add an extra byte
// at the end
const bytes = oldVar.value.split(',').map((x) => parseInt(x, 10));
for (let i = 0; i < 2; i++) {
const randomIndex = Math.floor(Math.random() * bytes.length);
bytes[randomIndex] = Math.floor(Math.random() * 256);
}
bytes.push(Math.floor(Math.random() * 256));
value = bytes.join(', ');
size = bytes.length;
}
} else {
// Everything else: reverse the value
value = oldVar.value.split('').reverse().join('');
size = value.length;
}

const newVar = new ZedVariable(oldVar.name, value, oldVar.kind, value.length, size);
this._vars.set(key, newVar);

// Add the variable to the list of updated variables
updated.push(newVar);
}

// Emit the updated variables to the front end
this.emitUpdate(updated);

return count;
}

/**
*
* @param count The number of variables to remove
* @returns The number of variables that were removed
*/
public removeVars(count: number): number {
// We can't remove more variables than we have, so clamp the count to
// the number of variables in the environment.
if (count > this._vars.size) {
count = this._vars.size;
}

// Remove the variables
const keys = this.selectRandomKeys(count);
for (const key of keys) {
this._vars.delete(key);
}

// Emit the removed variables to the front end
this.emitUpdate(undefined, keys);

return count;
}

/**
Expand All @@ -132,4 +280,31 @@ export class ZedEnvironment {
variables: vars
});
}

private emitUpdate(assigned?: Array<ZedVariable>, removed?: Array<string>) {
this._onDidEmitData.fire({
msg_type: 'update',
assigned: assigned || [],
removed: removed || []
});
}

/**
* Selects random variable name keys on which to perform some action
*
* @param count The number of keys to select
* @returns An array of keys representing the names of the selected variables
*/
private selectRandomKeys(count: number): Array<string> {
// Make a list of variables; we randomly select variables from the
// environment until we have the desired number.
const keys = Array.from(this._vars.keys());
const randomKeys = [];
for (let i = 0; i < count; i++) {
const randomIndex = Math.floor(Math.random() * keys.length);
randomKeys.push(keys[randomIndex]);
keys.splice(randomIndex, 1);
}
return randomKeys;
}
}
25 changes: 23 additions & 2 deletions extensions/positron-zed/src/positronZedLanguageRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ const HelpLines = [
'ansi hidden - Displays hidden text',
'ansi rgb - Displays RGB ANSI colors as foreground and background colors',
'code X Y - Simulates a successful X line input with Y lines of output (where X >= 1 and Y >= 0)',
'def X - Defines X variables',
'def X Y - Defines X variables of type Y',
'def X - Defines X variables (randomly typed)',
'def X Y - Defines X variables of type Y, where Y is one of: string, number, vector, or blob',
'env clear - Clears all variables from the environment',
'error X Y Z - Simulates an unsuccessful X line input with Y lines of error message and Z lines of traceback (where X >= 1 and Y >= 1 and Z >= 0)',
'help - Shows this help',
'offline - Simulates going offline for two seconds',
'progress - Renders a progress bar',
'rm X - Removes X variables',
'shutdown - Simulates orderly shutdown',
'update X - Updates X variables',
'version - Shows the Zed version'
].join('\n');

Expand Down Expand Up @@ -219,8 +221,27 @@ export class PositronZedLanguageRuntime implements positron.LanguageRuntime {
'No Environments',
'No environments are available to define variables in.', []);
}
} else if (match = code.match(/^update ([1-9]{1}[\d]*)/)) {
let count = +match[1];
if (this._environments.size > 0) {
for (const environment of this._environments.values()) {
count = environment.updateVars(count);
}
}
return this.simulateSuccessfulCodeExecution(id, code,
`Updated the values of ${count} variables.`);
} else if (match = code.match(/^rm ([1-9]{1}[\d]*)/)) {
let count = +match[1];
if (this._environments.size > 0) {
for (const environment of this._environments.values()) {
count = environment.removeVars(count);
}
}
return this.simulateSuccessfulCodeExecution(id, code,
`Removed ${count} variables.`);
}


// Process the "code".
switch (code) {
case '': {
Expand Down
17 changes: 15 additions & 2 deletions src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class ExtHostLanguageRuntimeAdapter implements ILanguageRuntime {
const client = new ExtHostRuntimeClientInstance<T>(id, type, this.handle, this._proxy);
this._clients.set(id, client);
this._logService.info(`Creating ${type} client '${id}'...`);
client.setClientState(RuntimeClientState.Opening);

// Kick off the creation of the client on the server side. There's no
// reply defined to this call in the protocol, so this is almost
Expand All @@ -148,8 +149,20 @@ class ExtHostLanguageRuntimeAdapter implements ILanguageRuntime {
// If the creation fails on the server, we'll either get an error here
// or see the server end get closed immediately via a CommClose message.
// In either case we'll let the client know.
this._proxy.$createClient(this.handle, id, type, params).catch((err) => {
this._logService.error(`Failed to create client '${id}' in runtime '${this.handle}': ${err}`);
this._proxy.$createClient(this.handle, id, type, params).then(() => {
// There is no protocol message indicating that the client has been
// successfully created, so presume it's connected once the message
// has been safely delivered, and handle the close event if it
// happens.
if (client.getClientState() === RuntimeClientState.Opening) {
client.setClientState(RuntimeClientState.Connected);
} else {
this._logService.warn(`Client '${id}' in runtime '${this.metadata.runtimeName}' ` +
`was closed before it could be created`);
}
}).catch((err) => {
this._logService.error(`Failed to create client '${id}' ` +
`in runtime '${this.metadata.runtimeName}': ${err}`);
client.setClientState(RuntimeClientState.Closed);
this._clients.delete(id);
});
Expand Down
Loading