Skip to content

Commit

Permalink
fix: add realtime editing and theme overrides (#116), resolves #114
Browse files Browse the repository at this point in the history
* fix: add custom themes for runner

* fix: add the :theme command

* fix: add readme about theme
  • Loading branch information
daretodave committed May 11, 2024
1 parent 44af7ab commit e968b41
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 3 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

![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)
![image](https://github.com/mterm-io/mterm/assets/7341502/ab7b3a97-98c0-4dda-aa39-af3d6a33d0f7)

**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 @@ -68,7 +69,8 @@ in this folder, there are a couple of important files to help configure mterm -

- `commands.ts`, used to attach commands to the terminal. see [commands](#commands)
- `package.json`, an npm package declaration for use by [commands](#commands)
- `settings`, general runtime settings [settings](#settings)
- `theme.css`, default theme used by the terminal, change this path/file with the `theme` property for profiles
- `settings.json`, general runtime settings [settings](#settings)

### Settings

Expand Down Expand Up @@ -142,6 +144,7 @@ mterm provided a few system commands to help control the terminal and settings.
| `: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` |
| `:theme` | `:css` | Edit the terminal theme real time |
| `: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 |
Expand Down
129 changes: 129 additions & 0 deletions resources/theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* Override themes for your terminal here
* See default here: https://github.com/mterm-io/mterm/blob/main/src/renderer/src/assets/runner.css
*/

/*
* prepend .runner-container__commander_mode
* to apply styles only for commander mode
*/
.runner-container {
background: rgba(27, 27, 31, 0.89);
color: white;
}
.runner-container__commander_mode .runner-result {
}

.runner-input-container {
}

.runner-input {
}

.runner-input-field {
}

.runner-input-field {
}
.runner-input-field:focus {
}

.runner-container {
}

.runner-tabs {
}

.runner-tabs-title {
}

.runner-tabs-title-active {
}
.runner-spacer {
}

.runner-context {
}

.runner-main {
}

.runner-editor {
}


.runner-editor-header {
}

.runner-editor-header-result {
}
.runner-editor-header-result::-webkit-scrollbar {
}
.runner-editor-header-result::-webkit-scrollbar-corner {
}
.runner-editor-header-result::-webkit-scrollbar-thumb {
}
.runner-editor-header-result pre {
}
.runner-context-folder {
}

.runner-history {
}

.runner-history::-webkit-scrollbar {
}
.runner-history::-webkit-scrollbar-corner {
}
.runner-history::-webkit-scrollbar-thumb {
}
.runner-history-item {
}
.runner-history-item:hover {
}
.runner-history-selected {
}
.runner-history-running {
}
.runner-history-complete {
}
.runner-history-aborted {
}
.runner-history-error {
}

.runner-history::-webkit-scrollbar-track {
}

.runner-result-error {
}
.runner-result {
}

.space {
}
.runner-info {
}
.toggle-button-slider {
}
.toggle-button-circle {
}
.toggle-button {
}
.toggle-button-spacer {
}
.toggle-button.toggle-button-on .toggle-button-slider {
}

.runner-result::-webkit-scrollbar {
}
.runner-result::-webkit-scrollbar-corner {
}
.runner-result::-webkit-scrollbar-thumb {
}
.contextmenu.tab-context-menu {
}
.tab-context-menu > .contextmenu__item {
}
.tab-context-menu > .contextmenu__item:hover {
}
4 changes: 4 additions & 0 deletions src/main/framework/runtime-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ export function attach({ app, workspace }: BootstrapContext): void {
)
})

ipcMain.handle('runner.theme', async (_, profile): Promise<string> => {
return workspace.theme.get(profile)
})

ipcMain.handle('runtime.kill', async (_, commandId, runtimeId): Promise<boolean> => {
const runtime = workspace.runtimes.find((r) => r.id === runtimeId)
if (!runtime) {
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 @@ -17,6 +17,7 @@ import Edit from './system-commands/edit'
import Reset from './system-commands/reset'
import Commands from './system-commands/commands'
import Restart from './system-commands/restart'
import Theme from './system-commands/theme'

const systemCommands: Array<{
command: string
Expand All @@ -37,7 +38,8 @@ const systemCommands: Array<{
Edit,
Reset,
Commands,
Restart
Restart,
Theme
]
export async function execute(context: ExecuteContext): Promise<void | boolean> {
// check for system commands
Expand Down
2 changes: 1 addition & 1 deletion src/main/framework/system-commands/reload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ExecuteContext } from '../execute-context'

async function reload(context: ExecuteContext): Promise<void> {
await context.workspace.load()
context.out('- settings reloaded \n')
context.out('- settings and themes reloaded \n')

await context.workspace.commands.load(context.workspace.settings)
context.out('- commands reloaded \n')
Expand Down
39 changes: 39 additions & 0 deletions src/main/framework/system-commands/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ExecuteContext } from '../execute-context'
import { errorModal } from '../../index'

export default {
command: ':theme',
alias: [':css'],
async task(context: ExecuteContext, task?: string): Promise<void> {
context.out('')
if (!task) {
if (!context.profile) {
context.out(
'No profile file to edit, make sure your ~/mterm/settings.json defaultProfile is set'
)
return
}

const profileThemeLocation = context.workspace.theme.getProfileThemeLocation(context.profile)
if (!profileThemeLocation) {
context.out(
'No theme location was found for the default profile, please check your settings!'
)
return
}

await context.edit(profileThemeLocation, async () => {
context.out('Saved theme file!\n')

try {
await context.workspace.theme.load()
} catch (e) {
console.log(e)
await errorModal.showError(e)
}

context.out('Theme reloaded\n')
})
}
}
}
76 changes: 76 additions & 0 deletions src/main/framework/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Workspace } from './workspace'
import { ProfileMap } from './runtime'
import { DEFAULT_PROFILE, DEFAULT_PROFILES } from '../../constants'
import { pathExists, readFile, writeFile } from 'fs-extra'
import { log } from '../logger'

export class Theme {
public themes: Map<string, string> = new Map()
constructor(
public workspace: Workspace,
public defaultThemeLocation: string
) {}

getProfileThemeLocation(profileKey: string): string {
const profiles = this.workspace.settings.get<ProfileMap>('profiles', DEFAULT_PROFILES)
if (profileKey === 'default') {
profileKey = this.workspace.settings.get<string>('defaultProfile', DEFAULT_PROFILE)
}
let theme = profiles[profileKey]?.theme
if (theme) {
theme = this.workspace.resolve(theme)
}
return theme
}
async load(): Promise<void> {
this.themes.clear()
//get profiles
const profiles = this.workspace.settings.get<ProfileMap>('profiles', DEFAULT_PROFILES)
const cache = {}

const getDefaultTheme = async (): Promise<string> => {
if (cache[this.defaultThemeLocation]) {
return cache[this.defaultThemeLocation]
}

const cssBuffer = await readFile(this.defaultThemeLocation)
const css = cssBuffer.toString()

cache[this.defaultThemeLocation] = css

return css
}

for (const profileKey in profiles) {
const profile = profiles[profileKey]
const cssFileLocation = this.workspace.resolve(profile.theme)

let css = cache[cssFileLocation]
if (!css) {
const isExist = await pathExists(cssFileLocation)
if (!isExist) {
log(
`profile = ${profileKey} is mapped to css file @ ${profile.theme}. could not find @ ${cssFileLocation} - creating default`
)
css = await getDefaultTheme()

await writeFile(cssFileLocation, css, 'utf-8')
} else {
const cssFileBuffer = await readFile(cssFileLocation)
css = cssFileBuffer.toString()
}

cache[cssFileLocation] = css
}

this.themes.set(profileKey, css)
}
}

get(profileKey: string): string {
if (profileKey === 'default') {
profileKey = this.workspace.settings.get<string>('defaultProfile', DEFAULT_PROFILE)
}
return this.themes.get(profileKey) || ''
}
}
13 changes: 13 additions & 0 deletions src/main/framework/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DEFAULT_FOLDER, DEFAULT_HISTORY_ENABLED, DEFAULT_HISTORY_MAX_ITEMS } fr
import { Commands } from './commands'
import { Store } from './store'
import { History } from './history'
import { Theme } from './theme'

export function resolveFolderPathForMTERM(folder: string): string {
folder = folder.replace('~', homedir())
Expand All @@ -25,6 +26,7 @@ export class Workspace {
public history: History
public settings: Settings
public commands: Commands
public theme: Theme
public isAppQuiting: boolean = false
public windows: MTermWindow[] = []
public runtimes: Runtime[] = []
Expand All @@ -42,6 +44,7 @@ export class Workspace {
this.settings = new Settings(join(this.folder, 'settings.json'), defaultSettings)
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'))
}

get runtime(): Runtime {
Expand Down Expand Up @@ -88,6 +91,7 @@ export class Workspace {
}

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

/**
* Load an initial index
Expand Down Expand Up @@ -172,6 +176,15 @@ export class Workspace {
}
}

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

return location
}

/**
* Applies the updated settings to all the windows in the workspace.
*
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/src/assets/runner.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ html,body {
width: 100%;
height: 100%;
}


body {
background: rgba(27, 27, 31, 0);
}

.runner-container {
background: rgba(27, 27, 31, 0.89);
color: white;
}
Expand Down
17 changes: 17 additions & 0 deletions src/renderer/src/runner/runner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,23 @@ export default function Runner(): ReactElement {
reloadRuntimesFromBackend().catch((error) => console.error(error))
}, [])

useEffect(() => {
let styleSheet = document.getElementById('theme')
if (!styleSheet) {
styleSheet = document.createElement('style')
styleSheet.setAttribute('id', 'theme')
document.head.appendChild(styleSheet)
}

window.electron.ipcRenderer
.invoke('runner.theme', runtimeList.find((o) => o.target)?.profile)
.then((theme) => {
styleSheet.innerText = theme
})

return () => {}
}, [runtimeList])

if (!runtime) {
return <p>Loading</p>
}
Expand Down

0 comments on commit e968b41

Please sign in to comment.