Skip to content

Commit

Permalink
Merge pull request #166 from EXXETA/feature/160-variable-syntax-highl…
Browse files Browse the repository at this point in the history
…ighting

feat(160): Variable Syntax Highlighting and Completion
  • Loading branch information
SoulKa authored Jan 13, 2025
2 parents 155f89f + 0df5acc commit 1c8452e
Show file tree
Hide file tree
Showing 26 changed files with 869 additions and 102 deletions.
68 changes: 68 additions & 0 deletions __tests__/monaco-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { editor, IPosition, IRange } from 'monaco-editor';

class MockModel implements Partial<editor.ITextModel> {
constructor(private readonly lines: string[]) {}

getValueInRange(range: IRange) {
if (range.startLineNumber !== range.endLineNumber)
throw new Error('Range must be in the same line');
return this.lines[range.startLineNumber - 1].slice(range.startColumn - 1, range.endColumn - 1);
}

getLineMaxColumn(lineNumber: number) {
return this.lines[lineNumber - 1].length + 1;
}

getLineCount() {
return this.lines.length;
}

getLineContent(lineNumber: number) {
return this.lines[lineNumber - 1];
}

getWordAtPosition(position: IPosition) {
const prefixLength = /\w*$/.exec(
this.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
})
)[0].length;
const postfixLength = /^\w*/.exec(
this.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: position.column,
endLineNumber: position.lineNumber,
endColumn: this.getLineMaxColumn(position.lineNumber),
})
)[0].length;

const startColumn = position.column - prefixLength;
const endColumn = position.column + postfixLength;
if (startColumn === endColumn) {
return null;
}

return {
word: this.getValueInRange({
startLineNumber: position.lineNumber,
startColumn,
endLineNumber: position.lineNumber,
endColumn,
}),
startColumn,
endColumn,
};
}
}

/**
* Mock a monaco editor model.
* @param json The JSON content of the model as object.
*/
export function mockModel(json: object) {
const lines = JSON.stringify(json, null, 2).split('\n');
return new MockModel(lines) as Partial<editor.ITextModel> as editor.ITextModel;
}
23 changes: 6 additions & 17 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
const baseConfig = {
preset: 'ts-jest',
moduleFileExtensions: [
'js',
'jsx',
'ts',
'tsx',
'json',
],
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'],
moduleNameMapper: {
'\\.(css|less|sass|scss)$': 'identity-obj-proxy',
'^main/(.*)$': '<rootDir>/src/main/$1',
'^@/(.*)$': '<rootDir>/src/renderer/$1',
'^shim/(.*)$': '<rootDir>/src/shim/$1',
'^monaco-editor$': '<rootDir>/node_modules/@monaco-editor/react',
},
setupFilesAfterEnv: [
'./scripts/setup-jest.ts',
],
setupFilesAfterEnv: ['./scripts/setup-jest.ts'],
testEnvironmentOptions: {
url: 'http://localhost/',
},
Expand All @@ -32,17 +25,13 @@ module.exports = {
...baseConfig,
displayName: 'main',
testEnvironment: 'node',
testMatch: [
'<rootDir>/src/main/**/*.test.ts',
],
testMatch: ['<rootDir>/src/main/**/*.test.ts'],
},
{
...baseConfig,
displayName: 'renderer',
testEnvironment: 'jsdom',
testMatch: [
'<rootDir>/src/renderer/**/*.test.ts',
],
testMatch: ['<rootDir>/src/renderer/**/*.test.ts'],
},
],
};
};
63 changes: 13 additions & 50 deletions src/main/environment/service/environment-service.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import { Readable } from 'node:stream';
import { randomInt, randomUUID } from 'node:crypto';
import { TemplateReplaceStream } from 'template-replace-stream';
import { Initializable } from 'main/shared/initializable';
import { PersistenceService } from 'main/persistence/service/persistence-service';
import { Collection } from 'shim/objects/collection';
import { VariableObject } from 'shim/variables';
import { getSystemVariable, getSystemVariables } from './system-variable';

const persistenceService = PersistenceService.instance;

enum SystemVariable {
TimestampIso = '$timestampIso',
TimestampUnix = '$timestampUnix',
Time = '$time',
Date = '$date',
RandomInt = '$randomInt',
RandomUuid = '$randomUuid',
}

/**
* The environment service is responsible for managing the current collection and
* the system, collection, and request variables (the ones that can be used in the
Expand Down Expand Up @@ -56,7 +47,7 @@ export class EnvironmentService implements Initializable {
if (variable !== undefined) {
variable.value = value;
} else {
this.currentCollection.variables[key] = { value, enabled: true };
this.currentCollection.variables[key] = { key, value, isActive: true };
}
}

Expand All @@ -68,16 +59,7 @@ export class EnvironmentService implements Initializable {
*/
public setCollectionVariableEnabled(key: string, enabled: boolean) {
const variable = this.currentCollection.variables[key];
if (variable !== undefined) variable.enabled = enabled;
}

/**
* Replaces all variables in the current collection with the provided variables.
*
* @param variables The new variables to set.
*/
public setCollectionVariables(variables: Record<string, VariableObject>) {
this.currentCollection.variables = variables;
if (variable !== undefined) variable.isActive = enabled;
}

/**
Expand All @@ -90,13 +72,13 @@ export class EnvironmentService implements Initializable {
}

/**
* Returns the keys of all active variables in the current collection. This also includes system
* Returns all active variables in the current collection. This also includes system
* variables.
*/
public getActiveVariableKeys() {
return Object.keys(this.currentCollection.variables)
.filter((key) => this.currentCollection.variables[key].enabled)
.concat(Object.values(SystemVariable));
public getActiveVariables() {
return Object.values(this.currentCollection.variables)
.filter((variable) => variable.isActive)
.concat(getSystemVariables());
}

/**
Expand All @@ -105,32 +87,13 @@ export class EnvironmentService implements Initializable {
* 2. System variables
*
* @param key The key of the variable.
* @returns The value of the variable if it exists and is enabled, otherwise undefined.
* @returns The value of the variable if it exists, otherwise undefined.
*/
private getVariableValue(key: string) {
return this.currentCollection.variables[key]?.value ?? this.getSystemVariableValue(key);
public getVariable(key: string): VariableObject | undefined {
return this.currentCollection.variables[key] ?? getSystemVariable(key);
}

/**
* Returns the value of a dynamic, predefined system variable.
*
* @param key The key of the system variable.
* @returns The value of the system variable.
*/
private getSystemVariableValue(key: string) {
switch (key) {
case SystemVariable.TimestampIso:
return new Date().toISOString();
case SystemVariable.TimestampUnix:
return Math.floor(Date.now() / 1e3).toString();
case SystemVariable.Time:
return new Date().toTimeString();
case SystemVariable.Date:
return new Date().toDateString();
case SystemVariable.RandomInt:
return randomInt(2 ** 48 - 1).toString();
case SystemVariable.RandomUuid:
return randomUUID();
}
private getVariableValue(key: string) {
return this.getVariable(key)?.value;
}
}
56 changes: 56 additions & 0 deletions src/main/environment/service/system-variable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { getSystemVariable, getSystemVariableKeys, getSystemVariables } from './system-variable';

describe('getSystemVariable()', () => {
it('should return the the system variable object', () => {
// Arrange
const key = '$randomUuid';

// Act
const result = getSystemVariable(key);

// Assert
expect(result.key).toBe(key);
expect(result.isActive).toBe(true);
expect(result.value).toBeDefined();
expect(result.description).toBeDefined();
});

it('should return a random UUID for $randomUuid', () => {
// Arrange
const key = '$randomUuid';

// Act
const firstValue = getSystemVariable(key).value;
const secondValue = getSystemVariable(key).value;

// Assert
expect(firstValue).not.toBe(secondValue);
});
});

describe('getSystemVariableKeys()', () => {
it('should return all system variable keys', () => {
// Act
const result = getSystemVariableKeys();

// Assert
expect(result).toContain('$randomUuid');
expect(result).toHaveLength(6);
});
});

describe('getSystemVariables()', () => {
it('should return all system variables', () => {
// Act
const result = getSystemVariables();

// Assert
expect(result).toContainEqual({
key: '$randomUuid',
isActive: true,
value: expect.any(String),
description: expect.any(String),
});
expect(result).toHaveLength(6);
});
});
72 changes: 72 additions & 0 deletions src/main/environment/service/system-variable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { VariableObject } from 'shim/variables';
import { randomInt, randomUUID } from 'node:crypto';

const systemVariables = new Map<VariableObject['key'], VariableObject>(
[
{
key: '$timestampIso',
get value() {
return new Date().toISOString();
},
description: 'The current timestamp in ISO format.',
},
{
key: '$timestampUnix',
get value() {
return Math.floor(Date.now() / 1e3).toString();
},
description: 'The current timestamp in Unix format.',
},
{
key: '$time',
get value() {
return new Date().toTimeString();
},
description: 'The current time.',
},
{
key: '$date',
get value() {
return new Date().toDateString();
},
description: 'The current date.',
},
{
key: '$randomInt',
get value() {
return randomInt(2 ** 48 - 1).toString();
},
description: 'A positive random integer less than 2⁴⁸.',
},
{
key: '$randomUuid',
get value() {
return randomUUID();
},
description: 'A random UUID.',
},
].map((variable) => [variable.key, Object.assign(variable, { isActive: true })])
);

/**
* Returns the value of a dynamic, predefined system variable.
* @param key The key of the system variable.
* @returns The value of the system variable if it exists, otherwise undefined.
*/
export function getSystemVariable(key: string) {
return systemVariables.get(key);
}

/**
* Returns all keys of the system variables.
*/
export function getSystemVariableKeys() {
return Array.from(systemVariables.keys());
}

/**
* Returns all system variables.
*/
export function getSystemVariables() {
return Array.from(systemVariables.values());
}
8 changes: 8 additions & 0 deletions src/main/event/main-event-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,12 @@ export class MainEventService implements IEventService {
async deleteObject(object: TrufosObject) {
await persistenceService.delete(object);
}

async getActiveEnvironmentVariables() {
return environmentService.getActiveVariables();
}

async getVariable(key: string) {
return environmentService.getVariable(key);
}
}
2 changes: 1 addition & 1 deletion src/main/import/service/postman-importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class PostmanImporter implements CollectionImporter {
variable.id,
{
value: variable.toString(),
enabled: !variable.disabled,
isActive: !variable.disabled,
},
] as [string, VariableObject]
);
Expand Down
Loading

0 comments on commit 1c8452e

Please sign in to comment.