mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-01 20:45:57 +03:00
remove terminal
This commit is contained in:
parent
425ee0ffb1
commit
3c0d4acabe
204
Cargo.lock
generated
204
Cargo.lock
generated
@ -838,7 +838,7 @@ dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset 0.9.0",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
@ -978,12 +978,6 @@ dependencies = [
|
||||
"syn 2.0.31",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.8.0"
|
||||
@ -1351,21 +1345,10 @@ version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
|
||||
dependencies = [
|
||||
"memoffset 0.9.0",
|
||||
"memoffset",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filedescriptor"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"thiserror",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.22"
|
||||
@ -1748,7 +1731,6 @@ dependencies = [
|
||||
"async-trait",
|
||||
"backoff",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"console-subscriber",
|
||||
"diffy",
|
||||
@ -1758,7 +1740,6 @@ dependencies = [
|
||||
"md5",
|
||||
"notify",
|
||||
"num_cpus",
|
||||
"portable-pty",
|
||||
"posthog-rs",
|
||||
"rand 0.8.5",
|
||||
"refinery",
|
||||
@ -1780,12 +1761,10 @@ dependencies = [
|
||||
"tantivy",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-websocket",
|
||||
"tauri-plugin-window-state",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@ -2328,15 +2307,6 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ioctl-rs"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.8.0"
|
||||
@ -2759,15 +2729,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
@ -2812,9 +2773,9 @@ dependencies = [
|
||||
"libc",
|
||||
"mach2",
|
||||
"memmap2 0.5.10",
|
||||
"memoffset 0.9.0",
|
||||
"memoffset",
|
||||
"minidump-common",
|
||||
"nix 0.26.4",
|
||||
"nix",
|
||||
"procfs-core",
|
||||
"scroll",
|
||||
"tempfile",
|
||||
@ -2937,20 +2898,6 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset 0.6.5",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.4"
|
||||
@ -3584,29 +3531,6 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-pty"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 1.3.2",
|
||||
"downcast-rs",
|
||||
"filedescriptor",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"nix 0.25.1",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serial",
|
||||
"shared_library",
|
||||
"shell-words",
|
||||
"winapi",
|
||||
"winreg 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "posthog-rs"
|
||||
version = "0.2.2"
|
||||
@ -4513,48 +4437,6 @@ dependencies = [
|
||||
"syn 2.0.31",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86"
|
||||
dependencies = [
|
||||
"serial-core",
|
||||
"serial-unix",
|
||||
"serial-windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial-core"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial-unix"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7"
|
||||
dependencies = [
|
||||
"ioctl-rs",
|
||||
"libc",
|
||||
"serial-core",
|
||||
"termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial-windows"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"serial-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serialize-to-javascript"
|
||||
version = "0.1.1"
|
||||
@ -4618,16 +4500,6 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_library"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.0"
|
||||
@ -5247,22 +5119,6 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-websocket"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#5b814f56e6368fdec46c4ddb04a07e0923ff995a"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "0.1.0"
|
||||
@ -5381,15 +5237,6 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termios"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-slice"
|
||||
version = "0.1.1"
|
||||
@ -5542,20 +5389,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.8"
|
||||
@ -5761,26 +5594,6 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"native-tls",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
@ -6460,15 +6273,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
|
@ -17,7 +17,6 @@ tauri-build = { version = "1.4", features = [] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.4", features = [ "window-maximize", "window-unmaximize", "process-relaunch", "dialog-open", "fs-read-file", "path-all", "protocol-asset", "shell-open", "system-tray", "window-start-dragging"] }
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri-plugin-websocket = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
notify = { version = "6.0.1" }
|
||||
serde_json = {version = "1.0", features = [ "std", "arbitrary_precision" ] }
|
||||
uuid = "1.4.1"
|
||||
@ -35,9 +34,6 @@ thiserror = "1.0.44"
|
||||
tantivy = "0.20.2"
|
||||
similar = { version = "2.2.1", features = ["unicode"] }
|
||||
tokio = { version = "1.29.1", features = [ "full", "sync", "tracing" ] }
|
||||
tokio-tungstenite = "0.20.0"
|
||||
portable-pty = { version = "0.8.0", features = [ "serde_support" ] }
|
||||
bytes = "1.1.0"
|
||||
futures = "0.3"
|
||||
serde-jsonlines = "0.4.0"
|
||||
sentry-anyhow = "0.31.0"
|
||||
|
@ -3,14 +3,13 @@ use std::{collections::HashMap, ops, path, time};
|
||||
use anyhow::{Context, Result};
|
||||
use futures::executor::block_on;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tokio::task;
|
||||
|
||||
use crate::{
|
||||
bookmarks, deltas, gb_repository,
|
||||
git::{self, diff},
|
||||
keys,
|
||||
project_repository::{self, activity, conflicts},
|
||||
projects, pty, reader, search, sessions, users,
|
||||
projects, reader, search, sessions, users,
|
||||
virtual_branches::{self, target},
|
||||
watcher,
|
||||
};
|
||||
@ -60,17 +59,6 @@ impl TryFrom<&AppHandle> for App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn start_pty_server(&self) -> Result<()> {
|
||||
let self_ = self.clone();
|
||||
task::Builder::new().name("pty-server").spawn(async move {
|
||||
let port = if cfg!(debug_assertions) { 7702 } else { 7703 };
|
||||
if let Err(e) = pty::start_server(port, self_).await {
|
||||
tracing::error!("failed to start pty server: {:#}", e);
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init_project(&self, project: &projects::Project) -> Result<()> {
|
||||
block_on(async move {
|
||||
self.watchers
|
||||
@ -558,26 +546,6 @@ impl App {
|
||||
self.searcher.search(query)
|
||||
}
|
||||
|
||||
pub fn record_pty(&self, project_id: &str, typ: pty::Type, bytes: &[u8]) -> Result<()> {
|
||||
let gb_repository = self.gb_repository(project_id)?;
|
||||
let pty_writer = pty::Writer::new(&gb_repository)?;
|
||||
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
|
||||
let record = pty::Record {
|
||||
timestamp,
|
||||
typ,
|
||||
bytes: bytes.to_vec(),
|
||||
};
|
||||
|
||||
pty_writer.write(&record).context("failed to append pty")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_all_data(&self) -> Result<()> {
|
||||
self.searcher
|
||||
.delete_all_data()
|
||||
|
@ -15,7 +15,6 @@ pub mod lock;
|
||||
pub mod logs;
|
||||
pub mod project_repository;
|
||||
pub mod projects;
|
||||
pub mod pty;
|
||||
pub mod reader;
|
||||
pub mod search;
|
||||
pub mod sessions;
|
||||
|
@ -704,16 +704,12 @@ async fn main() {
|
||||
sentry::configure_scope(|scope| scope.set_user(Some(user.clone().into())))
|
||||
}
|
||||
|
||||
app.start_pty_server()
|
||||
.context("failed to start pty server")?;
|
||||
|
||||
app.init().context("failed to init app")?;
|
||||
|
||||
app_handle.manage(app);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.plugin(tauri_plugin_websocket::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
add_project,
|
||||
|
@ -1,199 +0,0 @@
|
||||
use bytes::BytesMut;
|
||||
use std::{
|
||||
env,
|
||||
io::{Read, Write},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
|
||||
use tokio::net;
|
||||
use tokio_tungstenite::{
|
||||
self,
|
||||
tungstenite::handshake::server::{Request, Response},
|
||||
};
|
||||
|
||||
use crate::app;
|
||||
|
||||
use super::recorder;
|
||||
|
||||
const TERM: &str = "xterm-256color";
|
||||
|
||||
pub async fn accept_connection(app: app::App, stream: net::TcpStream) -> Result<()> {
|
||||
let mut project = None;
|
||||
let copy_uri_callback = |req: &Request, response: Response| {
|
||||
let path = req.uri().path().to_string();
|
||||
if let Some(project_id) = path.split('/').last() {
|
||||
project = match app.get_project(project_id) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
tracing::error!("failed to get project: {}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(response)
|
||||
};
|
||||
|
||||
let mut ws_stream = tokio_tungstenite::accept_hdr_async(stream, copy_uri_callback)
|
||||
.await
|
||||
.with_context(|| "failed to accept connection".to_string())?;
|
||||
|
||||
if project.is_none() {
|
||||
ws_stream
|
||||
.close(None)
|
||||
.await
|
||||
.with_context(|| "failed to close connection".to_string())?;
|
||||
return Ok(());
|
||||
}
|
||||
let project = project.unwrap();
|
||||
|
||||
let (mut ws_sender, mut ws_receiver) = ws_stream.split();
|
||||
|
||||
let pty_system = native_pty_system();
|
||||
|
||||
let pty_pair = pty_system
|
||||
.openpty(PtySize {
|
||||
rows: 24,
|
||||
cols: 80,
|
||||
// Not all systems support pixel_width, pixel_height,
|
||||
// but it is good practice to set it to something
|
||||
// that matches the size of the selected font. That
|
||||
// is more complex than can be shown here in this
|
||||
// brief example though!
|
||||
pixel_width: 0,
|
||||
pixel_height: 0,
|
||||
})
|
||||
.with_context(|| "failed to open pty".to_string())?;
|
||||
|
||||
let mut cmd = if cfg!(target_os = "windows") {
|
||||
// CommandBuilder::new(r"powershell")
|
||||
// CommandBuilder::new(r"C:\Program Files\Git\bin\bash.exe")
|
||||
// CommandBuilder::new(r"ubuntu.exe") // if WSL is active
|
||||
// on UI the user should have the option to choose
|
||||
|
||||
let mut cmd = CommandBuilder::new(r"cmd");
|
||||
|
||||
// this is needed only for cmd.exe
|
||||
// because the prompt does not have an empty space at the end
|
||||
// the prompt should be sepratared from the command being typed, for command parsing
|
||||
cmd.env("PROMPT", "$P$G ");
|
||||
|
||||
cmd
|
||||
} else {
|
||||
let user_default_shell = env::var("SHELL")?;
|
||||
let mut cmd = CommandBuilder::new(user_default_shell);
|
||||
cmd.env("TERM", TERM);
|
||||
cmd.args(["-i"]);
|
||||
cmd
|
||||
};
|
||||
|
||||
// set to project path
|
||||
cmd.cwd(project.path.clone());
|
||||
|
||||
let mut pty_child_process = pty_pair.slave.spawn_command(cmd)?;
|
||||
|
||||
let mut pty_reader = pty_pair.master.try_clone_reader()?;
|
||||
let mut pty_writer = pty_pair.master.take_writer()?;
|
||||
|
||||
let shared_project_id = project.id.clone();
|
||||
let shared_app = app.clone();
|
||||
// it's important to spawn a new thread for the pty reader
|
||||
std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
let mut buffer = BytesMut::with_capacity(1024);
|
||||
|
||||
buffer.resize(1024, 0u8);
|
||||
loop {
|
||||
buffer[0] = 0u8;
|
||||
let tail = &mut buffer[1..];
|
||||
|
||||
match pty_reader.read(tail) {
|
||||
Ok(0) => {
|
||||
// EOF
|
||||
tracing::debug!("0 bytes read from pty. EOF.");
|
||||
if let Err(e) = ws_sender
|
||||
.send(tokio_tungstenite::tungstenite::Message::Close(None))
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"{}: error sending data to websocket: {:#}",
|
||||
shared_project_id,
|
||||
e
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
let data = &buffer[..n + 1];
|
||||
if let Err(e) = ws_sender
|
||||
.send(tokio_tungstenite::tungstenite::Message::Binary(
|
||||
data.to_vec(),
|
||||
))
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"{}: error sending data to websocket: {:#}",
|
||||
shared_project_id,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
shared_app.record_pty(&shared_project_id, recorder::Type::Output, data)
|
||||
{
|
||||
tracing::error!("{}: error recording data: {:#}", shared_project_id, e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error reading from pty: {:#}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("PTY child process killed.");
|
||||
});
|
||||
});
|
||||
|
||||
while let Some(message) = ws_receiver.next().await {
|
||||
match message {
|
||||
Ok(tokio_tungstenite::tungstenite::Message::Binary(msg)) => {
|
||||
let msg_bytes = msg.as_slice();
|
||||
match (msg_bytes[0], msg_bytes[1..].to_vec()) {
|
||||
(0, data) => {
|
||||
if msg_bytes.len().gt(&0) {
|
||||
pty_writer.write_all(&data)?;
|
||||
if let Err(e) =
|
||||
app.record_pty(&project.id, recorder::Type::Input, &data.to_vec())
|
||||
{
|
||||
tracing::error!("{}: error recording data: {:#}", &project.id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
(1, data) => {
|
||||
let pty_size: PtySize = serde_json::from_slice(&data)?;
|
||||
pty_pair.master.resize(pty_size)?;
|
||||
}
|
||||
(code, _) => tracing::error!("Unknown command {}", code),
|
||||
}
|
||||
}
|
||||
Ok(tokio_tungstenite::tungstenite::Message::Close(_)) => {
|
||||
tracing::debug!("Closing the websocket connection...");
|
||||
|
||||
tracing::debug!("Killing PTY child process...");
|
||||
pty_child_process
|
||||
.kill()
|
||||
.with_context(|| "failed to kill pty child process".to_string())?;
|
||||
break;
|
||||
}
|
||||
Ok(_) => tracing::error!("Unknown received data type"),
|
||||
Err(e) => {
|
||||
tracing::error!("Error receiving data: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
mod connection;
|
||||
mod recorder;
|
||||
mod server;
|
||||
mod writer;
|
||||
|
||||
pub use recorder::{Record, Type};
|
||||
pub use server::start_server;
|
||||
pub use writer::PtyWriter as Writer;
|
@ -1,17 +0,0 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Type {
|
||||
Input,
|
||||
Output,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Record {
|
||||
pub timestamp: u128,
|
||||
#[serde(rename = "type")]
|
||||
pub typ: Type,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use tokio::{net, task};
|
||||
|
||||
use crate::app;
|
||||
|
||||
use super::connection;
|
||||
|
||||
pub async fn start_server(port: usize, app: app::App) -> Result<()> {
|
||||
let pty_ws_address = format!("127.0.0.1:{}", port);
|
||||
let listener = net::TcpListener::bind(&pty_ws_address)
|
||||
.await
|
||||
.with_context(|| format!("failed to bind to {}", pty_ws_address))?;
|
||||
|
||||
tracing::info!("pty-ws: listening on {}", pty_ws_address);
|
||||
|
||||
while let Ok((stream, _)) = listener.accept().await {
|
||||
let app_clone = app.clone();
|
||||
task::Builder::new().name("pty-connection").spawn(async {
|
||||
if let Err(e) = connection::accept_connection(app_clone, stream).await {
|
||||
tracing::error!("pty-ws: failed to accept connection {:#}", e);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
tracing::info!("pty-ws: server stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::gb_repository;
|
||||
|
||||
use super::Record;
|
||||
|
||||
pub struct PtyWriter<'writer> {
|
||||
repository: &'writer gb_repository::Repository,
|
||||
}
|
||||
|
||||
impl<'writer> PtyWriter<'writer> {
|
||||
pub fn new(repository: &'writer gb_repository::Repository) -> Result<Self> {
|
||||
repository
|
||||
.get_or_create_current_session()
|
||||
.context("failed to create session")?;
|
||||
Ok(Self { repository })
|
||||
}
|
||||
|
||||
pub fn write(&self, record: &Record) -> Result<()> {
|
||||
let _lock = self.repository.lock();
|
||||
|
||||
serde_jsonlines::append_json_lines(
|
||||
self.repository.session_path().join("pty.jsonl"),
|
||||
[record],
|
||||
)?;
|
||||
|
||||
tracing::debug!(
|
||||
"{}: appended pty record to session",
|
||||
self.repository.project_id
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -84,18 +84,10 @@
|
||||
"svelte-outclick": "^3.6.2",
|
||||
"svelte-resize-observer": "^2.0.0",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tauri-plugin-log-api": "git+https://git@github.com/tauri-apps/tauri-plugin-log.git",
|
||||
"tauri-plugin-websocket-api": "git+https://git@github.com/tauri-apps/tauri-plugin-websocket.git",
|
||||
"tinykeys": "^2.1.0",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.4.9",
|
||||
"vitest": "^0.34.3",
|
||||
"xterm": "^5.2.1",
|
||||
"xterm-addon-canvas": "^0.4.0",
|
||||
"xterm-addon-fit": "^0.7.0",
|
||||
"xterm-addon-ligatures": "^0.6.0",
|
||||
"xterm-addon-unicode11": "^0.5.0",
|
||||
"xterm-addon-webgl": "^0.15.0"
|
||||
"vitest": "^0.34.3"
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
IconGitCommit,
|
||||
IconFile,
|
||||
IconFeedback,
|
||||
IconTerminal,
|
||||
IconSettings,
|
||||
IconDiscord,
|
||||
IconSearch,
|
||||
@ -147,14 +146,6 @@ const navigateGroup = ({ project, input }: { project?: Project; input: string })
|
||||
},
|
||||
icon: IconGitCommit
|
||||
},
|
||||
{
|
||||
title: 'Terminal',
|
||||
hotkey: 'Meta+T',
|
||||
action: {
|
||||
href: `/projects/${project.id}/terminal/`
|
||||
},
|
||||
icon: IconTerminal
|
||||
},
|
||||
{
|
||||
title: 'Project settings',
|
||||
hotkey: 'Meta+Shift+,',
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { Button, Tooltip } from '$lib/components';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { IconRewind, IconSearch, IconSettings, IconTerminal } from '$lib/icons';
|
||||
import { IconRewind, IconSearch, IconSettings } from '$lib/icons';
|
||||
|
||||
export let data: LayoutData;
|
||||
const { project } = data;
|
||||
@ -69,15 +69,6 @@
|
||||
/>
|
||||
</Tooltip>
|
||||
</li>
|
||||
<li>
|
||||
<Tooltip label="Terminal">
|
||||
<Button
|
||||
on:click={() => goto(`/projects/${$project.id}/terminal`)}
|
||||
kind="plain"
|
||||
icon={IconTerminal}
|
||||
/>
|
||||
</Tooltip>
|
||||
</li>
|
||||
<li>
|
||||
<Tooltip label="Project settings">
|
||||
<Button
|
||||
|
@ -1,54 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { LayoutData } from '../$types';
|
||||
import ResizeObserver from 'svelte-resize-observer';
|
||||
import setupTerminal from './terminal';
|
||||
import 'xterm/css/xterm.css';
|
||||
import type { Project } from '$lib/api/ipc/projects';
|
||||
import { debounce } from '$lib/utils';
|
||||
import { Button, Statuses } from '$lib/components';
|
||||
|
||||
export let data: LayoutData;
|
||||
const { project, statuses } = data;
|
||||
|
||||
type Unpromisify<T> = T extends Promise<infer U> ? U : T;
|
||||
let term: Unpromisify<ReturnType<typeof setupTerminal>> | undefined;
|
||||
|
||||
const handleTerminalResize = debounce(() => term?.resize(), 5);
|
||||
const runCommand = (command: string) => term?.run(command);
|
||||
|
||||
$: terminal = (target: HTMLElement, params: { project: Project }) => {
|
||||
let setupPromise = setupTerminal(params);
|
||||
setupPromise.then((terminal) => (term = terminal));
|
||||
setupPromise.then((terminal) => terminal.bind(target));
|
||||
return {
|
||||
update: (params: { project: Project }) => {
|
||||
setupPromise.then((term) => term.destroy());
|
||||
setupPromise = setupTerminal(params);
|
||||
setupPromise.then((terminal) => (term = terminal));
|
||||
setupPromise.then((terminal) => terminal.bind(target));
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="terminal-page flex h-full w-full flex-auto flex-row">
|
||||
<div class="side-panel p-4">
|
||||
<h2 class="pb-4 text-lg font-bold text-zinc-300">Git Status</h2>
|
||||
{#await statuses.load() then}
|
||||
<Statuses statuses={$statuses} />
|
||||
{/await}
|
||||
<div class="mt-4 font-bold">Commands</div>
|
||||
<ul class="py-2">
|
||||
<Button color="purple" width="full-width" on:click={() => runCommand('git push')}>
|
||||
Push Commit
|
||||
</Button>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content-container h-full">
|
||||
<div class="flex h-full w-full flex-row">
|
||||
<div class="h-full w-full" use:terminal={{ project: $project }} />
|
||||
<ResizeObserver on:resize={handleTerminalResize} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,129 +0,0 @@
|
||||
import type { Project } from '$lib/api/ipc/projects';
|
||||
import { Terminal } from 'xterm';
|
||||
import { CanvasAddon } from 'xterm-addon-canvas';
|
||||
import { WebglAddon } from 'xterm-addon-webgl';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { Unicode11Addon } from 'xterm-addon-unicode11';
|
||||
import WebSocket, { type Message } from 'tauri-plugin-websocket-api';
|
||||
import { dev } from '$app/environment';
|
||||
|
||||
const isWebgl2Supported = (() => {
|
||||
let isSupported = window.WebGL2RenderingContext ? undefined : false;
|
||||
return () => {
|
||||
if (isSupported === undefined) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const gl = canvas.getContext('webgl2', { depth: false, antialias: false });
|
||||
isSupported = gl instanceof window.WebGL2RenderingContext;
|
||||
}
|
||||
return isSupported;
|
||||
};
|
||||
})();
|
||||
|
||||
const uint8ArrayToNumbers = (array: Uint8Array) => {
|
||||
const numbers = [];
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
numbers.push(array[i]);
|
||||
}
|
||||
return numbers;
|
||||
};
|
||||
|
||||
const encodeString = (msg: string): Message => ({
|
||||
type: 'Binary',
|
||||
data: uint8ArrayToNumbers(new TextEncoder().encode(msg))
|
||||
});
|
||||
|
||||
const userInputMessage = (data: string) => encodeString(`\x00${data}`);
|
||||
|
||||
const resizeMessage = (size: {
|
||||
rows: number;
|
||||
cols: number;
|
||||
pixel_width: number;
|
||||
pixel_height: number;
|
||||
}) => encodeString(`\x01${JSON.stringify(size)}`);
|
||||
|
||||
const newSession = async (params: { project: Project }) => {
|
||||
const { project } = params;
|
||||
const port = dev ? 7702 : 7703;
|
||||
return WebSocket.connect(`ws://localhost:${port}/${project.id}`).then((conn) => {
|
||||
const sendMessage = (message: Message) => {
|
||||
conn.send(message).catch((e: any) => {
|
||||
console.error(`failed to send message to terminal: ${e}`);
|
||||
});
|
||||
};
|
||||
|
||||
const term = new Terminal({
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'block',
|
||||
fontSize: 13,
|
||||
rows: 24,
|
||||
cols: 80,
|
||||
allowProposedApi: true
|
||||
});
|
||||
const fitAddon = new FitAddon();
|
||||
|
||||
term.loadAddon(new Unicode11Addon());
|
||||
term.loadAddon(fitAddon);
|
||||
if (isWebgl2Supported()) {
|
||||
term.loadAddon(new WebglAddon());
|
||||
} else {
|
||||
term.loadAddon(new CanvasAddon());
|
||||
}
|
||||
|
||||
term.unicode.activeVersion = '11';
|
||||
|
||||
const inputListener = term.onData((data: string) => sendMessage(userInputMessage(data)));
|
||||
|
||||
conn.addListener((message) => {
|
||||
if (message.type === 'Binary') {
|
||||
term.write(new Uint8Array(message.data));
|
||||
}
|
||||
});
|
||||
|
||||
conn.addListener((message) => {
|
||||
if (message.type === 'Close') {
|
||||
term.dispose();
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
resize: () => {
|
||||
fitAddon.fit();
|
||||
const size = fitAddon.proposeDimensions();
|
||||
if (!size) return;
|
||||
sendMessage(
|
||||
resizeMessage({
|
||||
cols: size.cols,
|
||||
rows: size.rows,
|
||||
pixel_width: 0,
|
||||
pixel_height: 0
|
||||
})
|
||||
);
|
||||
},
|
||||
destroy: () => {
|
||||
term.dispose();
|
||||
inputListener.dispose();
|
||||
conn.disconnect();
|
||||
},
|
||||
run: (command: string) => {
|
||||
sendMessage(userInputMessage(`${command}\n`));
|
||||
},
|
||||
bind: (target: HTMLElement) => {
|
||||
term.open(target);
|
||||
fitAddon.fit();
|
||||
term.focus();
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const sessions = new Map<string, ReturnType<typeof newSession>>();
|
||||
|
||||
export default (params: { project: Project }) => {
|
||||
const { project } = params;
|
||||
const session = sessions.get(project.id);
|
||||
if (session) return session;
|
||||
const s = newSession(params);
|
||||
sessions.set(project.id, s);
|
||||
return s;
|
||||
};
|
Loading…
Reference in New Issue
Block a user