Skip to content

Commit

Permalink
fix: add file editing 💯 (#99)
Browse files Browse the repository at this point in the history
* fix: add edit base

* fix: add runtime resolver

* fix: make runtime focus on no command after execution

* fix: cd can't enter a file now :grimace:

* fix: setup construct for file edit path for context

* fix: history scroll to restore edit file context

* fix: pass edit context through command result

* fix: editor pass through to backend to save context

* fix: edit view model flow (w/ control s)

* fix: edit to impact result edit field

* fix: added ':settings edit' for inline editor

* fix: remove some console logs, oops

* fix: add some docs about file editing

* fix: little runner cleanup

* fix: image update on doc
  • Loading branch information
daretodave authored May 7, 2024
1 parent 8c99f76 commit b8b46fd
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 48 deletions.
42 changes: 27 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mterm-io_mterm&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=mterm-io_mterm)
[![release](https://github.com/mterm-io/mterm/actions/workflows/release.yml/badge.svg)](https://github.com/mterm-io/mterm/actions/workflows/release.yml)

![image](https://github.com/mterm-io/mterm/assets/7341502/6eb47f43-1ab5-41c5-9c0e-5eb61ce575bf)
![image](https://github.com/mterm-io/mterm/assets/7341502/27bcad62-6891-4b49-80b5-e5a17e0562ab)

**mterm** is a cross-platform command-line terminal that proxies the underlying command-line interpreters, such as [powershell](https://learn.microsoft.com/en-us/powershell/), [sh](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html) or [wsl](https://ubuntu.com/desktop/wsl). commands are executed in the background and results streamed to the foreground.
Expand Down Expand Up @@ -128,21 +129,23 @@ here is an example `~/mterm/settings.json` -

mterm provided a few system commands to help control the terminal and settings. mterm settings will always start with `:` (a colon) unless the intention is to override a system command. for example, because `clear` needs to be handled in a special way for mterm windows + tabs, it is overriden in mterm.

| Command | Alias | Purpose |
|------------------------------|--------|-----------------------------------------------------------------------------|
| `clear` | `cls` | Clear the current terminal output |
| `cd` | | Navigate the file tree on the host machine |
| `:exit` | `exit` | Exit the current tab, or mterm if only 1 tab is open |
| `:history` | | Print out terminal history for debugging in a JSON format |
| `:reload` | | Reload settings, the ui, and commands without restarting |
| `:tab` | | Open a new tab |
| `:test` | | Sample command that executes after 10 seconds. Helpful for debugging |
| `:vault` | | Open the secret management tool, the mterm vault |
| `:version` | `:v` | Print the current mterm version |
| `:workspace` | | Open the mterm workspace folder on disk: `~/mterm` |
| `:settings` | | Open the mterm settings gui to manage `~/mterm/settings.json` |
| `:settings reload` | | Reload `~/mterm/settings.json` and all ui etc associated with the settings |
| `:settings {get\|set} {key}` | | Set the setting key matching the path in `~/mterm/settings.json` and reload |
| Command | Alias | Purpose |
|------------------------------|--------|----------------------------------------------------------------------------------------|
| `clear` | `cls` | Clear the current terminal output |
| `cd` | | Navigate the file tree on the host machine |
| `:exit` | `exit` | Exit the current tab, or mterm if only 1 tab is open |
| `:edit` | `edit` | Open the in-terminal editor with the file provided. Hit `Ctrl+S` to save in the editor |
| `:history` | | Print out terminal history for debugging in a JSON format |
| `:reload` | | Reload settings, the ui, and commands without restarting |
| `:tab` | | Open a new tab |
| `:test` | | Sample command that executes after 10 seconds. Helpful for debugging |
| `:vault` | | Open the secret management tool, the mterm vault |
| `:version` | `:v` | Print the current mterm version |
| `:workspace` | | Open the mterm workspace folder on disk: `~/mterm` |
| `:settings` | | Open the mterm settings gui to manage `~/mterm/settings.json` |
| `:settings edit` | | Open the `~/mterm/settings.json` in the terminal editor with hot reloading |
| `:settings reload` | | Reload `~/mterm/settings.json` and all ui etc associated with the settings |
| `:settings {get\|set} {key}` | | Set the setting key matching the path in `~/mterm/settings.json` and reload |

### Commands

Expand Down Expand Up @@ -212,6 +215,15 @@ export function who() {

![image](https://github.com/mterm-io/mterm/assets/7341502/76b26a62-33ea-4883-b07c-677f99ab3355)

### Editor

mterm provides an editor with `:edit <FILE>` or `edit <FILE>` commands -

![image](https://github.com/mterm-io/mterm/assets/7341502/25db8038-7a86-419c-a5d7-777b97025ec7)

hit `control + s` within the file editor to save this


### Other Notes

When you change the tab name to include `$idx` - this will be replaced with the current tab index
Expand Down
2 changes: 0 additions & 2 deletions src/main/bootstrap/create-tray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,4 @@ export async function createTray(context: BootstrapContext): Promise<void> {

tray.setToolTip('MTERM')
tray.setContextMenu(menu)

console.log(tray)
}
5 changes: 1 addition & 4 deletions src/main/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ export async function boostrap(context: BootstrapContext): Promise<void> {
}
})

autoUpdater
.checkForUpdatesAndNotify()
.then((r) => console.log(r))
.catch(console.error)
autoUpdater.checkForUpdatesAndNotify().catch(console.error)

attach(context)

Expand Down
2 changes: 2 additions & 0 deletions src/main/framework/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface HistoricalExecution {
error: boolean
aborted: boolean
profile: string
edit?: string
when: {
start: number
finish: number
Expand Down Expand Up @@ -40,6 +41,7 @@ export class History {
result: saveResult ? command.result.stream.map((o) => o.raw) : undefined,
error: command.error,
profile,
edit: command?.result?.edit?.path,
when: {
start,
finish: Date.now()
Expand Down
103 changes: 94 additions & 9 deletions src/main/framework/runtime-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '../../constants'
import Convert from 'ansi-to-html'
import { HistoricalExecution } from './history'
import { readFile, writeFile } from 'fs-extra'

const convert = new Convert()
const DOMPurify = createDOMPurify(new JSDOM('').window)
Expand All @@ -35,7 +36,15 @@ export function attach({ app, workspace }: BootstrapContext): void {
const focus = runtime.history.find((cmd) => cmd.id === runtime.commandFocus)

const result = focus
? focus.result
? {
...focus.result,
edit: focus.result.edit
? {
...focus.result.edit,
callback: undefined
}
: undefined
}
: {
code: 0,
stream: []
Expand All @@ -44,7 +53,17 @@ export function attach({ app, workspace }: BootstrapContext): void {
const history: CommandViewModel[] = runtime.history.map((historyItem) => {
return {
...historyItem,
process: undefined
process: undefined,
result: {
...historyItem.result,
edit: historyItem.result.edit
? {
content: historyItem.result.edit.content,
path: historyItem.result.edit.path,
modified: historyItem.result.edit.modified
}
: undefined
}
}
})

Expand Down Expand Up @@ -73,6 +92,57 @@ export function attach({ app, workspace }: BootstrapContext): void {
})
}

ipcMain.handle(
'runtime.set-edit',
async (_, runtimeId: string, commandId: string, result: string): Promise<boolean> => {
const runtime = workspace.runtimes.find((r) => r.id === runtimeId)
if (!runtime) {
return false
}

if (!commandId) {
commandId = runtime.commandFocus
}

const command = runtime.history.find((c) => c.id === commandId)
if (!command || !command.result.edit) {
return false
}

command.result.edit.content = result
command.result.edit.modified = true

return true
}
)

ipcMain.handle(
'runtime.save-edit',
async (_, runtimeId: string, commandId: string): Promise<boolean> => {
const runtime = workspace.runtimes.find((r) => r.id === runtimeId)
if (!runtime) {
return false
}

if (!commandId) {
commandId = runtime.commandFocus
}

const command = runtime.history.find((c) => c.id === commandId)
if (!command || !command.result.edit) {
return false
}

command.result.edit.modified = false

await writeFile(command.result.edit.path, command.result.edit.content)

await command.result.edit.callback(command.result.edit.content)

return true
}
)

ipcMain.handle(
'runtime.set-result',
async (_, runtimeId: string, commandId: string, result: string): Promise<boolean> => {
Expand Down Expand Up @@ -101,7 +171,7 @@ export function attach({ app, workspace }: BootstrapContext): void {
}
}

return false
return true
}
)

Expand Down Expand Up @@ -332,7 +402,8 @@ export function attach({ app, workspace }: BootstrapContext): void {
aborted: false,
result: {
code: 0,
stream: []
stream: [],
edit: undefined
},
runtime: runtime.id
}
Expand All @@ -345,7 +416,7 @@ export function attach({ app, workspace }: BootstrapContext): void {
return command
})

ipcMain.handle('runtime.execute', async (_, { id, runtime }: Command): Promise<Command> => {
ipcMain.handle('runtime.execute', async (_, { id, runtime }: Command): Promise<boolean> => {
const runtimeTarget = workspace.runtimes.find((r) => r.id === runtime)
if (!runtimeTarget) {
throw `Runtime '${runtime}' does not exist`
Expand Down Expand Up @@ -392,10 +463,12 @@ export function attach({ app, workspace }: BootstrapContext): void {

try {
const out = (text: string, error: boolean = false): void => {
if (command.aborted || command.complete) {
return
const isFinished = command.aborted || command.complete
if (isFinished) {
if (!command.result.edit) {
return
}
}

const raw = text.toString()

text = DOMPurify.sanitize(raw)
Expand Down Expand Up @@ -430,6 +503,18 @@ export function attach({ app, workspace }: BootstrapContext): void {
workspace,
runtime: runtimeTarget,
command,
async edit(path: string, callback: (text: string) => void) {
const file = await readFile(path)

this.command.result.edit = {
path,
modified: false,
content: file.toString(),
callback
}

_.sender.send('runtime.commandEvent')
},
out,
finish
})
Expand All @@ -452,7 +537,7 @@ export function attach({ app, workspace }: BootstrapContext): void {
finish(result.code)
}

return command
return true
})

ipcMain.handle('runtimes', async (): Promise<RuntimeModel[]> => {
Expand Down
3 changes: 2 additions & 1 deletion src/main/framework/runtime-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import Version from './system-commands/version'
import Vault from './system-commands/vault'
import Workspace from './system-commands/workspace'
import Settings from './system-commands/settings'
import Edit from './system-commands/edit'

const systemCommands: Array<{
command: string
alias?: string[]
task: (context: ExecuteContext, ...args: string[]) => Promise<void> | void
}> = [Reload, Exit, History, Cd, Tab, Test, Clear, Version, Vault, Workspace, Settings]
}> = [Reload, Exit, History, Cd, Tab, Test, Clear, Version, Vault, Workspace, Settings, Edit]
export async function execute(context: ExecuteContext): Promise<void | boolean> {
const { platform, workspace, runtime, command, out, finish } = context
const [cmd, ...args] = command.prompt.split(' ')
Expand Down
32 changes: 30 additions & 2 deletions src/main/framework/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { resolveFolderPathForMTERM, Workspace } from './workspace'
import short from 'short-uuid'
import { ChildProcessWithoutNullStreams } from 'node:child_process'
import { resolve } from 'path'

export interface ResultStream {
error: boolean
Expand All @@ -11,13 +12,30 @@ export interface ResultStream {
export interface Result {
code: number
stream: ResultStream[]
edit?: EditFile
}

export interface ResultViewModel {
code: number
stream: ResultStream[]
edit?: EditFileViewModel
}

export interface ResultStreamEvent {
runtime: string
command: string
entry: ResultStream
}

export interface EditFileViewModel {
path: string
modified: boolean
content: string
}

export interface EditFile extends EditFileViewModel {
callback: (text: string) => Promise<void> | void
}
export interface Command {
prompt: string
result: Result
Expand All @@ -31,7 +49,7 @@ export interface Command {

export interface CommandViewModel {
prompt: string
result: Result
result: ResultViewModel
runtime: string
aborted: boolean
complete: boolean
Expand All @@ -54,7 +72,7 @@ export interface RuntimeModel {
id: string
prompt: string
profile: string
result: Result
result: ResultViewModel
target: boolean
folder: string
history: CommandViewModel[]
Expand All @@ -81,13 +99,23 @@ export class Runtime {
icon: '',
title: 'mterm [$idx]'
}

resolve(path: string): string {
let location = resolve(this.folder, path)
if (path.startsWith('~')) {
location = resolveFolderPathForMTERM(path)
}

return location
}
}

export interface ExecuteContext {
platform: string
workspace: Workspace
runtime: Runtime
command: Command
edit: (path: string, callback: (content: string) => void) => Promise<void>
out: (text: string, error?: boolean) => void
finish: (code: number) => void
}
Loading

0 comments on commit b8b46fd

Please sign in to comment.