aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--helix-core/src/state.rs4
-rw-r--r--helix-core/src/syntax.rs2
-rw-r--r--helix-term/src/editor.rs22
-rw-r--r--helix-term/test.txt1000
-rw-r--r--helix-view/src/commands.rs74
-rw-r--r--helix-view/src/keymap.rs28
-rw-r--r--helix-view/src/view.rs22
8 files changed, 1133 insertions, 20 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7253b7fd..68870ef6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -225,6 +225,7 @@ checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
dependencies = [
"bitflags",
"indexmap",
+ "lazy_static",
"os_str_bytes",
"textwrap",
"unicode-width",
diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs
index 36ca221c..047ff83d 100644
--- a/helix-core/src/state.rs
+++ b/helix-core/src/state.rs
@@ -9,6 +9,7 @@ use std::path::PathBuf;
pub enum Mode {
Normal,
Insert,
+ Goto,
}
/// A state represents the current editor state of a single buffer.
@@ -287,9 +288,6 @@ impl State {
}
}
-/// Coordinates are a 0-indexed line and column pair.
-pub type Coords = (usize, usize); // line, col
-
/// Convert a character index to (line, column) coordinates.
pub fn coords_at_pos(text: &RopeSlice, pos: usize) -> Position {
let line = text.char_to_line(pos);
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index 638ec8ee..3e5927e5 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -312,7 +312,7 @@ pub struct LanguageLayer {
tree: Option<Tree>,
}
-use crate::state::{coords_at_pos, Coords};
+use crate::state::coords_at_pos;
use crate::transaction::{ChangeSet, Operation};
use crate::Tendril;
diff --git a/helix-term/src/editor.rs b/helix-term/src/editor.rs
index 70b5758e..09d66c5a 100644
--- a/helix-term/src/editor.rs
+++ b/helix-term/src/editor.rs
@@ -87,7 +87,7 @@ impl Editor {
// TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
let source_code = view.state.doc().to_string();
- let last_line = view.last_line(viewport);
+ let last_line = view.last_line();
let range = {
// calculate viewport byte ranges
@@ -219,7 +219,7 @@ impl Editor {
}
let style: Style = view.theme.get("ui.linenr");
- for (i, line) in (view.first_line..(last_line as u16)).enumerate() {
+ for (i, line) in (view.first_line..last_line).enumerate() {
self.surface
.set_stringn(0, i as u16, format!("{:>5}", line + 1), 5, style);
// lavender
@@ -254,6 +254,7 @@ impl Editor {
let mode = match view.state.mode() {
Mode::Insert => "INS",
Mode::Normal => "NOR",
+ Mode::Goto => "GOTO",
};
self.surface.set_style(
Rect::new(0, self.size.1 - 1, self.size.0, 1),
@@ -278,13 +279,14 @@ impl Editor {
match view.state.mode() {
Mode::Insert => write!(stdout, "\x1B[6 q"),
Mode::Normal => write!(stdout, "\x1B[2 q"),
+ Mode::Goto => write!(stdout, "\x1B[2 q"),
};
// render the cursor
let pos = view.state.selection().cursor();
let pos = view
- .screen_coords_at_pos(&view.state.doc().slice(..), pos, area)
+ .screen_coords_at_pos(&view.state.doc().slice(..), pos)
.expect("Cursor is out of bounds.");
execute!(
@@ -326,6 +328,7 @@ impl Editor {
}))) => {
break;
}
+
Some(Ok(Event::Key(event))) => {
if let Some(view) = &mut self.view {
match view.state.mode() {
@@ -359,6 +362,19 @@ impl Editor {
self.render();
}
}
+ Mode::Goto => {
+ // TODO: handle modes and sequences (`gg`)
+ let keys = vec![event];
+ if let Some(command) = keymap[&Mode::Goto].get(&keys) {
+ // TODO: handle count other than 1
+ command(view, 1);
+
+ // TODO: simplistic ensure cursor in view for now
+ view.ensure_cursor_in_view();
+
+ self.render();
+ }
+ }
}
}
}
diff --git a/helix-term/test.txt b/helix-term/test.txt
new file mode 100644
index 00000000..11798245
--- /dev/null
+++ b/helix-term/test.txt
@@ -0,0 +1,1000 @@
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
diff --git a/helix-view/src/commands.rs b/helix-view/src/commands.rs
index 6dd1101c..19853c37 100644
--- a/helix-view/src/commands.rs
+++ b/helix-view/src/commands.rs
@@ -13,6 +13,8 @@ use crate::view::View;
/// state (usually by creating and applying a transaction).
pub type Command = fn(view: &mut View, count: usize);
+const PADDING: usize = 5;
+
pub fn move_char_left(view: &mut View, count: usize) {
// TODO: use a transaction
let selection = view
@@ -117,6 +119,74 @@ pub fn move_next_word_end(view: &mut View, count: usize) {
view.state.selection = Selection::single(pos, pos);
}
+pub fn move_file_start(view: &mut View, _count: usize) {
+ // TODO: use a transaction
+ view.state.selection = Selection::single(0, 0);
+
+ view.state.mode = Mode::Normal;
+}
+
+pub fn move_file_end(view: &mut View, _count: usize) {
+ // TODO: use a transaction
+ let text = &view.state.doc;
+ let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
+ view.state.selection = Selection::single(last_line, last_line);
+
+ view.state.mode = Mode::Normal;
+}
+
+pub fn check_cursor_in_view(view: &mut View) -> bool {
+ let cursor = view.state.selection().cursor();
+ let line = view.state.doc().char_to_line(cursor);
+ let document_end = view.first_line + view.size.1.saturating_sub(1) as usize;
+
+ if (line > document_end.saturating_sub(PADDING)) | (line < view.first_line + PADDING) {
+ return false;
+ }
+ true
+}
+
+pub fn page_up(view: &mut View, _count: usize) {
+ view.first_line = view.first_line.saturating_sub(view.size.1 as usize);
+
+ if !check_cursor_in_view(view) {
+ let text = view.state.doc();
+ let pos = text.line_to_char(view.last_line().saturating_sub(PADDING as usize));
+ view.state.selection = Selection::single(pos, pos);
+ }
+}
+
+pub fn page_down(view: &mut View, _count: usize) {
+ view.first_line += view.size.1 as usize + PADDING;
+
+ if view.first_line < view.state.doc().len_lines() {
+ let text = view.state.doc();
+ let pos = text.line_to_char(view.first_line as usize);
+ view.state.selection = Selection::single(pos, pos);
+ }
+}
+
+pub fn half_page_up(view: &mut View, _count: usize) {
+ view.first_line = view.first_line.saturating_sub(view.size.1 as usize / 2);
+
+ if !check_cursor_in_view(view) {
+ let text = &view.state.doc;
+ let pos = text.line_to_char(view.last_line() - PADDING as usize);
+ view.state.selection = Selection::single(pos, pos);
+ }
+}
+
+pub fn half_page_down(view: &mut View, _count: usize) {
+ let lines = view.state.doc().len_lines();
+ if view.first_line < lines.saturating_sub(view.size.1 as usize) {
+ view.first_line += view.size.1 as usize / 2;
+ }
+ if !check_cursor_in_view(view) {
+ let text = view.state.doc();
+ let pos = text.line_to_char(view.first_line as usize);
+ view.state.selection = Selection::single(pos, pos);
+ }
+}
// avoid select by default by having a visual mode switch that makes movements into selects
pub fn extend_char_left(view: &mut View, count: usize) {
@@ -292,6 +362,10 @@ pub fn normal_mode(view: &mut View, _count: usize) {
}
}
+pub fn goto_mode(view: &mut View, _count: usize) {
+ view.state.mode = Mode::Goto;
+}
+
// TODO: insert means add text just before cursor, on exit we should be on the last letter.
pub fn insert_char(view: &mut View, c: char) {
let c = Tendril::from_char(c);
diff --git a/helix-view/src/keymap.rs b/helix-view/src/keymap.rs
index ef23ff2a..72fc0e79 100644
--- a/helix-view/src/keymap.rs
+++ b/helix-view/src/keymap.rs
@@ -108,6 +108,15 @@ macro_rules! shift {
};
}
+macro_rules! ctrl {
+ ($ch:expr) => {
+ Key {
+ code: KeyCode::Char($ch),
+ modifiers: Modifiers::CONTROL,
+ }
+ };
+}
+
pub fn default() -> Keymaps {
hashmap!(
state::Mode::Normal =>
@@ -126,6 +135,7 @@ pub fn default() -> Keymaps {
vec![key!('w')] => commands::move_next_word_start,
vec![key!('b')] => commands::move_prev_word_start,
vec![key!('e')] => commands::move_next_word_end,
+ vec![key!('g')] => commands::goto_mode,
vec![key!('i')] => commands::insert_mode,
vec![shift!('I')] => commands::prepend_to_line,
vec![key!('a')] => commands::append_mode,
@@ -139,6 +149,16 @@ pub fn default() -> Keymaps {
code: KeyCode::Esc,
modifiers: Modifiers::NONE
}] => commands::normal_mode,
+ vec![Key {
+ code: KeyCode::PageUp,
+ modifiers: Modifiers::NONE
+ }] => commands::page_up,
+ vec![Key {
+ code: KeyCode::PageDown,
+ modifiers: Modifiers::NONE
+ }] => commands::page_down,
+ vec![ctrl!('u')] => commands::half_page_up,
+ vec![ctrl!('d')] => commands::half_page_down,
),
state::Mode::Insert => hashmap!(
vec![Key {
@@ -161,6 +181,14 @@ pub fn default() -> Keymaps {
code: KeyCode::Tab,
modifiers: Modifiers::NONE
}] => commands::insert_tab,
+ ),
+ state::Mode::Goto => hashmap!(
+ vec![Key {
+ code: KeyCode::Esc,
+ modifiers: Modifiers::NONE
+ }] => commands::normal_mode as Command,
+ vec![key!('g')] => commands::move_file_start as Command,
+ vec![key!('e')] => commands::move_file_end as Command,
)
)
}
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 09cd4c65..887c45a2 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -11,7 +11,7 @@ use tui::layout::Rect;
pub struct View {
pub state: State,
- pub first_line: u16,
+ pub first_line: usize,
pub size: (u16, u16),
pub theme: Theme, // TODO: share one instance
}
@@ -33,10 +33,10 @@ impl View {
pub fn ensure_cursor_in_view(&mut self) {
let cursor = self.state.selection().cursor();
- let line = self.state.doc().char_to_line(cursor) as u16;
- let document_end = self.first_line + self.size.1.saturating_sub(1) - 1;
+ let line = self.state.doc().char_to_line(cursor);
+ let document_end = self.first_line + (self.size.1 as usize).saturating_sub(1);
- let padding = 5u16;
+ let padding = 5usize;
// TODO: side scroll
@@ -51,9 +51,10 @@ impl View {
/// Calculates the last visible line on screen
#[inline]
- pub fn last_line(&self, viewport: Rect) -> usize {
+ pub fn last_line(&self) -> usize {
+ let viewport = Rect::new(6, 0, self.size.0, self.size.1 - 1); // - 1 for statusline
std::cmp::min(
- (self.first_line + viewport.height) as usize,
+ self.first_line + (viewport.height as usize),
self.state.doc().len_lines() - 1,
)
}
@@ -61,15 +62,10 @@ impl View {
/// Translates a document position to an absolute position in the terminal.
/// Returns a (line, col) position if the position is visible on screen.
// TODO: Could return width as well for the character width at cursor.
- pub fn screen_coords_at_pos(
- &self,
- text: &RopeSlice,
- pos: usize,
- viewport: Rect,
- ) -> Option<Position> {
+ pub fn screen_coords_at_pos(&self, text: &RopeSlice, pos: usize) -> Option<Position> {
let line = text.char_to_line(pos);
- if line < self.first_line as usize || line > self.last_line(viewport) {
+ if line < self.first_line as usize || line > self.last_line() {
// Line is not visible on screen
return None;
}