Skip to content

Commit 610eab2

Browse files
authored
Merge pull request #170 from curlpipe/0.6.8
0.6.8
2 parents bb2192a + 6bdf62a commit 610eab2

File tree

24 files changed

+1439
-161
lines changed

24 files changed

+1439
-161
lines changed

Cargo.lock

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ exclude = ["cactus"]
77

88
[package]
99
name = "ox"
10-
version = "0.6.7"
10+
version = "0.6.8"
1111
edition = "2021"
1212
authors = ["Curlpipe <[email protected]>"]
1313
description = "A Rust powered text editor."
@@ -41,4 +41,4 @@ kaolinite = { path = "./kaolinite" }
4141
mlua = { version = "0.9.9", features = ["lua54", "vendored"] }
4242
quick-error = "2.0.1"
4343
shellexpand = "3.1.0"
44-
synoptic = "2"
44+
synoptic = "2.2.1"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ It is mainly used on linux systems, but macOS and Windows users (via WSL) are fr
5555
- :electric_plug: Plug-In system where you can write your own plug-ins or integrate other people's
5656
- :wrench: A wide number of options for configuration with everything from colours to the status line to syntax highlighting being open to customisation
5757
- :moon: Ox uses Lua as a configuration language for familiarity when scripting and configuring
58+
- 🤝 A configuration assistant to quickly get Ox set up for you from the get-go
5859

5960
### Out of the box features
6061

config/.oxrc

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -117,47 +117,32 @@ event_mapping = {
117117
end,
118118
["alt_up"] = function()
119119
-- current line information
120-
line = editor:get_line()
121-
y = editor.cursor.y
120+
local line = editor:get_line()
121+
local cursor = editor.cursor
122122
-- insert a new line
123-
editor:insert_line_at(line, y - 1)
123+
editor:insert_line_at(line, cursor.y - 1)
124124
-- delete old copy and reposition cursor
125-
editor:remove_line_at(y + 1)
126-
editor:move_up()
125+
editor:remove_line_at(cursor.y + 1)
126+
-- restore cursor position
127+
editor:move_to(cursor.x, cursor.y - 1)
127128
-- correct indentation level
128129
autoindent:fix_indent()
129130
end,
130131
["alt_down"] = function()
131132
-- current line information
132-
line = editor:get_line()
133-
y = editor.cursor.y
133+
local line = editor:get_line()
134+
local cursor = editor.cursor
134135
-- insert a new line
135-
editor:insert_line_at(line, y + 2)
136+
editor:insert_line_at(line, cursor.y + 2)
136137
-- delete old copy and reposition cursor
137-
editor:remove_line_at(y)
138-
editor:move_down()
138+
editor:remove_line_at(cursor.y)
139+
-- restore cursor position
140+
editor:move_to(cursor.x, cursor.y + 1)
139141
-- correct indentation level
140142
autoindent:fix_indent()
141143
end,
142144
["ctrl_w"] = function()
143-
y = editor.cursor.y
144-
x = editor.cursor.x
145-
if editor:get_character() == " " then
146-
start = 0
147-
else
148-
start = 1
149-
end
150-
editor:move_previous_word()
151-
new_x = editor.cursor.x
152-
diff = x - new_x
153-
if editor.cursor.y == y then
154-
-- Cursor on the same line
155-
for i = start, diff do
156-
editor:remove_at(new_x, y)
157-
end
158-
else
159-
-- Cursor has passed up onto the previous line
160-
end
145+
editor:remove_word()
161146
end,
162147
}
163148

@@ -232,17 +217,18 @@ line_numbers.padding_right = 1
232217

233218
-- Configure Mouse Behaviour --
234219
terminal.mouse_enabled = true
235-
terminal.scroll_amount = 1
220+
terminal.scroll_amount = 2
236221

237222
-- Configure Tab Line --
238223
tab_line.enabled = true
239224
tab_line.format = " {file_name}{modified} "
240225

241226
-- Configure Status Line --
242-
status_line:add_part(" {file_name}{modified} │ {file_type} │") -- The left side of the status line
243-
status_line:add_part("│ {cursor_y} / {line_count} {cursor_x} ") -- The right side of the status line
244-
245-
status_line.alignment = "between" -- This will put a space between the left and right sides
227+
status_line.parts = {
228+
" {file_name}{modified} │ {file_type} │", -- The left side of the status line
229+
"│ {cursor_y} / {line_count} {cursor_x} ", -- The right side of the status line
230+
}
231+
status_line.alignment = "between" -- This will put a space between the parts (left and right sides)
246232

247233
-- Configure Greeting Message --
248234
greeting_message.enabled = true

kaolinite/src/document.rs

Lines changed: 153 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -662,30 +662,74 @@ impl Document {
662662
self.cancel_selection();
663663
}
664664

665+
/// Find the word boundaries
666+
pub fn word_boundaries(&mut self, line: &str) -> Vec<(usize, usize)> {
667+
let re = r"(\s{2,}|[A-Za-z0-9_]+|\.)";
668+
let mut searcher = Searcher::new(re);
669+
let starts: Vec<Match> = searcher.lfinds(line);
670+
let mut ends: Vec<Match> = starts.clone();
671+
ends.iter_mut()
672+
.for_each(|m| m.loc.x += m.text.chars().count());
673+
let starts: Vec<usize> = starts.iter().map(|m| m.loc.x).collect();
674+
let ends: Vec<usize> = ends.iter().map(|m| m.loc.x).collect();
675+
starts.into_iter().zip(ends).collect()
676+
}
677+
678+
/// Find the current state of the cursor in relation to words
679+
pub fn cursor_word_state(&mut self, words: &[(usize, usize)], x: usize) -> WordState {
680+
let in_word = words
681+
.iter()
682+
.position(|(start, end)| *start <= x && x <= *end);
683+
if let Some(idx) = in_word {
684+
let (word_start, word_end) = words[idx];
685+
if x == word_end {
686+
WordState::AtEnd(idx)
687+
} else if x == word_start {
688+
WordState::AtStart(idx)
689+
} else {
690+
WordState::InCenter(idx)
691+
}
692+
} else {
693+
WordState::Out
694+
}
695+
}
696+
665697
/// Moves to the previous word in the document
666698
pub fn move_prev_word(&mut self) -> Status {
667699
let Loc { x, y } = self.char_loc();
700+
// Handle case where we're at the beginning of the line
668701
if x == 0 && y != 0 {
669702
return Status::StartOfLine;
670703
}
671-
let re = format!("(\t| {{{}}}|^|\\W|$| )", self.tab_width);
672-
let mut searcher = Searcher::new(&re);
673-
let line = self
674-
.line(y)
675-
.unwrap_or_default()
676-
.chars()
677-
.take(x)
678-
.collect::<String>();
679-
let mut matches = searcher.rfinds(&line);
680-
if let Some(mtch) = matches.first() {
681-
if mtch.loc.x == x {
682-
matches.remove(0);
704+
// Find where all the words are
705+
let line = self.line(y).unwrap_or_default();
706+
let words = self.word_boundaries(&line);
707+
let state = self.cursor_word_state(&words, x);
708+
// Work out where to move to
709+
let new_x = match state {
710+
// Go to start of line if at beginning
711+
WordState::AtEnd(0) | WordState::InCenter(0) | WordState::AtStart(0) => 0,
712+
// Cursor is at the middle / end of a word, move to previous end
713+
WordState::AtEnd(idx) | WordState::InCenter(idx) => words[idx.saturating_sub(1)].1,
714+
WordState::AtStart(idx) => words[idx.saturating_sub(1)].0,
715+
WordState::Out => {
716+
// Cursor is not touching any words, find previous end
717+
let mut shift_back = x;
718+
while let WordState::Out = self.cursor_word_state(&words, shift_back) {
719+
shift_back = shift_back.saturating_sub(1);
720+
if shift_back == 0 {
721+
break;
722+
}
723+
}
724+
match self.cursor_word_state(&words, shift_back) {
725+
WordState::AtEnd(idx) => words[idx].1,
726+
_ => 0,
727+
}
683728
}
684-
}
685-
if let Some(mtch) = matches.first_mut() {
686-
mtch.loc.y = self.loc().y;
687-
self.move_to(&mtch.loc);
688-
}
729+
};
730+
// Perform the move
731+
self.move_to_x(new_x);
732+
// Clean up
689733
self.old_cursor = self.loc().x;
690734
Status::None
691735
}
@@ -694,18 +738,97 @@ impl Document {
694738
pub fn move_next_word(&mut self) -> Status {
695739
let Loc { x, y } = self.char_loc();
696740
let line = self.line(y).unwrap_or_default();
741+
// Handle case where we're at the end of the line
697742
if x == line.chars().count() && y != self.len_lines() {
698743
return Status::EndOfLine;
699744
}
700-
let re = format!("(\t| {{{}}}|\\W|$|^ +| )", self.tab_width);
701-
if let Some(mut mtch) = self.next_match(&re, 0) {
702-
mtch.loc.x += mtch.text.chars().count();
703-
self.move_to(&mtch.loc);
704-
}
745+
// Find and move to the next word
746+
let line = self.line(y).unwrap_or_default();
747+
let words = self.word_boundaries(&line);
748+
let state = self.cursor_word_state(&words, x);
749+
// Work out where to move to
750+
let new_x = match state {
751+
// Cursor is at the middle / end of a word, move to next end
752+
WordState::AtEnd(idx) | WordState::InCenter(idx) => {
753+
if let Some(word) = words.get(idx + 1) {
754+
word.1
755+
} else {
756+
// No next word exists, just go to end of line
757+
line.chars().count()
758+
}
759+
}
760+
WordState::AtStart(idx) => {
761+
// Cursor is at the start of a word, move to next start
762+
if let Some(word) = words.get(idx + 1) {
763+
word.0
764+
} else {
765+
// No next word exists, just go to end of line
766+
line.chars().count()
767+
}
768+
}
769+
WordState::Out => {
770+
// Cursor is not touching any words, find next start
771+
let mut shift_forward = x;
772+
while let WordState::Out = self.cursor_word_state(&words, shift_forward) {
773+
shift_forward += 1;
774+
if shift_forward >= line.chars().count() {
775+
break;
776+
}
777+
}
778+
match self.cursor_word_state(&words, shift_forward) {
779+
WordState::AtStart(idx) => words[idx].0,
780+
_ => line.chars().count(),
781+
}
782+
}
783+
};
784+
// Perform the move
785+
self.move_to_x(new_x);
786+
// Clean up
705787
self.old_cursor = self.loc().x;
706788
Status::None
707789
}
708790

791+
/// Function to delete a word at a certain location
792+
/// # Errors
793+
/// Errors if out of range
794+
pub fn delete_word(&mut self) -> Result<()> {
795+
let Loc { x, y } = self.char_loc();
796+
let line = self.line(y).unwrap_or_default();
797+
let words = self.word_boundaries(&line);
798+
let state = self.cursor_word_state(&words, x);
799+
let delete_upto = match state {
800+
WordState::InCenter(idx) | WordState::AtEnd(idx) => {
801+
// Delete back to start of this word
802+
words[idx].0
803+
}
804+
WordState::AtStart(0) => 0,
805+
WordState::AtStart(idx) => {
806+
// Delete back to start of the previous word
807+
words[idx.saturating_sub(1)].0
808+
}
809+
WordState::Out => {
810+
// Delete back to the end of the previous word
811+
let mut shift_back = x;
812+
while let WordState::Out = self.cursor_word_state(&words, shift_back) {
813+
shift_back = shift_back.saturating_sub(1);
814+
if shift_back == 0 {
815+
break;
816+
}
817+
}
818+
let char = line.chars().nth(shift_back);
819+
let state = self.cursor_word_state(&words, shift_back);
820+
match (char, state) {
821+
// Shift to start of previous word if there is a space
822+
(Some(' '), WordState::AtEnd(idx)) => words[idx].0,
823+
// Shift to end of previous word if there is not a space
824+
(_, WordState::AtEnd(idx)) => words[idx].1,
825+
_ => 0,
826+
}
827+
}
828+
};
829+
self.delete(delete_upto..=x, y)
830+
}
831+
709832
/// Function to search the document to find the next occurance of a regex
710833
pub fn next_match(&mut self, regex: &str, inc: usize) -> Option<Match> {
711834
// Prepare
@@ -1204,3 +1327,11 @@ pub struct Cursor {
12041327
pub loc: Loc,
12051328
pub selection_end: Loc,
12061329
}
1330+
1331+
/// State of a word
1332+
pub enum WordState {
1333+
AtStart(usize),
1334+
AtEnd(usize),
1335+
InCenter(usize),
1336+
Out,
1337+
}

kaolinite/src/searching.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::utils::Loc;
44
use regex::Regex;
55

66
/// Stores information about a match in a document
7-
#[derive(Debug, PartialEq, Eq)]
7+
#[derive(Debug, PartialEq, Eq, Clone)]
88
pub struct Match {
99
pub loc: Loc,
1010
pub text: String,

0 commit comments

Comments
 (0)