on master help sign in
wip: Compositor
commit83f2c24115cc5a3dce90a77440f1ef06f6cf9c78 authorBlaž Hrastnik <blaz@mxxn.io> dateSun 6 Dec 2020, 11:53 (6 years ago) parentb7a3e52
3 files changed+261−107
helix-term/src/application.rs+149−107 helix-term/src/compositor.rs+111 helix-term/src/main.rs+1
helix-term/src/application.rs+149−107
@@ -8,6 +8,8 @@ use helix_view::{
88 Document, Editor, Theme, View,
99};
1010
11+use crate::compositor::{Component, Compositor};
12+
1113use log::{debug, info};
1214
1315use std::{
@@ -35,23 +37,21 @@ use tui::{
3537 style::{Color, Modifier, Style},
3638};
3739
38−const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
39−
4040type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
4141
4242const BASE_WIDTH: u16 = 30;
4343
4444pub struct Application<'a> {
45− editor: Editor,
4645 prompt: Option<Prompt>,
47− terminal: Renderer,
4846
49− keymap: Keymaps,
47+ compositor: Compositor,
48+ renderer: Renderer,
49+
5050 executor: &'a smol::Executor<'a>,
5151 language_server: helix_lsp::Client,
5252}
5353
54−struct Renderer {
54+pub(crate) struct Renderer {
5555 size: (u16, u16),
5656 terminal: Terminal,
5757 surface: Surface,
@@ -92,7 +92,6 @@ impl Renderer {
9292 // TODO: ideally not &mut View but highlights require it because of cursor cache
9393 pub fn render_buffer(&mut self, view: &mut View, viewport: Rect, theme: &Theme) {
9494 let area = Rect::new(0, 0, self.size.0, self.size.1);
95− self.surface.reset(); // reset is faster than allocating new empty surface
9695
9796 // clear with background color
9897 self.surface.set_style(area, theme.get("ui.background"));
@@ -221,8 +220,12 @@ impl Renderer {
221220
222221 // TODO: paint cursor heads except primary
223222
224− self.surface
225− .set_string(OFFSET + visual_x, line, grapheme, style);
223+ self.surface.set_string(
224+ viewport.x + visual_x,
225+ viewport.y + line,
226+ grapheme,
227+ style,
228+ );
226229
227230 visual_x += width;
228231 }
@@ -321,7 +324,7 @@ impl Renderer {
321324 .set_string(2, self.size.1 - 1, &prompt.line, self.text_color);
322325 }
323326
324− pub fn draw(&mut self) {
327+ pub fn draw_and_swap(&mut self) {
325328 use tui::backend::Backend;
326329 // TODO: theres probably a better place for this
327330 self.terminal
@@ -363,112 +366,40 @@ impl Renderer {
363366 }
364367}
365368
366−impl<'a> Application<'a> {
367− pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
368− let terminal = Renderer::new()?;
369− let mut editor = Editor::new();
370−
371− if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
372− editor.open(file, terminal.size)?;
373− }
374−
375− let language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
369+struct EditorView {
370+ editor: Editor,
371+ prompt: Option<Prompt>, // TODO: this is None for now, make a layer
372+ keymap: Keymaps,
373+}
376374
377− let mut app = Self {
375+impl EditorView {
376+ fn new(editor: Editor) -> Self {
377+ Self {
378378 editor,
379− terminal,
380− // TODO; move to state
381379 prompt: None,
382−
383− //
384380 keymap: keymap::default(),
385− executor,
386− language_server,
387− };
388−
389− Ok(app)
390− }
391−
392− fn render(&mut self) {
393− let viewport = Rect::new(OFFSET, 0, self.terminal.size.0, self.terminal.size.1 - 2); // - 2 for statusline and prompt
394−
395− // SAFETY: we cheat around the view_mut() borrow because it doesn't allow us to also borrow
396− // theme. Theme is immutable mutating view won't disrupt theme_ref.
397− let theme_ref = unsafe { &*(&self.editor.theme as *const Theme) };
398− if let Some(view) = self.editor.view_mut() {
399− self.terminal.render_view(view, viewport, theme_ref);
400− if let Some(prompt) = &self.prompt {
401− if prompt.should_close {
402− self.prompt = None;
403− } else {
404− self.terminal.render_prompt(view, prompt, theme_ref);
405− }
406− }
407− }
408−
409− self.terminal.draw();
410−
411− // TODO: drop unwrap
412− self.terminal
413− .render_cursor(self.editor.view().unwrap(), self.prompt.as_ref(), viewport);
414− }
415−
416− pub async fn event_loop(&mut self) {
417− let mut reader = EventStream::new();
418−
419− // initialize lsp
420− self.language_server.initialize().await.unwrap();
421− self.language_server
422− .text_document_did_open(&self.editor.view().unwrap().doc)
423− .await
424− .unwrap();
425−
426− self.render();
427−
428− loop {
429− if self.editor.should_close {
430− break;
431− }
432−
433− use futures_util::{select, FutureExt};
434− select! {
435− event = reader.next().fuse() => {
436− self.handle_terminal_events(event).await
437− }
438− call = self.language_server.incoming.next().fuse() => {
439− self.handle_language_server_message(call).await
440− }
441− }
442381 }
443382 }
383+}
444384
445− pub async fn handle_terminal_events(
446− &mut self,
447− event: Option<Result<Event, crossterm::ErrorKind>>,
448− ) {
449− // Handle key events
385+impl Component for EditorView {
386+ fn handle_event(&mut self, event: Event, executor: &smol::Executor) -> bool {
450387 match event {
451− Some(Ok(Event::Resize(width, height))) => {
452− self.terminal.resize(width, height);
453−
388+ Event::Resize(width, height) => {
454389 // TODO: simplistic ensure cursor in view for now
455390 // TODO: loop over views
456391 if let Some(view) = self.editor.view_mut() {
457− view.size = self.terminal.size;
392+ view.size = (width, height);
458393 view.ensure_cursor_in_view()
459394 };
460−
461− self.render();
462395 }
463− Some(Ok(Event::Key(event))) => {
396+ Event::Key(event) => {
464397 // if there's a prompt, it takes priority
465398 if let Some(prompt) = &mut self.prompt {
466399 self.prompt
467400 .as_mut()
468401 .unwrap()
469402 .handle_input(event, &mut self.editor);
470−
471− self.render();
472403 } else if let Some(view) = self.editor.view_mut() {
473404 let keys = vec![event];
474405 // TODO: sequences (`gg`)
@@ -478,7 +409,7 @@ impl<'a> Application<'a> {
478409 if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
479410 let mut cx = helix_view::commands::Context {
480411 view,
481− executor: self.executor,
412+ executor: executor,
482413 count: 1,
483414 };
484415
@@ -490,7 +421,7 @@ impl<'a> Application<'a> {
490421 {
491422 let mut cx = helix_view::commands::Context {
492423 view,
493− executor: self.executor,
424+ executor: executor,
494425 count: 1,
495426 };
496427 commands::insert::insert_char(&mut cx, c);
@@ -557,7 +488,7 @@ impl<'a> Application<'a> {
557488 } else if let Some(command) = self.keymap[&Mode::Normal].get(&keys) {
558489 let mut cx = helix_view::commands::Context {
559490 view,
560− executor: self.executor,
491+ executor: executor,
561492 count: 1,
562493 };
563494 command(&mut cx);
@@ -570,7 +501,7 @@ impl<'a> Application<'a> {
570501 if let Some(command) = self.keymap[&mode].get(&keys) {
571502 let mut cx = helix_view::commands::Context {
572503 view,
573− executor: self.executor,
504+ executor: executor,
574505 count: 1,
575506 };
576507 command(&mut cx);
@@ -580,10 +511,119 @@ impl<'a> Application<'a> {
580511 }
581512 }
582513 }
583− self.render();
584514 }
585515 }
586− Some(Ok(Event::Mouse(_))) => (), // unhandled
516+ Event::Mouse(_) => (),
517+ }
518+
519+ true
520+ }
521+ fn render(&mut self, renderer: &mut Renderer) {
522+ const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
523+ let viewport = Rect::new(OFFSET, 0, renderer.size.0, renderer.size.1 - 2); // - 2 for statusline and prompt
524+
525+ // SAFETY: we cheat around the view_mut() borrow because it doesn't allow us to also borrow
526+ // theme. Theme is immutable mutating view won't disrupt theme_ref.
527+ let theme_ref = unsafe { &*(&self.editor.theme as *const Theme) };
528+ if let Some(view) = self.editor.view_mut() {
529+ renderer.render_view(view, viewport, theme_ref);
530+ if let Some(prompt) = &self.prompt {
531+ if prompt.should_close {
532+ self.prompt = None;
533+ } else {
534+ renderer.render_prompt(view, prompt, theme_ref);
535+ }
536+ }
537+ }
538+
539+ // TODO: drop unwrap
540+ renderer.render_cursor(self.editor.view().unwrap(), self.prompt.as_ref(), viewport);
541+ }
542+}
543+
544+impl<'a> Application<'a> {
545+ pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
546+ let renderer = Renderer::new()?;
547+ let mut editor = Editor::new();
548+
549+ if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
550+ editor.open(file, renderer.size)?;
551+ }
552+
553+ let mut compositor = Compositor::new();
554+ compositor.push(Box::new(EditorView::new(editor)));
555+
556+ let language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
557+
558+ let mut app = Self {
559+ renderer,
560+ // TODO; move to state
561+ compositor,
562+ prompt: None,
563+
564+ executor,
565+ language_server,
566+ };
567+
568+ Ok(app)
569+ }
570+
571+ fn render(&mut self) {
572+ // v2:
573+ self.renderer.surface.reset(); // reset is faster than allocating new empty surface
574+ self.compositor.render(&mut self.renderer); // viewport,
575+ self.renderer.draw_and_swap();
576+ }
577+
578+ pub async fn event_loop(&mut self) {
579+ let mut reader = EventStream::new();
580+
581+ // initialize lsp
582+ self.language_server.initialize().await.unwrap();
583+ // TODO: temp
584+ // self.language_server
585+ // .text_document_did_open(&self.editor.view().unwrap().doc)
586+ // .await
587+ // .unwrap();
588+
589+ self.render();
590+
591+ loop {
592+ // TODO:
593+ // if self.editor.should_close {
594+ // break;
595+ // }
596+
597+ use futures_util::{select, FutureExt};
598+ select! {
599+ event = reader.next().fuse() => {
600+ self.handle_terminal_events(event)
601+ }
602+ call = self.language_server.incoming.next().fuse() => {
603+ self.handle_language_server_message(call).await
604+ }
605+ }
606+ }
607+ }
608+
609+ pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) {
610+ // Handle key events
611+ match event {
612+ Some(Ok(Event::Resize(width, height))) => {
613+ self.renderer.resize(width, height);
614+
615+ // TODO: use the response
616+ self.compositor
617+ .handle_event(Event::Resize(width, height), self.executor);
618+
619+ self.render();
620+ }
621+ Some(Ok(event)) => {
622+ // TODO: use the response
623+ self.compositor.handle_event(event, self.executor);
624+
625+ self.render();
626+ }
587627 Some(Err(x)) => panic!(x),
588628 None => panic!(),
589629 };
@@ -599,11 +639,13 @@ impl<'a> Application<'a> {
599639 match notification {
600640 Notification::PublishDiagnostics(params) => {
601641 let path = Some(params.uri.to_file_path().unwrap());
602− let view = self
603− .editor
604− .views
605− .iter_mut()
606− .find(|view| view.doc.path == path);
642+ let view: Option<&mut helix_view::View> = None;
643+ // TODO:
644+ // let view = self
645+ // .editor
646+ // .views
647+ // .iter_mut()
648+ // .find(|view| view.doc.path == path);
607649
608650 if let Some(view) = view {
609651 let doc = view.doc.text().slice(..);
helix-term/src/compositor.rsnew file+111
@@ -0,0 +1,111 @@
1+// Features:
2+// Tracks currently focused component which receives all input
3+// Event loop is external as opposed to cursive-rs
4+// Calls render on the component and translates screen coords to local component coords
5+//
6+// TODO:
7+// Q: where is the Application state stored? do we store it into an external static var?
8+// A: probably makes sense to initialize the editor into a `static Lazy<>` global var.
9+//
10+// Q: how do we composit nested structures? There should be sub-components/views
11+//
12+// Each component declares it's own size constraints and gets fitted based on it's parent.
13+// Q: how does this work with popups?
14+// cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), <component>)
15+
16+use crate::application::Renderer;
17+use crossterm::event::Event;
18+use smol::Executor;
19+use tui::buffer::Buffer as Surface;
20+
21+pub(crate) trait Component {
22+ /// Process input events, return true if handled.
23+ fn handle_event(&mut self, event: Event, executor: &Executor) -> bool;
24+ // , args: ()
25+
26+ /// Should redraw? Useful for saving redraw cycles if we know component didn't change.
27+ fn should_update(&self) -> bool {
28+ true
29+ }
30+
31+ fn render(&mut self, renderer: &mut Renderer);
32+}
33+
34+// struct Editor { };
35+
36+// For v1:
37+// Child views are something each view needs to handle on it's own for now, positioning and sizing
38+// options, focus tracking. In practice this is simple: we only will need special solving for
39+// splits etc
40+
41+// impl Editor {
42+// fn render(&mut self, surface: &mut Surface, args: ()) {
43+// // compute x, y, w, h rects for sub-views!
44+// // get surface area
45+// // get constraints for textarea, statusbar
46+// // -> cassowary-rs
47+
48+// // first render textarea
49+// // then render statusbar
50+// }
51+// }
52+
53+// usecases to consider:
54+// - a single view with subviews (textarea + statusbar)
55+// - a popup panel / dialog with it's own interactions
56+// - an autocomplete popup that doesn't change focus
57+
58+//fn main() {
59+// let root = Editor::new();
60+// let compositor = Compositor::new();
61+
62+// compositor.push(root);
63+
64+// // pos: clip to bottom of screen
65+// compositor.push_at(pos, Prompt::new(
66+// ":",
67+// (),
68+// |input: &str| match input {}
69+// )); // TODO: this Prompt needs to somehow call compositor.pop() on close, but it can't refer to parent
70+// // Cursive solves this by allowing to return a special result on process_event
71+// // that's either Ignore | Consumed(Opt<C>) where C: fn (Compositor) -> ()
72+
73+// // TODO: solve popup focus: we want to push autocomplete popups on top of the current layer
74+// // but retain the focus where it was. The popup will also need to update as we type into the
75+// // textarea. It should also capture certain input, such as tab presses etc
76+// //
77+// // 1) This could be faked by the top layer pushing down edits into the previous layer.
78+// // 2) Alternatively,
79+//}
80+
81+pub(crate) struct Compositor {
82+ layers: Vec<Box<dyn Component>>,
83+}
84+
85+impl Compositor {
86+ pub fn new() -> Self {
87+ Self { layers: Vec::new() }
88+ }
89+
90+ pub fn push(&mut self, layer: Box<dyn Component>) {
91+ self.layers.push(layer);
92+ }
93+
94+ pub fn pop(&mut self) {
95+ self.layers.pop();
96+ }
97+
98+ pub fn handle_event(&mut self, event: Event, executor: &Executor) -> () {
99+ // TODO: custom focus
100+ if let Some(layer) = self.layers.last_mut() {
101+ layer.handle_event(event, executor);
102+ // return should_update
103+ }
104+ }
105+
106+ pub fn render(&mut self, renderer: &mut Renderer) {
107+ for layer in &mut self.layers {
108+ layer.render(renderer)
109+ }
110+ }
111+}
helix-term/src/main.rs+1
@@ -1,6 +1,7 @@
11#![allow(unused)]
22
33mod application;
4+mod compositor;
45
56use application::Application;
67