WIP terminal integration
This commit is contained in:
@@ -1 +1,10 @@
|
|||||||
# Newton
|
# 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
|
||||||
|
`
|
@@ -5,6 +5,7 @@ const { menu } = require('./menu');
|
|||||||
const { systemTray } = require('./system-tray');
|
const { systemTray } = require('./system-tray');
|
||||||
const { argsParser } = require('./args-parser');
|
const { argsParser } = require('./args-parser');
|
||||||
const { settingsManager } = require('./settings-manager');
|
const { settingsManager } = require('./settings-manager');
|
||||||
|
const { terminal } = require('./terminal');
|
||||||
const { newtonFs } = require('./fs');
|
const { newtonFs } = require('./fs');
|
||||||
const { newtonIPC } = require('./ipc');
|
const { newtonIPC } = require('./ipc');
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ const createWindow = (startType = "build", debug = false, args = []) => {
|
|||||||
|
|
||||||
menu.load(window);
|
menu.load(window);
|
||||||
systemTray.load(menu.menuStruct);
|
systemTray.load(menu.menuStruct);
|
||||||
|
terminal.load(window);
|
||||||
|
|
||||||
// window.setAutoHideMenuBar(true)
|
// window.setAutoHideMenuBar(true)
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
|
|
||||||
contextBridge.exposeInMainWorld('main', {
|
contextBridge.exposeInMainWorld('main', {
|
||||||
onMenuActions: (callback) => ipcRenderer.on('menu-actions', (_event, action) => callback(action)),
|
onMenuActions: (callback) => ipcRenderer.on('menu-actions', (_event, action) => callback(action)),
|
||||||
|
onTerminalActions: (callback) => ipcRenderer.on('terminal-actions', (_event, action) => callback(action)),
|
||||||
quit: () => ipcRenderer.invoke("quit"),
|
quit: () => ipcRenderer.invoke("quit"),
|
||||||
toggleFullScreen: () => ipcRenderer.invoke("toggleFullScreen"),
|
toggleFullScreen: () => ipcRenderer.invoke("toggleFullScreen"),
|
||||||
});
|
});
|
||||||
|
35
newton/terminal.js
Normal file
35
newton/terminal.js
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@@ -51,6 +51,7 @@
|
|||||||
"@angular/core": "19.2.0",
|
"@angular/core": "19.2.0",
|
||||||
"@angular/forms": "19.2.0",
|
"@angular/forms": "19.2.0",
|
||||||
"@angular/platform-browser": "19.2.0",
|
"@angular/platform-browser": "19.2.0",
|
||||||
|
"@xterm/xterm": "5.5.0",
|
||||||
"ace-builds": "1.43.0",
|
"ace-builds": "1.43.0",
|
||||||
"ace-diff": "3.0.3",
|
"ace-diff": "3.0.3",
|
||||||
"ace-layout": "1.5.0",
|
"ace-layout": "1.5.0",
|
||||||
@@ -61,6 +62,7 @@
|
|||||||
"electron-fetch": "1.9.1",
|
"electron-fetch": "1.9.1",
|
||||||
"express": "4.18.2",
|
"express": "4.18.2",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
|
"node-pty": "^1.0.0",
|
||||||
"rxjs": "7.8.0",
|
"rxjs": "7.8.0",
|
||||||
"socket.io": "4.8.1",
|
"socket.io": "4.8.1",
|
||||||
"uuid": "11.1.0",
|
"uuid": "11.1.0",
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
<editors></editors>
|
<editors></editors>
|
||||||
<search-replace></search-replace>
|
<search-replace></search-replace>
|
||||||
<markdown-preview></markdown-preview>
|
<markdown-preview></markdown-preview>
|
||||||
|
<terminal></terminal>
|
||||||
|
|
||||||
<lsp-manager></lsp-manager>
|
<lsp-manager></lsp-manager>
|
||||||
</div>
|
</div>
|
@@ -5,6 +5,7 @@ import { TabsComponent } from './editor/tabs/tabs.component';
|
|||||||
import { EditorsComponent } from './editor/editors.component';
|
import { EditorsComponent } from './editor/editors.component';
|
||||||
import { SearchReplaceComponent } from "./editor/search-replace/search-replace.component";
|
import { SearchReplaceComponent } from "./editor/search-replace/search-replace.component";
|
||||||
import { MarkdownPreviewComponent } from "./editor/markdown-preview/markdown-preview.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";
|
import { LspManagerComponent } from "./editor/lsp-manager/lsp-manager.component";
|
||||||
|
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ import { LspManagerComponent } from "./editor/lsp-manager/lsp-manager.component"
|
|||||||
EditorsComponent,
|
EditorsComponent,
|
||||||
SearchReplaceComponent,
|
SearchReplaceComponent,
|
||||||
MarkdownPreviewComponent,
|
MarkdownPreviewComponent,
|
||||||
|
TerminalComponent,
|
||||||
LspManagerComponent,
|
LspManagerComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
|
@@ -37,6 +37,11 @@ export const Keybindings: Array<{}> = [
|
|||||||
bindKey: {win: "ctrl-r", mac: "ctrl-r"},
|
bindKey: {win: "ctrl-r", mac: "ctrl-r"},
|
||||||
readOnly: false
|
readOnly: false
|
||||||
}, {
|
}, {
|
||||||
|
|
||||||
|
name: "terminalPopup",
|
||||||
|
bindKey: {win: "ctrl-shift-.", mac: "ctrl-shift-."},
|
||||||
|
readOnly: false
|
||||||
|
}, {
|
||||||
name: "newFile",
|
name: "newFile",
|
||||||
bindKey: {win: "ctrl-t", mac: "ctrl-t"},
|
bindKey: {win: "ctrl-t", mac: "ctrl-t"},
|
||||||
service: "editorsService",
|
service: "editorsService",
|
||||||
|
23
src/app/common/services/editor/terminal/terminal.service.ts
Normal file
23
src/app/common/services/editor/terminal/terminal.service.ts
Normal file
@@ -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<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
|
||||||
|
|
||||||
|
|
||||||
|
public sendMessage(data: ServiceMessage): void {
|
||||||
|
this.messageSubject.next(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMessage$(): Observable<ServiceMessage> {
|
||||||
|
return this.messageSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -14,6 +14,7 @@ import { EditorsService } from '../../common/services/editor/editors.service';
|
|||||||
import { FilesService } from '../../common/services/files.service';
|
import { FilesService } from '../../common/services/files.service';
|
||||||
import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service';
|
import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service';
|
||||||
import { MarkdownPreviewService } from '../../common/services/editor/markdown-preview/markdown-preview.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 { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service';
|
||||||
|
|
||||||
import { EditorSettings } from "../../common/configs/editor.config";
|
import { EditorSettings } from "../../common/configs/editor.config";
|
||||||
@@ -38,6 +39,7 @@ export class CodeViewBase {
|
|||||||
protected filesService: FilesService = inject(FilesService);
|
protected filesService: FilesService = inject(FilesService);
|
||||||
protected searchReplaceService: SearchReplaceService = inject(SearchReplaceService);
|
protected searchReplaceService: SearchReplaceService = inject(SearchReplaceService);
|
||||||
protected markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService);
|
protected markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService);
|
||||||
|
protected terminalService: TerminalService = inject(TerminalService);
|
||||||
protected lspManagerService: LspManagerService = inject(LspManagerService);
|
protected lspManagerService: LspManagerService = inject(LspManagerService);
|
||||||
|
|
||||||
@ViewChild('editor') editorElm!: ElementRef;
|
@ViewChild('editor') editorElm!: ElementRef;
|
||||||
@@ -132,6 +134,12 @@ export class CodeViewBase {
|
|||||||
// this.editor.execCommand("replace");
|
// this.editor.execCommand("replace");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public terminalPopup() {
|
||||||
|
let message = new ServiceMessage();
|
||||||
|
message.action = "toggle-terminal";
|
||||||
|
this.terminalService.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
public showFilesList() {
|
public showFilesList() {
|
||||||
let paths = this.filesService.getAllPaths();
|
let paths = this.filesService.getAllPaths();
|
||||||
let stubPaths = [];
|
let stubPaths = [];
|
||||||
|
@@ -150,6 +150,7 @@ export class CodeViewComponent extends CodeViewBase {
|
|||||||
|
|
||||||
this.editorsService.sendMessage(message);
|
this.editorsService.sendMessage(message);
|
||||||
this.searchReplaceService.sendMessage(message);
|
this.searchReplaceService.sendMessage(message);
|
||||||
|
this.terminalService.sendMessage(message);
|
||||||
|
|
||||||
message = new ServiceMessage();
|
message = new ServiceMessage();
|
||||||
message.action = "set-active-editor";
|
message.action = "set-active-editor";
|
||||||
|
@@ -7,4 +7,4 @@
|
|||||||
<code-view [mode]="'mini-map'"></code-view>
|
<code-view [mode]="'mini-map'"></code-view>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
</div>
|
0
src/app/editor/terminal/terminal.component.css
Normal file
0
src/app/editor/terminal/terminal.component.css
Normal file
6
src/app/editor/terminal/terminal.component.html
Normal file
6
src/app/editor/terminal/terminal.component.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<div class="terminal-container" #terminalElm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
137
src/app/editor/terminal/terminal.component.ts
Normal file
137
src/app/editor/terminal/terminal.component.ts
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -20,6 +20,7 @@ declare global {
|
|||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
onMenuActions: (arg0: any) => Promise<string>,
|
onMenuActions: (arg0: any) => Promise<string>,
|
||||||
|
onTerminalActions: (arg0: any) => Promise<string>,
|
||||||
quit: any,
|
quit: any,
|
||||||
toggleFullScreen: any,
|
toggleFullScreen: any,
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user