on master help sign in
Work on insert mode.
579b689Blaž Hrastnik
helix-core/src/commands.rs+21−1
@@ -1,4 +1,4 @@
1−use crate::state::{Direction, Granularity, State};
1+use crate::state::{Direction, Granularity, Mode, State};
2
2
3/// A command is a function that takes the current state and a count, and does a side-effect on the
3/// A command is a function that takes the current state and a count, and does a side-effect on the
4/// state (usually by creating and applying a transaction).
4/// state (usually by creating and applying a transaction).
@@ -48,3 +48,23 @@ pub fn move_line_down(state: &mut State, count: usize) {
48 count,
48 count,
49 );
49 );
50}
50}
51+
52+pub fn insert_mode(state: &mut State, _count: usize) {
53+ state.mode = Mode::Insert;
54+}
55+
56+pub fn normal_mode(state: &mut State, _count: usize) {
57+ state.mode = Mode::Normal;
58+}
59+
60+// TODO: insert means add text just before cursor, on exit we should be on the last letter.
61+pub fn insert(state: &mut State, c: char) {
62+ // TODO: needs to work with multiple cursors
63+ use crate::transaction::ChangeSet;
64+
65+ let pos = state.selection.primary().head;
66+ let changes = ChangeSet::insert(&state.doc, pos, c);
67+ // TODO: need to store history
68+ changes.apply(state.contents_mut());
69+ state.selection = state.selection.clone().map(&changes);
70+}
helix-core/src/selection.rs+2−2
@@ -63,8 +63,8 @@ impl Range {
63 /// Map a range through a set of changes. Returns a new range representing the same position
63 /// Map a range through a set of changes. Returns a new range representing the same position
64 /// after the changes are applied.
64 /// after the changes are applied.
65 pub fn map(self, changes: &ChangeSet) -> Self {
65 pub fn map(self, changes: &ChangeSet) -> Self {
66− let anchor = changes.map_pos(self.anchor, Assoc::Before);
66+ let anchor = changes.map_pos(self.anchor, Assoc::After);
67− let head = changes.map_pos(self.head, Assoc::Before);
67+ let head = changes.map_pos(self.head, Assoc::After);
68
68
69 // TODO: possibly unnecessary
69 // TODO: possibly unnecessary
70 if self.anchor == anchor && self.head == head {
70 if self.anchor == anchor && self.head == head {
helix-core/src/state.rs+13−1
@@ -1,10 +1,16 @@
1use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
1use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
2use crate::{Buffer, Rope, RopeSlice, Selection, SelectionRange};
2use crate::{Buffer, Rope, RopeSlice, Selection, SelectionRange};
3
3
4+pub enum Mode {
5+ Normal,
6+ Insert,
7+}
8+
4/// A state represents the current editor state of a single buffer.
9/// A state represents the current editor state of a single buffer.
5pub struct State {
10pub struct State {
6 pub doc: Buffer,
11 pub doc: Buffer,
7 pub selection: Selection,
12 pub selection: Selection,
13+ pub mode: Mode,
8}
14}
9
15
10#[derive(Copy, Clone, PartialEq, Eq)]
16#[derive(Copy, Clone, PartialEq, Eq)]
@@ -26,6 +32,7 @@ impl State {
26 Self {
32 Self {
27 doc,
33 doc,
28 selection: Selection::single(0, 0),
34 selection: Selection::single(0, 0),
35+ mode: Mode::Normal,
29 }
36 }
30 }
37 }
31
38
@@ -119,10 +126,15 @@ impl State {
119 // TODO: update selection in state via transaction
126 // TODO: update selection in state via transaction
120 }
127 }
121
128
122− pub fn file(&self) -> &Rope {
129+ pub fn contents(&self) -> &Rope {
123 // used to access file contents for rendering to screen
130 // used to access file contents for rendering to screen
124 &self.doc.contents
131 &self.doc.contents
125 }
132 }
133+
134+ pub fn contents_mut(&mut self) -> &mut Rope {
135+ // used to access file contents for rendering to screen
136+ &mut self.doc.contents
137+ }
126}
138}
127
139
128/// Coordinates are a 0-indexed line and column pair.
140/// Coordinates are a 0-indexed line and column pair.
helix-core/src/transaction.rs+12
@@ -47,6 +47,18 @@ impl ChangeSet {
47 }
47 }
48 }
48 }
49
49
50+ pub fn insert(buf: &Buffer, pos: usize, c: char) -> Self {
51+ let len = buf.contents.len_chars();
52+ Self {
53+ changes: vec![
54+ Change::Retain(pos),
55+ Change::Insert(Tendril::from_char(c)),
56+ Change::Retain(len - pos),
57+ ],
58+ len,
59+ }
60+ }
61+
50 // TODO: from iter
62 // TODO: from iter
51
63
52 /// Combine two changesets together.
64 /// Combine two changesets together.
helix-term/src/editor.rs+76−55
@@ -9,36 +9,11 @@ use crossterm::{
9 terminal::{self, disable_raw_mode, enable_raw_mode},
9 terminal::{self, disable_raw_mode, enable_raw_mode},
10};
10};
11use futures::{future::FutureExt, select, StreamExt};
11use futures::{future::FutureExt, select, StreamExt};
12−use helix_core::{state::coords_at_pos, Buffer, State};
12+use helix_core::{state::coords_at_pos, state::Mode, Buffer, State};
13use std::io::{self, stdout, Write};
13use std::io::{self, stdout, Write};
14use std::path::PathBuf;
14use std::path::PathBuf;
15use std::time::Duration;
15use std::time::Duration;
16
16
17−pub struct BufferComponent<'a> {
18− x: u16,
19− y: u16,
20− contents: Vec<&'a str>,
21−}
22−
23−impl BufferComponent<'_> {
24− pub fn render(&self) {
25− for (n, line) in self.contents.iter().enumerate() {
26− execute!(
27− stdout(),
28− SetForegroundColor(Color::DarkCyan),
29− cursor::MoveTo(self.x, self.y + n as u16),
30− Print((n + 1).to_string())
31− );
32− execute!(
33− stdout(),
34− SetForegroundColor(Color::Reset),
35− cursor::MoveTo(self.x + 2, self.y + n as u16),
36− Print(line)
37− );
38− }
39− }
40−}
41−
42static EX: smol::Executor = smol::Executor::new();
17static EX: smol::Executor = smol::Executor::new();
43
18
44pub struct Editor {
19pub struct Editor {
@@ -70,21 +45,56 @@ impl Editor {
70 }
45 }
71
46
72 fn render(&mut self) {
47 fn render(&mut self) {
73− // TODO:
74−
75 match &self.state {
48 match &self.state {
76− Some(s) => {
49+ Some(state) => {
77− let view = BufferComponent {
50+ let lines = state
78− x: 0,
79− y: self.first_line,
80− contents: s
51+ .contents()
81− .file()
82− .lines_at(self.first_line as usize)
52+ .lines_at(self.first_line as usize)
83− .take(self.size.1 as usize)
53+ .take(self.size.1 as usize)
84− .map(|x| x.as_str().unwrap())
54+ .map(|x| x.as_str().unwrap());
85− .collect::<Vec<&str>>(),
55+
56+ let mut stdout = stdout();
57+
58+ for (n, line) in lines.enumerate() {
59+ execute!(
60+ stdout,
61+ SetForegroundColor(Color::DarkCyan),
62+ cursor::MoveTo(0, n as u16),
63+ Print((n + 1).to_string())
64+ );
65+ execute!(
66+ stdout,
67+ SetForegroundColor(Color::Reset),
68+ cursor::MoveTo(2, n as u16),
69+ Print(line)
70+ );
71+ }
72+
73+ let mode = match state.mode {
74+ Mode::Insert => "INS",
75+ Mode::Normal => "NOR",
76+ };
77+
78+ execute!(
79+ stdout,
80+ SetForegroundColor(Color::Reset),
81+ cursor::MoveTo(0, self.size.1),
82+ Print(mode)
83+ );
84+
85+ // set cursor shape
86+ match state.mode {
87+ Mode::Insert => write!(stdout, "\x1B[6 q"),
88+ Mode::Normal => write!(stdout, "\x1B[2 q"),
86 };
89 };
90+
87− view.render();
91+ // render the cursor
92+ let pos = state.selection.primary().head;
93+ let coords = coords_at_pos(&state.doc.contents.slice(..), pos);
94+ execute!(
95+ stdout,
96+ cursor::MoveTo((coords.1 + 2) as u16, coords.0 as u16)
97+ );
88 }
98 }
89 None => (),
99 None => (),
90 }
100 }
@@ -108,22 +118,30 @@ impl Editor {
108 break;
118 break;
109 }
119 }
110 Some(Ok(Event::Key(event))) => {
120 Some(Ok(Event::Key(event))) => {
121+ if let Some(state) = &mut self.state {
122+ match state.mode {
123+ Mode::Insert => {
124+ match event {
125+ KeyEvent {
126+ code: KeyCode::Esc, ..
127+ } => helix_core::commands::normal_mode(state, 1),
128+ KeyEvent {
129+ code: KeyCode::Char(c),
130+ ..
131+ } => helix_core::commands::insert(state, c),
132+ _ => (), // skip
133+ }
134+ self.render();
135+ }
136+ Mode::Normal => {
111− // TODO: handle modes and sequences (`gg`)
137+ // TODO: handle modes and sequences (`gg`)
112− if let Some(command) = keymap.get(&event) {
138+ if let Some(command) = keymap.get(&event) {
113− if let Some(state) = &mut self.state {
114− // TODO: handle count other than 1
139+ // TODO: handle count other than 1
115− command(state, 1);
140+ command(state, 1);
116− self.render();
141+
117− // render the cursor
142+ self.render();
118− let pos = self.state.as_ref().unwrap().selection.primary().head;
143+ }
119− let coords = coords_at_pos(
144+ }
120− &self.state.as_ref().unwrap().doc.contents.slice(..),
121− pos,
122− );
123− execute!(
124− stdout(),
125− cursor::MoveTo((coords.1 + 2) as u16, coords.0 as u16)
126− );
127 }
145 }
128 }
146 }
129 }
147 }
@@ -145,6 +163,9 @@ impl Editor {
145
163
146 self.print_events().await;
164 self.print_events().await;
147
165
166+ // reset cursor shape
167+ write!(stdout, "\x1B[2 q");
168+
148 execute!(stdout, terminal::LeaveAlternateScreen)?;
169 execute!(stdout, terminal::LeaveAlternateScreen)?;
149
170
150 disable_raw_mode()?;
171 disable_raw_mode()?;
helix-term/src/keymap.rs+15
@@ -116,5 +116,20 @@ pub fn default() -> Keymap {
116 code: KeyCode::Char('l'),
116 code: KeyCode::Char('l'),
117 modifiers: Modifiers::NONE
117 modifiers: Modifiers::NONE
118 } => commands::move_char_right as Command,
118 } => commands::move_char_right as Command,
119+ Key {
120+ code: KeyCode::Char('i'),
121+ modifiers: Modifiers::NONE
122+ } => commands::insert_mode as Command,
123+ Key {
124+ code: KeyCode::Esc,
125+ modifiers: Modifiers::NONE
126+ } => commands::normal_mode as Command,
119 )
127 )
128+
129+ // hashmap!(
130+ // Key {
131+ // code: KeyCode::Esc,
132+ // modifiers: Modifiers::NONE
133+ // } => commands::normal_mode as Command,
134+ // )
120}
135}