feat: added embedded terminal
Signed-off-by: mellobacon <mellodev@outlook.com>
mellobacon committed
Jan 31, 2024 at 22:30 UTC
63733a14467a5f0c648f6f940a92123c9fbf5922
10 files changed
+449
-29
src-tauri/Cargo.lock
+150
-2
@@ -580,6 +580,12 @@ version = "0.2.0"
580
source = "registry+https://github.com/rust-lang/crates.io-index"
581
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
582
583
+[[package]]
584
+name = "downcast-rs"
585
+version = "1.2.0"
586
+source = "registry+https://github.com/rust-lang/crates.io-index"
587
+checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
588
+
589
[[package]]
590
name = "dtoa"
591
version = "1.0.9"
@@ -611,7 +617,7 @@ dependencies = [
617
"rustc_version",
618
"toml 0.7.6",
619
"vswhom",
614
- "winreg",
620
+ "winreg 0.11.0",
621
]
622
623
[[package]]
@@ -686,10 +692,21 @@ version = "0.3.6"
692
source = "registry+https://github.com/rust-lang/crates.io-index"
693
checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
694
dependencies = [
689
- "memoffset",
695
+ "memoffset 0.9.0",
696
"rustc_version",
697
]
698
699
+[[package]]
700
+name = "filedescriptor"
701
+version = "0.8.2"
702
+source = "registry+https://github.com/rust-lang/crates.io-index"
703
+checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
704
+dependencies = [
705
+ "libc",
706
+ "thiserror",
707
+ "winapi",
708
+]
709
+
710
[[package]]
711
name = "filetime"
712
version = "0.2.22"
@@ -1345,6 +1362,15 @@ dependencies = [
1362
"cfg-if",
1363
]
1364
1365
+[[package]]
1366
+name = "ioctl-rs"
1367
+version = "0.1.6"
1368
+source = "registry+https://github.com/rust-lang/crates.io-index"
1369
+checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d"
1370
+dependencies = [
1371
+ "libc",
1372
+]
1373
+
1374
[[package]]
1375
name = "itoa"
1376
version = "0.4.8"
@@ -1564,6 +1590,15 @@ version = "2.5.0"
1590
source = "registry+https://github.com/rust-lang/crates.io-index"
1591
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
1592
1593
+[[package]]
1594
+name = "memoffset"
1595
+version = "0.6.5"
1596
+source = "registry+https://github.com/rust-lang/crates.io-index"
1597
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
1598
+dependencies = [
1599
+ "autocfg",
1600
+]
1601
+
1602
[[package]]
1603
name = "memoffset"
1604
version = "0.9.0"
@@ -1629,6 +1664,20 @@ version = "1.0.4"
1664
source = "registry+https://github.com/rust-lang/crates.io-index"
1665
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
1666
1667
+[[package]]
1668
+name = "nix"
1669
+version = "0.25.1"
1670
+source = "registry+https://github.com/rust-lang/crates.io-index"
1671
+checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
1672
+dependencies = [
1673
+ "autocfg",
1674
+ "bitflags 1.3.2",
1675
+ "cfg-if",
1676
+ "libc",
1677
+ "memoffset 0.6.5",
1678
+ "pin-utils",
1679
+]
1680
+
1681
[[package]]
1682
name = "nodrop"
1683
version = "0.1.14"
@@ -1683,6 +1732,7 @@ dependencies = [
1732
"encoding_rs",
1733
"infer 0.3.7",
1734
"log",
1735
+ "portable-pty",
1736
"serde",
1737
"serde_json",
1738
"tauri",
@@ -1691,6 +1741,7 @@ dependencies = [
1741
"tauri-plugin-log",
1742
"tauri-plugin-store",
1743
"time",
1744
+ "tokio",
1745
"trash",
1746
"windows 0.32.0",
1747
]
@@ -2038,6 +2089,27 @@ dependencies = [
2089
"miniz_oxide",
2090
]
2091
2092
+[[package]]
2093
+name = "portable-pty"
2094
+version = "0.8.1"
2095
+source = "registry+https://github.com/rust-lang/crates.io-index"
2096
+checksum = "806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be"
2097
+dependencies = [
2098
+ "anyhow",
2099
+ "bitflags 1.3.2",
2100
+ "downcast-rs",
2101
+ "filedescriptor",
2102
+ "lazy_static",
2103
+ "libc",
2104
+ "log",
2105
+ "nix",
2106
+ "serial",
2107
+ "shared_library",
2108
+ "shell-words",
2109
+ "winapi",
2110
+ "winreg 0.10.1",
2111
+]
2112
+
2113
[[package]]
2114
name = "ppv-lite86"
2115
version = "0.2.17"
@@ -2477,6 +2549,48 @@ dependencies = [
2549
"syn 2.0.29",
2550
]
2551
2552
+[[package]]
2553
+name = "serial"
2554
+version = "0.4.0"
2555
+source = "registry+https://github.com/rust-lang/crates.io-index"
2556
+checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86"
2557
+dependencies = [
2558
+ "serial-core",
2559
+ "serial-unix",
2560
+ "serial-windows",
2561
+]
2562
+
2563
+[[package]]
2564
+name = "serial-core"
2565
+version = "0.4.0"
2566
+source = "registry+https://github.com/rust-lang/crates.io-index"
2567
+checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581"
2568
+dependencies = [
2569
+ "libc",
2570
+]
2571
+
2572
+[[package]]
2573
+name = "serial-unix"
2574
+version = "0.4.0"
2575
+source = "registry+https://github.com/rust-lang/crates.io-index"
2576
+checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7"
2577
+dependencies = [
2578
+ "ioctl-rs",
2579
+ "libc",
2580
+ "serial-core",
2581
+ "termios",
2582
+]
2583
+
2584
+[[package]]
2585
+name = "serial-windows"
2586
+version = "0.4.0"
2587
+source = "registry+https://github.com/rust-lang/crates.io-index"
2588
+checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162"
2589
+dependencies = [
2590
+ "libc",
2591
+ "serial-core",
2592
+]
2593
+
2594
[[package]]
2595
name = "serialize-to-javascript"
2596
version = "0.1.1"
@@ -2529,6 +2643,22 @@ dependencies = [
2643
"lazy_static",
2644
]
2645
2646
+[[package]]
2647
+name = "shared_library"
2648
+version = "0.1.9"
2649
+source = "registry+https://github.com/rust-lang/crates.io-index"
2650
+checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
2651
+dependencies = [
2652
+ "lazy_static",
2653
+ "libc",
2654
+]
2655
+
2656
+[[package]]
2657
+name = "shell-words"
2658
+version = "1.1.0"
2659
+source = "registry+https://github.com/rust-lang/crates.io-index"
2660
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
2661
+
2662
[[package]]
2663
name = "simd-adler32"
2664
version = "0.3.7"
@@ -3004,6 +3134,15 @@ dependencies = [
3134
"utf-8",
3135
]
3136
3137
+[[package]]
3138
+name = "termios"
3139
+version = "0.2.2"
3140
+source = "registry+https://github.com/rust-lang/crates.io-index"
3141
+checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
3142
+dependencies = [
3143
+ "libc",
3144
+]
3145
+
3146
[[package]]
3147
name = "thin-slice"
3148
version = "0.1.1"
@@ -3884,6 +4023,15 @@ dependencies = [
4023
"memchr",
4024
]
4025
4026
+[[package]]
4027
+name = "winreg"
4028
+version = "0.10.1"
4029
+source = "registry+https://github.com/rust-lang/crates.io-index"
4030
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
4031
+dependencies = [
4032
+ "winapi",
4033
+]
4034
+
4035
[[package]]
4036
name = "winreg"
4037
version = "0.11.0"
src-tauri/Cargo.toml
+2
@@ -24,6 +24,8 @@ tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", br
24
time = "0.3.28"
25
log = "^0.4"
26
infer = "0.3"
27
+portable-pty = "0.8.1"
28
+tokio = { version = "1", features = ["time"] }
29
30
[dev-dependencies]
31
windows = "0.32.0"
src-tauri/src/main.rs
+53
-5
@@ -11,21 +11,45 @@ const LOG_TARGETS: [LogTarget; 2] = [LogTarget::Stdout, LogTarget::LogDir];
11
12
use std::collections::HashMap;
13
use std::fs::File;
14
-use std::io::Write;
14
+use std::io::{Write, BufReader};
15
use std::path::{Path, PathBuf};
16
use std::process::Command;
17
-use std::{env, fs};
17
+use std::{env, fs, sync::Arc};
18
19
-use tauri::{App, Manager, Wry};
19
+use tauri::{App, Manager, Wry, State, async_runtime::Mutex as AsyncMutex};
20
21
-mod encoding;
21
use encoding::BOM;
22
use log::{error, info, Level};
23
use tauri::plugin::TauriPlugin;
24
use tauri_plugin_log::{LogTarget, RotationStrategy};
25
+use portable_pty::{native_pty_system, PtySize};
26
27
use crate::encoding::convert_to_u16;
28
29
+mod encoding;
30
+mod terminal;
31
+
32
+#[tauri::command]
33
+// create a shell and add to it the $TERM env variable so we can use clear and other commands
34
+async fn async_create_shell(state: State<'_, terminal::AppState>, path: &str) -> Result<(), String> {
35
+ terminal::async_create_shell(state, path).await
36
+}
37
+
38
+#[tauri::command]
39
+async fn async_write_to_pty(data: &str, state: State<'_, terminal::AppState>) -> Result<(), ()> {
40
+ terminal::async_write_to_pty(data, state).await
41
+}
42
+
43
+#[tauri::command]
44
+async fn async_read_from_pty(state: State<'_, terminal::AppState>) -> Result<Option<String>, ()> {
45
+ terminal::async_read_from_pty(state).await
46
+}
47
+
48
+#[tauri::command]
49
+async fn async_resize_pty(rows: u16, cols: u16, state: State<'_, terminal::AppState>) -> Result<(), ()> {
50
+ terminal::async_resize_pty(rows, cols, state).await
51
+}
52
+
53
#[tauri::command]
54
fn open_in_explorer(path: &str) {
55
// FOR OTHER OS REFER - https://doc.rust-lang.org/std/env/consts/constant.OS.html
@@ -340,7 +364,27 @@ fn main() {
364
original(info);
365
error!("[FATAL]: {:?}", info.to_string());
366
}));
367
+
368
+ let pty_system = native_pty_system();
369
+
370
+ let pty_pair = pty_system
371
+ .openpty(PtySize {
372
+ rows: 24,
373
+ cols: 80,
374
+ pixel_width: 0,
375
+ pixel_height: 0,
376
+ })
377
+ .unwrap();
378
+
379
+ let reader = pty_pair.master.try_clone_reader().unwrap();
380
+ let writer = pty_pair.master.take_writer().unwrap();
381
+
382
tauri::Builder::default()
383
+ .manage(terminal::AppState {
384
+ pty_pair: Arc::new(AsyncMutex::new(pty_pair)),
385
+ writer: Arc::new(AsyncMutex::new(writer)),
386
+ reader: Arc::new(AsyncMutex::new(BufReader::new(reader))),
387
+ })
388
.invoke_handler(tauri::generate_handler![
389
open_in_explorer,
390
delete_file,
@@ -351,7 +395,11 @@ fn main() {
395
write_file,
396
open_in_default,
397
is_supported,
354
- open_terminal
398
+ open_terminal,
399
+ async_create_shell,
400
+ async_read_from_pty,
401
+ async_resize_pty,
402
+ async_write_to_pty
403
])
404
.plugin(tauri_plugin_fs_watch::init())
405
.plugin(tauri_plugin_store::Builder::default().build())
src-tauri/src/terminal.rs
new
+84
@@ -0,0 +1,84 @@
1
+use std::{
2
+ io::{BufRead, BufReader, Read, Write},
3
+ process::exit,
4
+ sync::Arc,
5
+ thread::{self},
6
+};
7
+use tauri::{async_runtime::Mutex as AsyncMutex, State};
8
+use portable_pty::{CommandBuilder, PtyPair, PtySize};
9
+
10
+pub struct AppState {
11
+ pub(crate) pty_pair: Arc<AsyncMutex<PtyPair>>,
12
+ pub(crate) writer: Arc<AsyncMutex<Box<dyn Write + Send>>>,
13
+ pub(crate) reader: Arc<AsyncMutex<BufReader<Box<dyn Read + Send>>>>,
14
+}
15
+
16
+pub async fn async_create_shell(state: State<'_, AppState>, path: &str) -> Result<(), String> {
17
+ #[cfg(target_os = "windows")]
18
+ let mut cmd = CommandBuilder::new("powershell.exe");
19
+
20
+ #[cfg(not(target_os = "windows"))]
21
+ let mut cmd = CommandBuilder::new("bash");
22
+
23
+ // add the $TERM env variable so we can use clear and other commands
24
+
25
+ #[cfg(target_os = "windows")]
26
+ cmd.env("TERM", "cygwin");
27
+
28
+ #[cfg(not(target_os = "windows"))]
29
+ cmd.env("TERM", "xterm-256color");
30
+
31
+ cmd.cwd(path);
32
+
33
+ let mut child = state
34
+ .pty_pair
35
+ .lock()
36
+ .await
37
+ .slave
38
+ .spawn_command(cmd)
39
+ .map_err(|err| err.to_string())?;
40
+
41
+ thread::spawn(move || {
42
+ let status = child.wait().unwrap();
43
+ exit(status.exit_code() as i32)
44
+ });
45
+ Ok(())
46
+}
47
+pub async fn async_write_to_pty(data: &str, state: State<'_, AppState>) -> Result<(), ()> {
48
+ write!(state.writer.lock().await, "{}", data).map_err(|_| ())
49
+}
50
+pub async fn async_read_from_pty(state: State<'_, AppState>) -> Result<Option<String>, ()> {
51
+ let mut reader = state.reader.lock().await;
52
+ let data = {
53
+ // Read all available text
54
+ let data = reader.fill_buf().map_err(|_| ())?;
55
+
56
+ // Send the data to the webview if necessary
57
+ if data.len() > 0 {
58
+ std::str::from_utf8(data)
59
+ .map(|v| Some(v.to_string()))
60
+ .map_err(|_| ())?
61
+ } else {
62
+ None
63
+ }
64
+ };
65
+
66
+ if let Some(data) = &data {
67
+ reader.consume(data.len());
68
+ }
69
+
70
+ Ok(data)
71
+}
72
+pub async fn async_resize_pty(rows: u16, cols: u16, state: State<'_, AppState>) -> Result<(), ()> {
73
+ state
74
+ .pty_pair
75
+ .lock()
76
+ .await
77
+ .master
78
+ .resize(PtySize {
79
+ rows,
80
+ cols,
81
+ ..Default::default()
82
+ })
83
+ .map_err(|_| ())
84
+}
\ No newline at end of file
src/App.svelte
+13
-8
@@ -14,6 +14,8 @@
14
import { error } from "tauri-plugin-log-api";
15
import ToolView from "./lib/ToolView.svelte";
16
import { getExtensions } from "./config/extensionhandler";
17
+ import { fitTerminal } from "./lib/Terminal.svelte";
18
+ import { getThemeProperty } from "./config/themehandler";
19
let resolution = writable(0);
20
21
let minPanelSize = 10;
@@ -21,7 +23,7 @@
23
let bottomPanelSize = 20;
24
25
onMount(async () => {
24
- getExtensions();
26
+ await getExtensions();
27
await loadDefaultSettings();
28
let size = await appWindow.innerSize();
29
resolution.set(size.width);
@@ -114,18 +116,16 @@
116
{/if}
117
<Pane>
118
<div class="__">
117
- <Splitpanes horizontal theme="editor-panes">
118
- <Pane>
119
+ <Splitpanes horizontal theme="editor-panes" on:resize={fitTerminal} on:resized={fitTerminal} class="{!$showBottomPanel ? "hidden" : ""}">
120
+ <Pane size={$showBottomPanel ? 100 - bottomPanelSize : 100}>
121
<div id="container">
122
<EditorTabList />
123
<div id="tabview" class:hidden={$hidden}></div>
124
</div>
125
</Pane>
124
- {#if $showBottomPanel}
125
- <Pane minSize={5} bind:size={bottomPanelSize} class="view-bottom-pane">
126
- <ToolView content={$editortool.content}></ToolView>
127
- </Pane>
128
- {/if}
126
+ <Pane bind:size={bottomPanelSize} maxSize={90} minSize={10} class="view-bottom-pane">
127
+ <ToolView content={$editortool.content} name={$editortool.name}></ToolView>
128
+ </Pane>
129
</Splitpanes>
130
</div>
131
</Pane>
@@ -142,6 +142,11 @@
142
.__ {
143
height: 100%;
144
overflow: hidden;
145
+ :global(.hidden) {
146
+ :global(.splitpanes__splitter), :global(.view-bottom-pane) {
147
+ display: none;
148
+ }
149
+ }
150
}
151
#main {
152
display: flex;
src/config/config.ts
+7
-2
@@ -7,6 +7,7 @@ import { Store } from "tauri-plugin-store-api";
7
import { setEditorFontFamily, setEditorFontSize } from "../lib/Editor.svelte";
8
import { watch } from "tauri-plugin-fs-watch-api";
9
import { info } from "tauri-plugin-log-api";
10
+import { setTerminalState } from "../lib/Statusbar.svelte";
11
12
export const systemfonts = writable([]);
13
export const editorfont = writable("");
@@ -82,8 +83,8 @@ export async function loadDefaultSettings() {
83
}
84
)
85
85
- loadTheme(await appSettings.get("nucleus.theme"));
86
- getShortcuts();
86
+ await getShortcuts();
87
+ await loadTheme(await appSettings.get("nucleus.theme"));
88
89
appSettings.onKeyChange("editor.fontSize", (value: number) => {
90
setEditorFontSize(value);
@@ -94,6 +95,10 @@ export async function loadDefaultSettings() {
95
appSettings.onKeyChange("nucleus.theme", (value: string) => {
96
loadTheme(value);
97
})
98
+ appSettings.onKeyChange("nucleus.useExternalTerminal", (value: boolean) => {
99
+ setTerminalState(value);
100
+ })
101
+ setTerminalState(await appSettings.get("nucleus.useExternalTerminal"))
102
info("Settings initialized", {file: "config.ts", line: 97});
103
}
104
src/lib/File.ts
+3
@@ -5,6 +5,7 @@ import { filetree } from "./FileTree.svelte";
5
import { watchImmediate } from "tauri-plugin-fs-watch-api";
6
import { openFileTree } from "./Sidebar.svelte";
7
import { info, trace, warn, error } from "tauri-plugin-log-api";
8
+import { homeDir } from "@tauri-apps/api/path";
9
10
export async function openFile() {
11
let newPath = await dialog.open() as string;
@@ -16,6 +17,7 @@ export async function openFile() {
17
export const workspaceName = writable("Untitled Workspace");
18
export const dirToLoad = writable("");
19
export const dirLoadFail = writable(false);
20
+export const workingDir = writable(await homeDir());
21
export async function openFolder() {
22
dirLoadFail.set(false);
23
let directory = await dialog.open({directory: true}) as string;
@@ -25,6 +27,7 @@ export async function openFolder() {
27
return
28
};
29
dirToLoad.set(directory.split(path.sep).pop());
30
+ workingDir.set(directory);
31
// if file path is not in the configured scope already, add it
32
// TODO: should configure this so it doesnt access restricted paths based on user permissions
33
await invoke("attempt_file_access", {app_handle: window, p: directory});
src/lib/Statusbar.svelte
+16
-10
@@ -3,10 +3,10 @@
3
import { line_info, language, encoding } from "./Editor.svelte";
4
import { getVersion } from '@tauri-apps/api/app';
5
import { onMount } from "svelte";
6
- import { Terminal } from "carbon-icons-svelte";
7
- import { homeDir } from '@tauri-apps/api/path';
8
- import { filetree } from "./FileTree.svelte";
6
+ import { Terminal as TerminalIcon } from "carbon-icons-svelte";
7
import { invoke } from "@tauri-apps/api";
8
+ import Terminal from "./Terminal.svelte";
9
+ import { workingDir } from "./File";
10
11
let appVersion = "";
12
onMount(async () => {
@@ -14,20 +14,22 @@
14
})
15
16
const tools = [
17
- {name: "Terminal", content: null, icon: Terminal, action: spawnTerminal}
17
+ {name: "Terminal", content: Terminal, icon: TerminalIcon, action: (t) => {spawnTerminal(t)}}
18
]
19
20
- async function spawnTerminal() {
21
- let workingdir = await homeDir();
22
- if ($filetree.length !== 0) {
23
- workingdir = $filetree[0].path;
20
+ async function spawnTerminal(t) {
21
+ if ($externalTerminal) {
22
+ invoke("open_terminal", {path: $workingDir});
23
+ }
24
+ else {
25
+ toggleBottomPanel(t);
26
}
25
- invoke("open_terminal", {path: workingdir});
27
}
28
</script>
29
<script lang="ts" context="module">
30
import { writable } from "svelte/store";
31
export let showBottomPanel = writable(false);
32
+ export let externalTerminal = writable(false);
33
let show = false;
34
35
export let editortool = writable({name: "", content: null});
@@ -39,6 +41,10 @@
41
editortool.set({name: x.name, content: x.content});
42
}
43
}
44
+ export function setTerminalState(value) {
45
+ let v = value === "true" ? true : false;
46
+ externalTerminal.set(v);
47
+ }
48
</script>
49
50
<div id="statusbar">
@@ -49,7 +55,7 @@
55
<div class="editor-tools">
56
{#each tools as tool}
57
<!-- svelte-ignore a11y-click-events-have-key-events -->
52
- <span class="tool" title={tool.name} on:click={() => {tool.action()}}>
58
+ <span class="tool" title={tool.name} on:click={() => {tool.action(tool)}}>
59
<svelte:component this={tool.icon}></svelte:component>
60
</span>
61
{/each}
src/lib/Terminal.svelte
new
+88
@@ -0,0 +1,88 @@
1
+<script lang="ts">
2
+ import "xterm/css/xterm.css";
3
+ import { onMount } from "svelte";
4
+ import {Terminal} from "xterm";
5
+ import {FitAddon} from "xterm-addon-fit";
6
+ import { invoke } from "@tauri-apps/api";
7
+ import { get, writable } from "svelte/store";
8
+ import { workingDir } from "./File";
9
+ import { getThemeProperty } from "../config/themehandler";
10
+
11
+ let terminalElement: HTMLElement;
12
+
13
+ function initializeXterm() {
14
+ terminalController.loadAddon(termFit);
15
+ terminalController.open(terminalElement);
16
+
17
+ initShell();
18
+ terminalController.onData(writeToPty);
19
+ fitTerminal();
20
+
21
+ window.requestAnimationFrame(readFromPty);
22
+ }
23
+
24
+ onMount(async () => {
25
+ initializeXterm();
26
+ });
27
+</script>
28
+<script lang="ts" context="module">
29
+
30
+export const termTheme = writable();
31
+let terminalController = new Terminal({
32
+ fontFamily: "Cascadia Mono",
33
+ fontSize: 14
34
+});
35
+
36
+export function updateTermTheme() {
37
+ terminalController.options.theme = get(termTheme);
38
+}
39
+
40
+let termFit = new FitAddon();
41
+export function fitTerminal() {
42
+ termFit.fit();
43
+ void invoke<string>("async_resize_pty", {
44
+ rows: terminalController.rows,
45
+ cols: terminalController.cols,
46
+ });
47
+}
48
+function writeToTerminal(data: string) {
49
+ return new Promise<void>((r) => {
50
+ terminalController.write(data, () => r());
51
+ });
52
+}
53
+
54
+function writeToPty(data: string) {
55
+ void invoke("async_write_to_pty", {
56
+ data,
57
+ });
58
+}
59
+
60
+function initShell() {
61
+ updateTermTheme();
62
+ invoke("async_create_shell", {path: get(workingDir)}).catch((error) => {
63
+ // on linux it seem to to "Operation not permitted (os error 1)" but it still works because echo $SHELL give /bin/bash
64
+ console.error("Error creating shell:", error);
65
+ });
66
+}
67
+async function readFromPty() {
68
+ const data = await invoke<string>("async_read_from_pty");
69
+ if (data) {
70
+ await writeToTerminal(data);
71
+ }
72
+ window.requestAnimationFrame(readFromPty);
73
+}
74
+</script>
75
+
76
+<svelte:window on:resize={fitTerminal}></svelte:window>
77
+
78
+<div id="terminal" bind:this={terminalElement} />
79
+
80
+<style>
81
+ #terminal {
82
+ height: 100%;
83
+ width: 100%;
84
+ }
85
+ :global(.xterm-screen) {
86
+ padding: 10px;
87
+ }
88
+</style>
\ No newline at end of file
src/lib/ToolView.svelte
+33
-2
@@ -1,6 +1,37 @@
1
<script lang="ts">
2
+ import { showBottomPanel } from "./Statusbar.svelte";
3
+
4
export let content = null;
5
+ export let name = "Tool";
6
+
7
+ function closePanel() {
8
+ showBottomPanel.set(false);
9
+ }
10
</script>
11
<div id="toolview">
5
- {content}
6
-</div>
\ No newline at end of file
12
+ <div class="header">{name} <span class="close" on:click={closePanel}></span></div>
13
+ <svelte:component this={content}></svelte:component>
14
+</div>
15
+
16
+<style lang="scss">
17
+.header {
18
+ height: 35px;
19
+ display: flex;
20
+ flex-direction: row;
21
+ align-items: center;
22
+ justify-content: space-between;
23
+ padding: 0 8px;
24
+ .close {
25
+ font-size: 12px;
26
+ padding: 0 7px;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ border-radius: 3px;
31
+ cursor: pointer;
32
+ &::before {
33
+ content: "\2715";
34
+ }
35
+ }
36
+}
37
+</style>
\ No newline at end of file