Skip to content

Commit

Permalink
feat: terminal shell preferences + reset button
Browse files Browse the repository at this point in the history
  • Loading branch information
NGPixel committed Feb 17, 2025
1 parent a68ba63 commit 36a3259
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 44 deletions.
4 changes: 2 additions & 2 deletions src-electron/handlers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { app, clipboard, dialog, ipcMain, shell } from 'electron'
import fs from 'node:fs/promises'
import path from 'node:path'
import { orderBy } from 'lodash-es'
import { pick, orderBy } from 'lodash-es'
import { encode, decode } from '@msgpack/msgpack'

/**
Expand Down Expand Up @@ -380,7 +380,7 @@ export function registerCallbacks (mainWindow, mainMenu, auth, git, lsp, tlm, te
// TERMINAL
// ----------------------------------------------------------
ipcMain.on('terminalInit', (ev, opts) => {
terminal.initialize(mainWindow, opts.cwd)
terminal.initialize(mainWindow, pick(opts, ['cwd', 'shell', 'args']))
})
ipcMain.on('terminalInput', (ev, data) => {
terminal.write(data)
Expand Down
47 changes: 20 additions & 27 deletions src-electron/terminal.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import os from 'node:os'
// import { spawn } from 'node:child_process'
import * as pty from '@lydell/node-pty'

const shell = os.platform() === 'win32'
? {
cmd: 'powershell.exe',
args: ['-NoLogo']
}
: {
cmd: 'bash',
args: []
}
const defaultShell = {
cmd: 'bash',
args: ''
}
switch (os.platform()) {
case 'darwin':
defaultShell.cmd = 'zsh'
defaultShell.args = ''
break
case 'win32':
defaultShell.cmd = 'pwsh.exe'
defaultShell.args = '-NoLogo -NoProfile'
break
}

export default {
term: null,
/**
* Initialize PTY
*/
initialize (mainWindow, cwd) {
initialize (mainWindow, opts = {}) {
if (this.term) { return }
this.term = pty.spawn(shell.cmd, shell.args, {
this.term = pty.spawn(opts.shell || defaultShell.cmd, (opts.args ?? defaultShell.args).split(' '), {
name: 'draftforge-terminal',
cols: 80,
rows: 30,
cwd,
cwd: opts.cwd,
env: process.env
})
this.term.onData(data => {
Expand All @@ -32,23 +36,12 @@ export default {
this.term.onExit(() => {
this.term = null
})
// this.term = spawn(shell, [], {
// windowsHide: true,
// cwd
// })
// this.term.stdout.on('data', data => {
// mainWindow.webContents.send('terminal.incomingData', data)
// })
// this.term.stderr.on('data', data => {
// mainWindow.webContents.send('terminal.incomingData', data)
// })
// this.term.on('exit', () => {
// this.term = null
// })
},
/**
* Stdin
*/
write (data) {
if (this.term) {
// this.term.stdin.write(data)
this.term.write(data)
}
},
Expand Down
30 changes: 30 additions & 0 deletions src/components/PreferencesDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,31 @@ q-dialog(
@update:model-value='updateTelemetryState'
)

template(v-else-if='state.tab === `terminal`')
q-form.q-gutter-md.q-pa-lg
.row
.col-8
.text-body2 Shell Executable
.text-caption.text-grey-5 The shell to use for the terminal.
.col-4
q-input(
v-model.number='editorStore.terminalShell'
outlined
dense
color='light-blue-4'
)
.row
.col-8
.text-body2 Shell Arguments
.text-caption.text-grey-5 Additional arguments to supply to the shell executable.
.col-4
q-input(
v-model.number='editorStore.terminalArgs'
outlined
dense
color='light-blue-4'
)

template(v-if='state.tab === `dev`')
q-form.q-gutter-md.q-pa-lg
.flex.items-center.text-red-5
Expand Down Expand Up @@ -476,6 +501,11 @@ const tabs = computed(() => ([
icon: 'mdi-broadcast',
label: 'Telemetry'
},
{
key: 'terminal',
icon: 'mdi-console',
label: 'Terminal'
},
{
key: 'dev',
icon: 'mdi-flask',
Expand Down
80 changes: 65 additions & 15 deletions src/components/TerminalDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,42 @@ q-dialog(
q-icon(name='mdi-console', left, size='sm')
span Terminal
q-space
//- q-banner.q-mr-md.text-white.bg-negative.q-px-md(rounded, dense)
//- template(#avatar)
//- q-icon(name='mdi-alert', size='xs')
//- span TTY not supported yet.
q-btn.q-mr-md(
unelevated
icon='mdi-restart-alert'
color='primary'
padding='xs md'
@click='resetTerminal'
label='Reset'
no-caps
:loading='state.resetLoading'
)
q-tooltip Kill active terminal and launch new one
q-btn.q-mr-md(
unelevated
icon='mdi-cog-outline'
color='primary'
padding='xs md'
@click='openTerminalSettings'
label='Settings'
no-caps
)
q-tooltip Configure Terminal
q-btn(
unelevated
icon='mdi-close'
color='primary'
padding='xs'
@click='onDialogCancel'
)
)
q-tooltip Close Terminal
q-card-section.card-border.q-pa-md.bg-black
div(ref='terminal', style='height: 500px;')
</template>

<script setup>
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { onBeforeUnmount, onMounted, reactive, useTemplateRef } from 'vue'
import { defineAsyncComponent, onBeforeUnmount, onMounted, reactive, useTemplateRef } from 'vue'
import { useEditorStore } from 'src/stores/editor'
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
Expand All @@ -53,29 +71,48 @@ const { dialogRef, onDialogCancel, onDialogHide } = useDialogPluginComponent()
// STATE
const state = reactive({
isLoading: false
resetLoading: false
})
let term = null
const terminalElm = useTemplateRef('terminal')
// METHODS
function resetTerminal () {
state.resetLoading = true
window.ipcBridge.emit('terminalDestroy')
term.reset()
setTimeout(() => {
window.ipcBridge.emit('terminalInit', {
cwd: editorStore.workingDirectory,
shell: editorStore.terminalShell,
args: editorStore.terminalArgs
})
state.resetLoading = false
}, 2000)
}
function openTerminalSettings () {
$q.dialog({
component: defineAsyncComponent(() => import('components/PreferencesDialog.vue')),
componentProps: {
tab: 'terminal'
}
})
}
function handleTerminalOutput (evt, data) {
term.write(data)
}
onMounted(async () => {
function initTerminal () {
term = new Terminal({
cursorBlink: true
})
const fitAddon = new FitAddon()
term.loadAddon(fitAddon)
window.ipcBridge.emit('terminalInit', {
cwd: editorStore.workingDirectory
})
setTimeout(() => {
term.open(terminalElm.value)
fitAddon.fit()
Expand All @@ -84,13 +121,26 @@ onMounted(async () => {
term.onData(ev => {
window.ipcBridge.emit('terminalInput', ev)
})
setTimeout(() => {
window.ipcBridge.emit('terminalInit', {
cwd: editorStore.workingDirectory,
shell: editorStore.terminalShell,
args: editorStore.terminalArgs
})
})
})
})
}
onBeforeUnmount(() => {
function killTerminal () {
window.ipcBridge.unsubscribe('terminal.incomingData', handleTerminalOutput)
window.ipcBridge.emit('terminalDestroy')
})
}
// HOOKS
onMounted(initTerminal)
onBeforeUnmount(killTerminal)
</script>

Expand Down
23 changes: 23 additions & 0 deletions src/stores/editor.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import { defineStore } from 'pinia'
import { decorationsStore } from 'src/stores/models'

// -> Default Values

const defaultShell = {
cmd: 'bash',
args: ''
}
switch (process.env.OS_PLATFORM) {
case 'darwin':
defaultShell.cmd = 'zsh'
defaultShell.args = ''
break
case 'win32':
defaultShell.cmd = 'pwsh.exe'
defaultShell.args = '-NoLogo -NoProfile'
break
}

// -> Editor Store

export const useEditorStore = defineStore('editor', {
state: () => ({
animationEffects: true,
Expand Down Expand Up @@ -42,6 +61,8 @@ export const useEditorStore = defineStore('editor', {
symbols: [],
tabSize: 2,
telemetry: false,
terminalShell: defaultShell.cmd,
terminalArgs: defaultShell.args,
theme: 'ietf-dark',
translucencyEffects: true,
validationChecksCurrent: null,
Expand Down Expand Up @@ -147,6 +168,8 @@ export const useEditorStore = defineStore('editor', {
'previewPaneShown',
'tabSize',
'telemetry',
'terminalShell',
'terminalArgs',
'theme',
'translucencyEffects',
'wordWrap',
Expand Down

0 comments on commit 36a3259

Please sign in to comment.