start terminal

This commit is contained in:
Dongdong Zhou 2021-10-13 17:59:08 +01:00
parent 2cd226ae1f
commit 2d1f1e13e4
10 changed files with 316 additions and 86 deletions

1
Cargo.lock generated
View File

@ -1990,6 +1990,7 @@ dependencies = [
name = "lapce-proxy"
version = "0.0.1"
dependencies = [
"alacritty_terminal",
"anyhow",
"crossbeam-channel 0.5.1",
"git2",

View File

@ -3,6 +3,7 @@
use anyhow::Result;
use druid::{Point, Rect, Selector, Size, WidgetId};
use indexmap::IndexMap;
use lapce_proxy::terminal::TermId;
use lsp_types::{
CodeActionResponse, CompletionItem, CompletionResponse, Location, Position,
PublishDiagnosticsParams, Range, TextEdit,
@ -20,6 +21,7 @@
data::EditorKind,
editor::{EditorLocation, EditorLocationNew, HighlightTextLayout},
palette::{NewPaletteItem, PaletteType},
proxy::TerminalContent,
split::SplitMoveDirection,
state::LapceWorkspace,
};
@ -374,6 +376,7 @@ pub enum LapceUICommand {
UpdateLineChanges(BufferId),
PublishDiagnostics(PublishDiagnosticsParams),
UpdateDiffFiles(Vec<PathBuf>),
TerminalUpdateContent(TermId, TerminalContent),
ReloadBuffer(BufferId, u64, String),
EnsureVisible((Rect, (f64, f64), Option<EnsureVisiblePosition>)),
EnsureRectVisible(Rect),

View File

@ -70,6 +70,7 @@
source_control::{SourceControlData, SOURCE_CONTROL_BUFFER},
split::SplitMoveDirection,
state::{LapceWorkspace, LapceWorkspaceType, Mode, VisualMode},
terminal::TerminalSplitData,
theme::OldLapceTheme,
};
@ -214,6 +215,7 @@ pub struct LapceTabData {
pub workspace: Option<Arc<LapceWorkspace>>,
pub main_split: LapceMainSplitData,
pub completion: Arc<CompletionData>,
pub terminal: Arc<TerminalSplitData>,
pub palette: Arc<PaletteData>,
pub source_control: Arc<SourceControlData>,
pub proxy: Arc<LapceProxy>,
@ -270,6 +272,8 @@ pub fn new(
&config,
);
let terminal = Arc::new(TerminalSplitData::new());
let mut panels = im::HashMap::new();
panels.insert(
PanelPosition::LeftTop,
@ -279,11 +283,20 @@ pub fn new(
shown: true,
}),
);
panels.insert(
PanelPosition::BottomLeft,
Arc::new(PanelData {
active: terminal.widget_id,
widgets: vec![terminal.widget_id],
shown: true,
}),
);
let mut tab = Self {
id: tab_id,
workspace: None,
main_split,
completion,
terminal,
source_control,
palette,
proxy,

View File

@ -5,11 +5,13 @@
use std::thread;
use std::{path::PathBuf, process::Child, sync::Arc};
use alacritty_terminal::term::cell::Cell;
use anyhow::{anyhow, Result};
use crossbeam_utils::sync::WaitGroup;
use druid::{ExtEventSink, WidgetId};
use druid::{Target, WindowId};
use lapce_proxy::dispatch::{FileNodeItem, NewBufferResponse};
use lapce_proxy::terminal::TermId;
use lsp_types::CompletionItem;
use lsp_types::Position;
use lsp_types::PublishDiagnosticsParams;
@ -28,6 +30,8 @@
use crate::state::LapceWorkspaceType;
use crate::{buffer::BufferId, command::LAPCE_UI_COMMAND};
pub type TerminalContent = Vec<(alacritty_terminal::index::Point, Cell)>;
#[derive(Clone)]
pub struct LapceProxy {
peer: Arc<Mutex<Option<RpcPeer>>>,
@ -139,6 +143,15 @@ pub fn new_buffer(&self, buffer_id: BufferId, path: PathBuf) -> Result<String> {
return Ok(resp.content);
}
pub fn new_terminal(&self, term_id: TermId) {
self.peer.lock().as_ref().unwrap().send_rpc_notification(
"new_terminal",
&json!({
"term_id": term_id,
}),
)
}
pub fn update(&self, buffer_id: BufferId, delta: &RopeDelta, rev: u64) {
self.peer.lock().as_ref().unwrap().send_rpc_notification(
"update",
@ -343,6 +356,10 @@ pub enum Notification {
ListDir {
items: Vec<FileNodeItem>,
},
TerminalUpdateContent {
id: TermId,
content: TerminalContent,
},
DiffFiles {
files: Vec<PathBuf>,
},
@ -385,6 +402,7 @@ fn handle_notification(
Notification::ListDir { mut items } => {}
Notification::DiffFiles { files } => {}
Notification::PublishDiagnostics { diagnostics } => {}
Notification::TerminalUpdateContent { id, content } => {}
}
}
@ -519,6 +537,13 @@ fn handle_notification(
Target::Widget(self.tab_id),
);
}
Notification::TerminalUpdateContent { id, content } => {
self.event_sink.submit_command(
LAPCE_UI_COMMAND,
LapceUICommand::TerminalUpdateContent(id, content),
Target::Widget(self.tab_id),
);
}
}
}

View File

@ -33,6 +33,7 @@
split::LapceSplitNew,
state::{LapceWorkspace, LapceWorkspaceType},
status::LapceStatusNew,
terminal::TerminalPanel,
};
pub struct LapceTabNew {
@ -73,6 +74,8 @@ pub fn new(data: &LapceTabData) -> Self {
data.source_control.widget_id,
WidgetPod::new(source_control.boxed()),
);
let terminal = TerminalPanel::new(&data);
panels.insert(data.terminal.widget_id, WidgetPod::new(terminal.boxed()));
Self {
id: data.id,
@ -186,6 +189,7 @@ fn event(
Arc::make_mut(buffer).load_content(content);
ctx.set_handled();
}
LapceUICommand::TerminalUpdateContent(id, content) => {}
LapceUICommand::UpdateDiffFiles(files) => {
let source_control = Arc::make_mut(&mut data.source_control);
source_control.diff_files = files

View File

@ -1,95 +1,107 @@
use std::{borrow::Cow, collections::HashMap, sync::Arc};
use std::sync::Arc;
use alacritty_terminal::{
config::Config,
event::{Event, EventListener, Notify},
event_loop::{EventLoop, Notifier},
grid::GridCell,
sync::FairMutex,
term::SizeInfo,
tty, Term,
use druid::{
BoxConstraints, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
PaintCtx, Point, Size, UpdateCtx, Widget, WidgetId, WidgetPod,
};
use anyhow::Result;
use crossbeam_channel::{Receiver, Sender};
use lsp_types::notification;
use lapce_proxy::terminal::TermId;
#[derive(Clone)]
pub struct Terminal {
pub term: Arc<FairMutex<Term<EventProxy>>>,
sender: Sender<Event>,
use crate::{data::LapceTabData, proxy::LapceProxy, split::LapceSplitNew};
pub struct TerminalSplitData {
pub widget_id: WidgetId,
pub split_id: WidgetId,
pub terminals: im::HashMap<TermId, LapceTerminalData>,
}
pub type TermConfig = Config<HashMap<String, String>>;
#[derive(Clone)]
pub struct EventProxy {
sender: Sender<Event>,
}
impl EventProxy {}
impl EventListener for EventProxy {
fn send_event(&self, event: alacritty_terminal::event::Event) {
self.sender.send(event);
}
}
impl Terminal {
impl TerminalSplitData {
pub fn new() -> Self {
let config = TermConfig::default();
let (sender, receiver) = crossbeam_channel::unbounded();
let event_proxy = EventProxy {
sender: sender.clone(),
};
let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, true);
let pty = tty::new(&config, &size, None);
let terminal = Term::new(&config, size, event_proxy.clone());
let terminal = Arc::new(FairMutex::new(terminal));
let event_loop =
EventLoop::new(terminal.clone(), event_proxy, pty, false, false);
let loop_tx = event_loop.channel();
let notifier = Notifier(loop_tx.clone());
event_loop.spawn();
let split_id = WidgetId::next();
let mut terminals = im::HashMap::new();
let terminal = Terminal {
term: terminal,
sender,
};
terminal.run(receiver, notifier);
terminal
}
fn run(&self, receiver: Receiver<Event>, notifier: Notifier) {
let term = self.term.clone();
std::thread::spawn(move || -> Result<()> {
loop {
let event = receiver.recv()?;
match event {
Event::MouseCursorDirty => {}
Event::Title(_) => {}
Event::ResetTitle => {}
Event::ClipboardStore(_, _) => {}
Event::ClipboardLoad(_, _) => {}
Event::ColorRequest(_, _) => {}
Event::PtyWrite(s) => {
notifier.notify(s.into_bytes());
}
Event::CursorBlinkingChange(_) => {}
Event::Wakeup => {
for cell in term.lock().renderable_content().display_iter {
if !cell.is_empty() {
println!("{:?}", cell);
}
}
}
Event::Bell => {}
Event::Exit => {}
}
}
});
}
pub fn insert<B: Into<String>>(&self, data: B) {
self.sender.send(Event::PtyWrite(data.into()));
Self {
widget_id: WidgetId::next(),
split_id,
terminals,
}
}
}
pub struct LapceTerminalData {
id: TermId,
}
impl LapceTerminalData {
pub fn new(proxy: Arc<LapceProxy>) -> Self {
let id = TermId::next();
proxy.new_terminal(id);
Self { id }
}
}
pub struct TerminalPanel {
widget_id: WidgetId,
split: WidgetPod<LapceTabData, LapceSplitNew>,
}
impl TerminalPanel {
pub fn new(data: &LapceTabData) -> Self {
let split = LapceSplitNew::new(data.terminal.split_id);
Self {
widget_id: data.terminal.widget_id,
split: WidgetPod::new(split),
}
}
}
impl Widget<LapceTabData> for TerminalPanel {
fn id(&self) -> Option<WidgetId> {
Some(self.widget_id)
}
fn event(
&mut self,
ctx: &mut EventCtx,
event: &Event,
data: &mut LapceTabData,
env: &Env,
) {
self.split.event(ctx, event, data, env);
}
fn lifecycle(
&mut self,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &LapceTabData,
env: &Env,
) {
self.split.lifecycle(ctx, event, data, env);
}
fn update(
&mut self,
ctx: &mut UpdateCtx,
old_data: &LapceTabData,
data: &LapceTabData,
env: &Env,
) {
self.split.update(ctx, data, env);
}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &LapceTabData,
env: &Env,
) -> Size {
self.split.layout(ctx, bc, data, env);
self.split.set_origin(ctx, data, env, Point::ZERO);
bc.max()
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {
self.split.paint(ctx, data, env);
}
}

View File

@ -5,6 +5,7 @@ authors = ["Dongdong Zhou <dzhou121@gmail.com>"]
edition = "2018"
[dependencies]
alacritty_terminal = "0.15.0"
notify = "4.0.16"
lapce-rpc = { path = "../rpc" }
xi-core-lib = "0.3.0"

View File

@ -2,6 +2,7 @@
use crate::core_proxy::CoreProxy;
use crate::lsp::LspCatalog;
use crate::plugin::PluginCatalog;
use crate::terminal::{TermId, Terminal, TerminalEvent};
use anyhow::{anyhow, Result};
use crossbeam_channel::{unbounded, Receiver, Sender};
use git2::{DiffOptions, Oid, Repository};
@ -31,6 +32,7 @@ pub struct Dispatcher {
pub git_sender: Sender<(BufferId, u64)>,
pub workspace: Arc<Mutex<PathBuf>>,
pub buffers: Arc<Mutex<HashMap<BufferId, Buffer>>>,
pub terminals: Arc<Mutex<HashMap<TermId, Terminal>>>,
open_files: Arc<Mutex<HashMap<String, BufferId>>>,
plugins: Arc<Mutex<PluginCatalog>>,
pub lsp: Arc<Mutex<LspCatalog>>,
@ -112,6 +114,9 @@ pub enum Notification {
delta: RopeDelta,
rev: u64,
},
NewTerminal {
term_id: TermId,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -212,6 +217,7 @@ pub fn new(sender: Sender<Value>) -> Dispatcher {
git_sender,
workspace: Arc::new(Mutex::new(PathBuf::new())),
buffers: Arc::new(Mutex::new(HashMap::new())),
terminals: Arc::new(Mutex::new(HashMap::new())),
open_files: Arc::new(Mutex::new(HashMap::new())),
plugins: Arc::new(Mutex::new(plugins)),
lsp: Arc::new(Mutex::new(LspCatalog::new())),
@ -400,6 +406,27 @@ fn handle_notification(&self, rpc: Notification) {
self.lsp.lock().update(buffer, &content_change, buffer.rev);
}
}
Notification::NewTerminal { term_id } => {
let (terminal, receiver) = Terminal::new();
self.terminals.lock().insert(term_id, terminal);
let local_proxy = self.clone();
thread::spawn(move || -> Result<()> {
loop {
let event = receiver.recv()?;
match event {
TerminalEvent::UpdateContent(content) => local_proxy
.send_notification(
"terminal_update_content",
json!({
"id": term_id,
"content": content,
}),
),
}
}
});
}
}
}

View File

@ -3,6 +3,7 @@
pub mod dispatch;
pub mod lsp;
pub mod plugin;
pub mod terminal;
use dispatch::Dispatcher;

143
proxy/src/terminal.rs Normal file
View File

@ -0,0 +1,143 @@
use std::{
borrow::Cow,
collections::HashMap,
sync::{
atomic::{self, AtomicU64},
Arc,
},
};
use alacritty_terminal::{
config::Config,
event::{Event, EventListener, Notify},
event_loop::{EventLoop, Notifier},
grid::GridCell,
index::Point,
sync::FairMutex,
term::{cell::Cell, SizeInfo},
tty, Term,
};
use anyhow::Result;
use crossbeam_channel::{Receiver, Sender};
use lsp_types::notification;
use serde::{Deserialize, Deserializer, Serialize};
pub struct Counter(AtomicU64);
impl Counter {
pub const fn new() -> Counter {
Counter(AtomicU64::new(1))
}
pub fn next(&self) -> u64 {
self.0.fetch_add(1, atomic::Ordering::Relaxed)
}
}
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug, Serialize, Deserialize)]
pub struct TermId(pub u64);
impl TermId {
pub fn next() -> Self {
static TERM_ID_COUNTER: Counter = Counter::new();
Self(TERM_ID_COUNTER.next())
}
}
pub enum TerminalEvent {
UpdateContent(Vec<(Point, Cell)>),
}
#[derive(Clone)]
pub struct Terminal {
pub term: Arc<FairMutex<Term<EventProxy>>>,
sender: Sender<Event>,
host_sender: Sender<TerminalEvent>,
}
pub type TermConfig = Config<HashMap<String, String>>;
#[derive(Clone)]
pub struct EventProxy {
sender: Sender<Event>,
}
impl EventProxy {}
impl EventListener for EventProxy {
fn send_event(&self, event: alacritty_terminal::event::Event) {
self.sender.send(event);
}
}
impl Terminal {
pub fn new() -> (Self, Receiver<TerminalEvent>) {
let config = TermConfig::default();
let (sender, receiver) = crossbeam_channel::unbounded();
let event_proxy = EventProxy {
sender: sender.clone(),
};
let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, true);
let pty = tty::new(&config, &size, None);
let terminal = Term::new(&config, size, event_proxy.clone());
let terminal = Arc::new(FairMutex::new(terminal));
let event_loop =
EventLoop::new(terminal.clone(), event_proxy, pty, false, false);
let loop_tx = event_loop.channel();
let notifier = Notifier(loop_tx.clone());
event_loop.spawn();
let (host_sender, host_receiver) = crossbeam_channel::unbounded();
let terminal = Terminal {
term: terminal,
sender,
host_sender,
};
terminal.run(receiver, notifier);
(terminal, host_receiver)
}
fn run(&self, receiver: Receiver<Event>, notifier: Notifier) {
let term = self.term.clone();
let host_sender = self.host_sender.clone();
std::thread::spawn(move || -> Result<()> {
loop {
let event = receiver.recv()?;
match event {
Event::MouseCursorDirty => {}
Event::Title(_) => {}
Event::ResetTitle => {}
Event::ClipboardStore(_, _) => {}
Event::ClipboardLoad(_, _) => {}
Event::ColorRequest(_, _) => {}
Event::PtyWrite(s) => {
notifier.notify(s.into_bytes());
}
Event::CursorBlinkingChange(_) => {}
Event::Wakeup => {
let content = term
.lock()
.renderable_content()
.display_iter
.filter_map(|c| {
if c.is_empty() {
None
} else {
Some((c.point, c.cell.clone()))
}
})
.collect::<Vec<(Point, Cell)>>();
let event = TerminalEvent::UpdateContent(content);
host_sender.send(event);
}
Event::Bell => {}
Event::Exit => {}
}
}
});
}
pub fn insert<B: Into<String>>(&self, data: B) {
self.sender.send(Event::PtyWrite(data.into()));
}
}