WIP terminal integration

This commit is contained in:
2025-09-04 00:26:22 -05:00
parent 73f25aae1c
commit ed89f34d40
16 changed files with 234 additions and 1 deletions

View File

@@ -4,6 +4,7 @@
<editors></editors>
<search-replace></search-replace>
<markdown-preview></markdown-preview>
<terminal></terminal>
<lsp-manager></lsp-manager>
</div>

View File

@@ -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',

View File

@@ -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",

View 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();
}
}

View File

@@ -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 = [];

View File

@@ -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";

View File

@@ -7,4 +7,4 @@
<code-view [mode]="'mini-map'"></code-view>
</div>
<div>
</div>

View File

@@ -0,0 +1,6 @@
<div class="col">
<div class="row">
<div class="terminal-container" #terminalElm>
</div>
</div>
</div>

View 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();
}
}
}

View File

@@ -20,6 +20,7 @@ declare global {
},
main: {
onMenuActions: (arg0: any) => Promise<string>,
onTerminalActions: (arg0: any) => Promise<string>,
quit: any,
toggleFullScreen: any,
},