From ed89f34d40839ea12befa09652efbb444ddfd459 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 4 Sep 2025 00:26:22 -0500 Subject: [PATCH] WIP terminal integration --- README.md | 9 ++ newton/app.js | 2 + newton/preload.js | 1 + newton/terminal.js | 35 +++++ package.json | 2 + src/app/app.component.html | 1 + src/app/app.component.ts | 2 + src/app/common/configs/keybindings.config.ts | 5 + .../editor/terminal/terminal.service.ts | 23 +++ src/app/editor/code-view/view.base.ts | 8 + src/app/editor/code-view/view.component.ts | 1 + src/app/editor/editors.component.html | 2 +- .../editor/terminal/terminal.component.css | 0 .../editor/terminal/terminal.component.html | 6 + src/app/editor/terminal/terminal.component.ts | 137 ++++++++++++++++++ src/polyfills.ts | 1 + 16 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 newton/terminal.js create mode 100644 src/app/common/services/editor/terminal/terminal.service.ts create mode 100644 src/app/editor/terminal/terminal.component.css create mode 100644 src/app/editor/terminal/terminal.component.html create mode 100644 src/app/editor/terminal/terminal.component.ts diff --git a/README.md b/README.md index cd4a134..11d843c 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ # Newton + +# Additional Tools +* [Fzf](https://github.com/junegunn/fzf) +* [bat (cat but has highlighting)](https://github.com/sharkdp/bat) +* [ripgrep (very fast grep alternative)](https://github.com/BurntSushi/ripgrep/tree/master) + +` +sudo pacman -Sy bat ripgrep fzf +` \ No newline at end of file diff --git a/newton/app.js b/newton/app.js index be91b56..f60a6eb 100644 --- a/newton/app.js +++ b/newton/app.js @@ -5,6 +5,7 @@ const { menu } = require('./menu'); const { systemTray } = require('./system-tray'); const { argsParser } = require('./args-parser'); const { settingsManager } = require('./settings-manager'); +const { terminal } = require('./terminal'); const { newtonFs } = require('./fs'); const { newtonIPC } = require('./ipc'); @@ -55,6 +56,7 @@ const createWindow = (startType = "build", debug = false, args = []) => { menu.load(window); systemTray.load(menu.menuStruct); + terminal.load(window); // window.setAutoHideMenuBar(true) diff --git a/newton/preload.js b/newton/preload.js index e69c620..b1f8348 100644 --- a/newton/preload.js +++ b/newton/preload.js @@ -9,6 +9,7 @@ contextBridge.exposeInMainWorld('electron', { contextBridge.exposeInMainWorld('main', { onMenuActions: (callback) => ipcRenderer.on('menu-actions', (_event, action) => callback(action)), + onTerminalActions: (callback) => ipcRenderer.on('terminal-actions', (_event, action) => callback(action)), quit: () => ipcRenderer.invoke("quit"), toggleFullScreen: () => ipcRenderer.invoke("toggleFullScreen"), }); diff --git a/newton/terminal.js b/newton/terminal.js new file mode 100644 index 0000000..1829653 --- /dev/null +++ b/newton/terminal.js @@ -0,0 +1,35 @@ + +const { ipcMain } = require('electron'); +// const pty = require('node-pty'); +const os = require("os"); + + +const shell = "win32" === os.platform() ? "powershell.exe" : "bash"; + + +const load = (win) => { + // const ptyProcess = pty.spawn(shell, [], { + // name: "xterm-color", + // cols: 172, + // rows: 256, + // cwd: process.env.HOME, + // env: process.env + // }); + + // ptyProcess.on('data', function(data) { + // win.webContents.send("terminal-actions", data); + // }); + + // ipcMain.on("terminal-keystroke", (event, key) => { + // ptyProcess.write(key); + // }); + +} + +module.exports = { + terminal: { + load: load + } +}; + + diff --git a/package.json b/package.json index 1dfeb53..21b2937 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@angular/core": "19.2.0", "@angular/forms": "19.2.0", "@angular/platform-browser": "19.2.0", + "@xterm/xterm": "5.5.0", "ace-builds": "1.43.0", "ace-diff": "3.0.3", "ace-layout": "1.5.0", @@ -61,6 +62,7 @@ "electron-fetch": "1.9.1", "express": "4.18.2", "node-fetch": "3.3.2", + "node-pty": "^1.0.0", "rxjs": "7.8.0", "socket.io": "4.8.1", "uuid": "11.1.0", diff --git a/src/app/app.component.html b/src/app/app.component.html index 34f4166..292a27a 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -4,6 +4,7 @@ + \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7e50850..dab7eda 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -5,6 +5,7 @@ import { TabsComponent } from './editor/tabs/tabs.component'; import { EditorsComponent } from './editor/editors.component'; import { SearchReplaceComponent } from "./editor/search-replace/search-replace.component"; import { MarkdownPreviewComponent } from "./editor/markdown-preview/markdown-preview.component"; +import { TerminalComponent } from "./editor/terminal/terminal.component"; import { LspManagerComponent } from "./editor/lsp-manager/lsp-manager.component"; @@ -17,6 +18,7 @@ import { LspManagerComponent } from "./editor/lsp-manager/lsp-manager.component" EditorsComponent, SearchReplaceComponent, MarkdownPreviewComponent, + TerminalComponent, LspManagerComponent, ], templateUrl: './app.component.html', diff --git a/src/app/common/configs/keybindings.config.ts b/src/app/common/configs/keybindings.config.ts index 6c8a513..197fce6 100644 --- a/src/app/common/configs/keybindings.config.ts +++ b/src/app/common/configs/keybindings.config.ts @@ -37,6 +37,11 @@ export const Keybindings: Array<{}> = [ bindKey: {win: "ctrl-r", mac: "ctrl-r"}, readOnly: false }, { + + name: "terminalPopup", + bindKey: {win: "ctrl-shift-.", mac: "ctrl-shift-."}, + readOnly: false + }, { name: "newFile", bindKey: {win: "ctrl-t", mac: "ctrl-t"}, service: "editorsService", diff --git a/src/app/common/services/editor/terminal/terminal.service.ts b/src/app/common/services/editor/terminal/terminal.service.ts new file mode 100644 index 0000000..b40d0d0 --- /dev/null +++ b/src/app/common/services/editor/terminal/terminal.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { ReplaySubject, Observable } from 'rxjs'; + +import { ServiceMessage } from '../../../types/service-message.type'; + + + +@Injectable({ + providedIn: 'root' +}) +export class TerminalService { + private messageSubject: ReplaySubject = new ReplaySubject(1); + + + public sendMessage(data: ServiceMessage): void { + this.messageSubject.next(data); + } + + public getMessage$(): Observable { + return this.messageSubject.asObservable(); + } + +} \ No newline at end of file diff --git a/src/app/editor/code-view/view.base.ts b/src/app/editor/code-view/view.base.ts index 611e883..792f672 100644 --- a/src/app/editor/code-view/view.base.ts +++ b/src/app/editor/code-view/view.base.ts @@ -14,6 +14,7 @@ import { EditorsService } from '../../common/services/editor/editors.service'; import { FilesService } from '../../common/services/files.service'; import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service'; import { MarkdownPreviewService } from '../../common/services/editor/markdown-preview/markdown-preview.service'; +import { TerminalService } from '../../common/services/editor/terminal/terminal.service'; import { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service'; import { EditorSettings } from "../../common/configs/editor.config"; @@ -38,6 +39,7 @@ export class CodeViewBase { protected filesService: FilesService = inject(FilesService); protected searchReplaceService: SearchReplaceService = inject(SearchReplaceService); protected markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService); + protected terminalService: TerminalService = inject(TerminalService); protected lspManagerService: LspManagerService = inject(LspManagerService); @ViewChild('editor') editorElm!: ElementRef; @@ -132,6 +134,12 @@ export class CodeViewBase { // this.editor.execCommand("replace"); } + public terminalPopup() { + let message = new ServiceMessage(); + message.action = "toggle-terminal"; + this.terminalService.sendMessage(message); + } + public showFilesList() { let paths = this.filesService.getAllPaths(); let stubPaths = []; diff --git a/src/app/editor/code-view/view.component.ts b/src/app/editor/code-view/view.component.ts index fe9a2ca..7c16d09 100644 --- a/src/app/editor/code-view/view.component.ts +++ b/src/app/editor/code-view/view.component.ts @@ -150,6 +150,7 @@ export class CodeViewComponent extends CodeViewBase { this.editorsService.sendMessage(message); this.searchReplaceService.sendMessage(message); + this.terminalService.sendMessage(message); message = new ServiceMessage(); message.action = "set-active-editor"; diff --git a/src/app/editor/editors.component.html b/src/app/editor/editors.component.html index 5e033cd..471fe4e 100644 --- a/src/app/editor/editors.component.html +++ b/src/app/editor/editors.component.html @@ -7,4 +7,4 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/src/app/editor/terminal/terminal.component.css b/src/app/editor/terminal/terminal.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/editor/terminal/terminal.component.html b/src/app/editor/terminal/terminal.component.html new file mode 100644 index 0000000..1621916 --- /dev/null +++ b/src/app/editor/terminal/terminal.component.html @@ -0,0 +1,6 @@ +
+
+
+
+
+
diff --git a/src/app/editor/terminal/terminal.component.ts b/src/app/editor/terminal/terminal.component.ts new file mode 100644 index 0000000..e9bd5ff --- /dev/null +++ b/src/app/editor/terminal/terminal.component.ts @@ -0,0 +1,137 @@ +import { + Component, + DestroyRef, + ElementRef, + HostBinding, + ViewChild, + inject +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +// import { Terminal } from 'xterm'; +import { Terminal } from '@xterm/xterm'; +// import { FitAddon } from 'xterm-addon-fit'; + +import { TerminalService } from '../../common/services/editor/terminal/terminal.service'; + +import { ServiceMessage } from '../../common/types/service-message.type'; + + + +@Component({ + selector: 'terminal', + standalone: true, + imports: [ + ], + templateUrl: './terminal.component.html', + styleUrl: './terminal.component.css', + host: { + 'class': 'row terminal', + "(keyup)": "globalTerminalKeyHandler($event)" + } +}) +export class TerminalComponent { + readonly #destroyRef: DestroyRef = inject(DestroyRef); + + private terminalService: TerminalService = inject(TerminalService); + private terminal: Terminal = new Terminal(); + + @HostBinding("class.hidden") isHidden: boolean = true; + @ViewChild("terminalElm") terminalElm!: ElementRef; + + private editor!: any; + + constructor() { + this.loadSubscribers(); + this.loadMainSubscribers(); + } + + private ngAfterViewInit(): void { + this.loadTerminal(); + } + + // Note: https://stackoverflow.com/questions/63390143/how-do-i-connect-xterm-jsin-electron-to-a-real-working-command-prompt + private loadTerminal() { + // const fitAddon = new FitAddon(); + // this.terminal.loadAddon(fitAddon); + + this.terminal.open(this.terminalElm.nativeElement); + this.terminal.onData(e => { + console.log(e); + // ipcRenderer.send("terminal-into", e); + // window.main.quit(); + } ); + + // ipcRenderer.on('terminal-actions', (event, data) => { + // this.terminal.write(data); + // }) + + // Make the terminal's size and geometry fit the size of #terminal-container + // fitAddon.fit(); + } + + private loadSubscribers() { + this.terminalService.getMessage$().pipe( + takeUntilDestroyed(this.#destroyRef) + ).subscribe((message: ServiceMessage) => { + switch ( message.action ) { + case "toggle-terminal": + this.toggleTerminal(message); + break; + case "set-active-editor": + this.setActiveEditor(message); + break; + default: + break; + } + }); + } + + private loadMainSubscribers() { + window.main.onTerminalActions(async (action: string) => { + this.terminal.write(action); + // switch ( action ) { + // case "terminal-actions": + // break; + // default: + // break; + // } + }); + } + + + private toggleTerminal(message: ServiceMessage) { + this.isHidden = !this.isHidden; + + if (this.isHidden) { + this.editor.focus(); + return; + } + + // setTimeout(() => { + // }, 200); + } + + + private setActiveEditor(message: ServiceMessage) { + if (this.editor == message.rawData) return; + + this.editor = message.rawData; + + if (this.isHidden) return; + } + + public hideTerminal() { + this.isHidden = true; + this.editor.focus(); + } + + public globalTerminalKeyHandler(event: any) { + if (event.ctrlKey && event.key === ".") { + this.hideTerminal(); + } + } + + + +} \ No newline at end of file diff --git a/src/polyfills.ts b/src/polyfills.ts index c158352..1b2e90b 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -20,6 +20,7 @@ declare global { }, main: { onMenuActions: (arg0: any) => Promise, + onTerminalActions: (arg0: any) => Promise, quit: any, toggleFullScreen: any, },