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

Feature/issue 89 #94

Merged
merged 7 commits into from
May 5, 2024
Merged
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
48 changes: 34 additions & 14 deletions src/constants.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Profile } from './main/framework/runtime'
import {
DEFAULT_WORKSPACE,
DEFAULT_PROFILE,
DEFAULT_PROFILES,
DEFAULT_FOLDER,
DEFAULT_HISTORY_ENABLED,
DEFAULT_HISTORY_MAX_ITEMS,
DEFAULT_HISTORY_SAVE_RESULT,
DEFAULT_SETTING_RUNNER_SHORTCUT,
DEFAULT_SETTING_COMMANDER_MODE_TOGGLE_SHORTCUT,
DEFAULT_SETTING_IS_COMMANDER_MODE,
Expand All @@ -12,17 +16,16 @@ import {
} from './constants'

describe('constants', () => {
it('should have the correct default workspace', () => {
it('should have the correct DEFAULT_WORKSPACE', () => {
expect(DEFAULT_WORKSPACE).toBe('~/mterm')
})

it('should have the correct default profile based on platform', () => {
const expectedDefaultProfile = process.platform === 'win32' ? 'powershell' : 'sh'
expect(DEFAULT_PROFILE).toBe(expectedDefaultProfile)
it('should have the correct DEFAULT_PROFILE based on the platform', () => {
expect(DEFAULT_PROFILE).toBe(process.platform === 'win32' ? 'powershell' : 'sh')
})

it('should have the correct default profiles based on platform', () => {
const expectedDefaultProfiles =
it('should have the correct DEFAULT_PROFILES based on the platform', () => {
const expectedProfiles: Record<string, Profile> =
process.platform === 'win32'
? {
powershell: {
Expand All @@ -43,26 +46,38 @@ describe('constants', () => {
icon: 'default'
}
}
expect(DEFAULT_PROFILES).toEqual(expectedDefaultProfiles)
expect(DEFAULT_PROFILES).toEqual(expectedProfiles)
})

it('should have the correct default folder', () => {
it('should have the correct DEFAULT_FOLDER', () => {
expect(DEFAULT_FOLDER).toBe('$CWD')
})

it('should have the correct default setting runner shortcut', () => {
it('should have the correct DEFAULT_HISTORY_ENABLED', () => {
expect(DEFAULT_HISTORY_ENABLED).toBe(true)
})

it('should have the correct DEFAULT_HISTORY_MAX_ITEMS', () => {
expect(DEFAULT_HISTORY_MAX_ITEMS).toBe(100)
})

it('should have the correct DEFAULT_HISTORY_SAVE_RESULT', () => {
expect(DEFAULT_HISTORY_SAVE_RESULT).toBe(true)
})

it('should have the correct DEFAULT_SETTING_RUNNER_SHORTCUT', () => {
expect(DEFAULT_SETTING_RUNNER_SHORTCUT).toBe('`+CommandOrControl')
})

it('should have the correct default setting commander mode toggle shortcut', () => {
it('should have the correct DEFAULT_SETTING_COMMANDER_MODE_TOGGLE_SHORTCUT', () => {
expect(DEFAULT_SETTING_COMMANDER_MODE_TOGGLE_SHORTCUT).toBe('`+Shift+CommandOrControl')
})

it('should have the correct default setting for commander mode', () => {
it('should have the correct DEFAULT_SETTING_IS_COMMANDER_MODE', () => {
expect(DEFAULT_SETTING_IS_COMMANDER_MODE).toBe(true)
})

it('should have the correct default setting for commander mode bounds', () => {
it('should have the correct DEFAULT_SETTING_COMMANDER_MODE_BOUNDS', () => {
expect(DEFAULT_SETTING_COMMANDER_MODE_BOUNDS).toEqual({
screen: 0,
x: 0,
Expand All @@ -72,7 +87,7 @@ describe('constants', () => {
})
})

it('should have the correct default setting for runner bounds', () => {
it('should have the correct DEFAULT_SETTING_RUNNER_BOUNDS', () => {
expect(DEFAULT_SETTING_RUNNER_BOUNDS).toEqual({
screen: 'PRIMARY',
x: 'SCREEN:-.5',
Expand All @@ -82,10 +97,15 @@ describe('constants', () => {
})
})

it('should have the correct default settings', () => {
it('should have the correct DEFAULT_SETTINGS', () => {
expect(DEFAULT_SETTINGS).toEqual({
defaultProfile: DEFAULT_PROFILE,
profiles: DEFAULT_PROFILES,
history: {
enabled: DEFAULT_HISTORY_ENABLED,
maxItems: DEFAULT_HISTORY_MAX_ITEMS,
saveResult: DEFAULT_HISTORY_SAVE_RESULT
},
runner: {
shortcut: DEFAULT_SETTING_RUNNER_SHORTCUT,
bounds: DEFAULT_SETTING_RUNNER_BOUNDS,
Expand Down
10 changes: 10 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export const DEFAULT_PROFILES: Record<string, Profile> =
}
}
export const DEFAULT_FOLDER = '$CWD'
export const DEFAULT_HISTORY_ENABLED = true

export const DEFAULT_HISTORY_MAX_ITEMS = 100
export const DEFAULT_HISTORY_SAVE_RESULT = true
export const DEFAULT_SETTING_RUNNER_SHORTCUT = '`+CommandOrControl'
export const DEFAULT_SETTING_COMMANDER_MODE_TOGGLE_SHORTCUT = '`+Shift+CommandOrControl'
export const DEFAULT_SETTING_IS_COMMANDER_MODE = true
Expand All @@ -43,9 +47,15 @@ export const DEFAULT_SETTING_RUNNER_BOUNDS = {
w: 720,
h: 500
}

export const DEFAULT_SETTINGS = {
defaultProfile: DEFAULT_PROFILE,
profiles: DEFAULT_PROFILES,
history: {
enabled: DEFAULT_HISTORY_ENABLED,
maxItems: DEFAULT_HISTORY_MAX_ITEMS,
saveResult: DEFAULT_HISTORY_SAVE_RESULT
},
runner: {
shortcut: DEFAULT_SETTING_RUNNER_SHORTCUT,
bounds: DEFAULT_SETTING_RUNNER_BOUNDS,
Expand Down
9 changes: 9 additions & 0 deletions src/main/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,16 @@ export async function boostrap(context: BootstrapContext): Promise<void> {
}
})

await workspace.history.load()
await workspace.commands.load(workspace.settings)

setInterval(
() =>
workspace.persist().catch((error) => {
console.error(error)
}),
workspace.settings.get<number>('workspace.persistInterval', 5000)
)
} catch (e) {
console.error(e)

Expand Down
72 changes: 72 additions & 0 deletions src/main/framework/history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { pathExists, readJSON, writeFile } from 'fs-extra'
import { Command } from './runtime'

export interface HistoricalExecution {
prompt: string
result?: string[]
error: boolean
aborted: boolean
profile: string
when: {
start: number
finish: number
}
code: number
}

// so why save two seperate histories?
// new execution is the history that is still pending writing
// this gives us the ability to easily scroll without exposing all the data to the front all at once
export class History {
public priorExecution: HistoricalExecution[] = []
public newExecution: HistoricalExecution[] = []
public scrollIndex: number = 0
constructor(public location: string) {}

async load(): Promise<void> {
const isExist = await pathExists(this.location)
if (!isExist) {
const prettyJSON = JSON.stringify([], null, 2)

await writeFile(this.location, prettyJSON, 'utf-8')
} else {
this.priorExecution = await readJSON(this.location)
}
}
append(command: Command, start: number, profile: string, saveResult: boolean): void {
this.newExecution.push({
prompt: command.prompt,
aborted: command.aborted,
result: saveResult ? command.result.stream.map((o) => o.raw) : undefined,
error: command.error,
profile,
when: {
start,
finish: Date.now()
},
code: command.result.code
})
}

async write(max: number): Promise<void> {
const history: Array<HistoricalExecution> = [...this.priorExecution, ...this.newExecution]

history.sort((historyA, historyB) => historyA.when.start - historyB.when.start)

const historyFinalList =
max < history.length ? history.slice(history.length - max, history.length) : history

await writeFile(this.location, JSON.stringify(historyFinalList, null, 2))
}

rewind(): HistoricalExecution | undefined {
if (this.scrollIndex >= this.priorExecution.length) {
return
}
const historicalItem = this.priorExecution[this.priorExecution.length - 1 - this.scrollIndex]

this.scrollIndex++

return historicalItem
}
}
89 changes: 78 additions & 11 deletions src/main/framework/runtime-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ import { execute } from './runtime-executor'
import createDOMPurify from 'dompurify'
import { JSDOM } from 'jsdom'
import {
DEFAULT_HISTORY_ENABLED,
DEFAULT_HISTORY_MAX_ITEMS,
DEFAULT_HISTORY_SAVE_RESULT,
DEFAULT_PROFILE,
DEFAULT_PROFILES,
DEFAULT_SETTING_IS_COMMANDER_MODE
} from '../../constants'
import Convert from 'ansi-to-html'
import { HistoricalExecution } from './history'

const convert = new Convert()
const DOMPurify = createDOMPurify(new JSDOM('').window)
Expand Down Expand Up @@ -90,6 +94,55 @@ export function attach({ app, workspace }: BootstrapContext): void {
return true
})

ipcMain.handle('history.try-scroll-next', async (_, runtimeId): Promise<boolean> => {
const runtime = workspace.runtimes.find((r) => r.id === runtimeId)
if (!runtime) {
return false
}
const enabled: boolean = workspace.settings.get<boolean>(
'history.enabled',
DEFAULT_HISTORY_ENABLED
)
if (!enabled) {
return false
}

const rewind: HistoricalExecution | undefined = workspace.history.rewind()
if (rewind === undefined) {
return false
}

const command: Command = {
prompt: rewind.prompt,
error: rewind.error,
aborted: rewind.aborted,
runtime: runtimeId,
id: short.generate(),
complete: true,
result: {
code: rewind.code,
stream: !rewind.result
? []
: rewind.result.map((raw) => {
let text = raw.toString()

text = DOMPurify.sanitize(raw)
text = convert.toHtml(text)

return {
error: rewind.error,
raw,
text: text
}
})
}
}

runtime.history.push(command)

return true
})

ipcMain.handle('runtime.rename', async (_, runtimeId, name): Promise<boolean> => {
const runtime = workspace.runtimes.find((r) => r.id === runtimeId)
if (!runtime) {
Expand Down Expand Up @@ -253,13 +306,35 @@ export function attach({ app, workspace }: BootstrapContext): void {
profileKey = workspace.settings.get<string>('defaultProfile', DEFAULT_PROFILE)
}

const history = {
enabled: workspace.settings.get<boolean>('history.enabled', DEFAULT_HISTORY_ENABLED),
results: workspace.settings.get<boolean>('history.saveResult', DEFAULT_HISTORY_SAVE_RESULT),
max: workspace.settings.get<number>('history.maxItems', DEFAULT_HISTORY_MAX_ITEMS)
}

const profiles = workspace.settings.get<ProfileMap>('profiles', DEFAULT_PROFILES)
const profile: Profile = profiles[profileKey]

const result: Result = command.result
const start = Date.now()

let finalize: boolean = true

const finish = (code: number): void => {
if (command.aborted || command.complete) {
return
}

result.code = code

command.complete = true
command.error = result.code !== 0

if (history.enabled) {
workspace.history.append(command, start, profileKey, history.results)
}
}

try {
const out = (text: string, error: boolean = false): void => {
if (command.aborted || command.complete) {
Expand Down Expand Up @@ -289,16 +364,6 @@ export function attach({ app, workspace }: BootstrapContext): void {
_.sender.send('runtime.commandEvent', streamEvent)
}
}
const finish = (code: number): void => {
if (command.aborted || command.complete) {
return
}

result.code = code

command.complete = true
command.error = result.code !== 0
}

if (!profile) {
throw `Profile ${profileKey} does not exist, provided by runtime as = ${runtimeTarget.profile}`
Expand All @@ -322,12 +387,14 @@ export function attach({ app, workspace }: BootstrapContext): void {
text: `${e}`,
raw: `${e}`
})
result.code = 1
finish(1)
}

if (finalize) {
command.complete = true
command.error = result.code !== 0

finish(result.code)
}

return command
Expand Down
1 change: 1 addition & 0 deletions src/main/framework/runtime-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export async function execute(context: ExecuteContext): Promise<void | boolean>
if (systemCommand.command === cmd || systemCommand?.alias?.includes(cmd)) {
await systemCommand.task(context, ...args)

finish(0)
return
}
}
Expand Down
Loading
Loading