Skip to content

Commit

Permalink
fix: resolves #123, resolves #119 - extension pattern 🕺 💯 (#127)
Browse files Browse the repository at this point in the history
* chore: extension pattern

* chore: RUNNER_THEME_CSS extension hook

* chore: ext add and ext rm

* chore: some doc on extenstions
  • Loading branch information
daretodave committed May 27, 2024
1 parent a9cecb0 commit 22f8591
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Head over to the [release page](https://github.com/mterm-io/mterm/releases/lates
mterm is customizable in a few ways -
- add your own [commands](#commands) to the terminal
- quick add a command with `:cmd command_name` and edit this
- add extensions to the terminal
- quick add an extension with `ext add mterm-red`, see [extensions](./docs/extensions.md) for more info
- change the [theme](#theme) of the terminal
- quick change the theme with `:theme` or `:css`
- change the [settings](#settings) of the terminal
Expand Down
17 changes: 17 additions & 0 deletions docs/extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Known Extensions

> Please make a pull request to add your extension to this list
- [mterm-ext-red](#mterm-ext-red)
- more...


### mterm-ext-red

```bash
ext add mterm-red
```
Make your terminal (mterm) red 10 seconds -

![red terminal](https://github.com/mterm-io/mterm-ext-red/blob/HEAD/info.png?raw=true)

30 changes: 30 additions & 0 deletions src/main/framework/execute-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { WebContents } from 'electron'
import { readFile } from 'fs-extra'
import short from 'short-uuid'
import { ResultStream } from './result-stream'

import { process } from './transformers'
import { spawn } from 'node:child_process'
export interface RuntimeContentHandle {
id: string
update(html: string): void
Expand Down Expand Up @@ -62,6 +64,34 @@ export class ExecuteContext {
return context
}

async runTask(env, folder: string, spawnTask: string, hide: boolean = true): Promise<void> {
const [platformProgram, ...platformProgramArgs] = this.platform.split(' ')

const argsClean = platformProgramArgs.map((arg: string) => `${arg.replace('$ARGS', spawnTask)}`)

const childSpawn = spawn(platformProgram, argsClean, {
cwd: folder,
env
})

childSpawn.stdout.on('data', (data) => {
if (!hide) {
this.out(data.toString())
}
})
childSpawn.stderr.on('data', (data) => this.out(data, true))

return new Promise((resolve, reject) => {
childSpawn.on('exit', (code) => {
if (code !== 0) {
reject()
} else {
resolve()
}
})
})
}

async resolve(text: string): Promise<string> {
return await process(this, text)
}
Expand Down
97 changes: 97 additions & 0 deletions src/main/framework/extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Workspace } from './workspace'
import { join } from 'path'
import { pathExists, readJson } from 'fs-extra'
import { log } from '../logger'

export enum ExtensionHook {
RUNNER_THEME_CSS = 'RUNNER_THEME_CSS'
}

export type ExtensionHookCallback = (workspace?: Workspace) => string
export type ExtensionHookResolution = string | ExtensionHookCallback
export class Extensions {
public extensionHooks: Map<ExtensionHook, Array<ExtensionHookResolution>> = new Map<
ExtensionHook,
Array<ExtensionHookResolution>
>()
public extensionList: string[] = []
constructor(private workspace: Workspace) {}

async run(hook: ExtensionHook): Promise<string> {
const resolutions = this.extensionHooks.get(hook) || []

let result = ''
for (const resolution of resolutions) {
if (typeof resolution === 'string') {
result += resolution
} else {
result += resolution(this.workspace)
}
}

return result
}

async load(): Promise<void> {
this.extensionList = []
this.extensionHooks.clear()

const start = Date.now()
const packageJson = join(this.workspace.folder, 'package.json')
const isPackageJsonExists = await pathExists(packageJson)
if (!isPackageJsonExists) {
log('No package.json found in workspace')
return
}
const packageJsonData = await readJson(packageJson)
const packages: string[] = []

if (packageJsonData.dependencies) {
packages.push(...Object.keys(packageJsonData.dependencies))
}
if (packageJsonData.devDependencies) {
packages.push(...Object.keys(packageJsonData.devDependencies))
}

const folder = this.workspace.folder
const ext = this.extensionHooks
const list = this.extensionList
async function scan(packageName: string): Promise<void> {
log(`Scanning package: ${packageName}..`)

const mtermExtPath = join(folder, 'node_modules', packageName, 'mterm.js')

const isMtermExtensionExists = await pathExists(mtermExtPath)
if (!isMtermExtensionExists) {
return
}

log(`Loading package: ${packageName}`)
const mtermExt = require(mtermExtPath)

const hooks = Object.keys(ExtensionHook)

log(`Mapping hooks for ${packageName} = ${hooks}`)

list.push(packageName)

for (const hook of hooks) {
const hookKey = hook as ExtensionHook
let resolutions: ExtensionHookResolution[] = []
if (ext.has(ExtensionHook[hookKey])) {
resolutions = ext.get(ExtensionHook[hookKey]) as ExtensionHookResolution[]
}

resolutions.push(mtermExt[hookKey])

ext.set(hookKey, resolutions)
}
}

const promiseList = packages.map(scan)

await Promise.all(promiseList)

log('Extensions loaded in ' + (Date.now() - start) + 'ms')
}
}
5 changes: 4 additions & 1 deletion src/main/framework/runtime-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { writeFile } from 'fs-extra'
import { ExecuteContext } from './execute-context'
import { ResultStream } from './result-stream'
import { transform } from './transformers'
import { ExtensionHook } from './extensions'

export function attach({ app, workspace }: BootstrapContext): void {
const eventListForCommand = (command: Command): ResultContentEvent[] => {
Expand Down Expand Up @@ -204,7 +205,9 @@ export function attach({ app, workspace }: BootstrapContext): void {
})

ipcMain.handle('runner.theme', async (_, profile): Promise<string> => {
return workspace.theme.get(profile)
const theme = workspace.theme.get(profile)
const extensionTheme = await workspace.extensions.run(ExtensionHook.RUNNER_THEME_CSS)
return `${extensionTheme}${theme}`
})

ipcMain.handle('runtime.kill', async (_, commandId, runtimeId): Promise<boolean> => {
Expand Down
4 changes: 3 additions & 1 deletion src/main/framework/runtime-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Reset from './system-commands/reset'
import Commands from './system-commands/commands'
import Restart from './system-commands/restart'
import Theme from './system-commands/theme'
import Ext from './system-commands/ext'

export interface SystemCommand {
command: string
Expand All @@ -40,7 +41,8 @@ export const systemCommands: Array<SystemCommand> = [
Reset,
Commands,
Restart,
Theme
Theme,
Ext
]
export async function execute(context: ExecuteContext): Promise<void | boolean> {
// check for system commands
Expand Down
64 changes: 64 additions & 0 deletions src/main/framework/system-commands/ext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ExecuteContext } from '../execute-context'

async function ext(context: ExecuteContext, task?: string): Promise<void> {
if (!task) {
context.out(
`ext list:
${context.workspace.extensions.extensionList.length > 0 ? '-' : ''}` +
context.workspace.extensions.extensionList.join('\n-')
)
context.finish(0)
return
}
if (task === 'load') {
await context.workspace.extensions.load()
context.out('Extensions loaded\n')
context.finish(0)
return
}

if (task === 'add') {
const extName = (context.prompt.args[1] || '').trim()
if (!extName) {
context.out('No extension name provided\n\n:ext add EXT_NAME', true)
context.finish(1)
return
}

context.out(`Installing ${extName}..\n`)

await context.runTask(process.env, context.workspace.folder, `npm install ${extName}`)

await context.workspace.extensions.load()

context.out('Done\n')
} else if (task === 'remove' || task === 'rm' || task === 'delete') {
const extName = (context.prompt.args[1] || '').trim()
if (!extName) {
context.out('No extension name provided\n\n:ext rm EXT_NAME', true)
context.finish(1)
return
}

context.out(`Removing ${extName}..\n`)

await context.runTask(process.env, context.workspace.folder, `npm rm ${extName}`)

await context.workspace.extensions.load()

context.out('Done\n')
} else {
context.out(
'Unknown command\n\nTry :ext {add,remove} EXT_NAME or :ext to list current extensions',
true
)
context.finish(1)
return
}
}

export default {
command: ':ext',
alias: [':extension', 'ext', 'extension'],
task: ext
}
4 changes: 4 additions & 0 deletions src/main/framework/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Store } from './store'
import { History } from './history'
import { Theme } from './theme'
import { Autocomplete } from './autocomplete'
import { Extensions } from './extensions'

export function resolveFolderPathForMTERM(folder: string): string {
folder = folder.replace('~', homedir())
Expand All @@ -28,6 +29,7 @@ export class Workspace {
public settings: Settings
public commands: Commands
public autocomplete: Autocomplete
public extensions: Extensions
public theme: Theme
public isAppQuiting: boolean = false
public windows: MTermWindow[] = []
Expand All @@ -47,6 +49,7 @@ export class Workspace {
this.history = new History(join(this.folder, '.history'))
this.store = new Store(join(this.folder, '.mterm-store'))
this.theme = new Theme(this, join(app.getAppPath(), './resources/theme.css'))
this.extensions = new Extensions(this)
this.autocomplete = new Autocomplete(this)
}

Expand Down Expand Up @@ -95,6 +98,7 @@ export class Workspace {

await this.settings.load()
await this.theme.load()
await this.extensions.load()

// we ignore the result of this (catch error ofc)
// let this run in the background
Expand Down

0 comments on commit 22f8591

Please sign in to comment.