mirror of
https://github.com/ilyakooo0/helix.git
synced 2024-11-28 21:20:23 +03:00
Indent draft, linewise paste
This commit is contained in:
parent
4a648555ed
commit
00e661f600
111
helix-core/src/indent.rs
Normal file
111
helix-core/src/indent.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use crate::{
|
||||
syntax::Syntax,
|
||||
tree_sitter::{Node, Tree},
|
||||
Rope, RopeSlice, State,
|
||||
};
|
||||
|
||||
const TAB_WIDTH: usize = 4;
|
||||
|
||||
fn indent_level_for_line(line: RopeSlice) -> usize {
|
||||
let mut len = 0;
|
||||
for ch in line.chars() {
|
||||
match ch {
|
||||
'\t' => len += TAB_WIDTH,
|
||||
' ' => len += 1,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
len / TAB_WIDTH
|
||||
}
|
||||
|
||||
/// Find the highest syntax node at position.
|
||||
/// This is to identify the column where this node (e.g., an HTML closing tag) ends.
|
||||
fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Node> {
|
||||
let tree = syntax.root_layer.tree.as_ref().unwrap();
|
||||
|
||||
let mut node = match tree.root_node().named_descendant_for_byte_range(pos, pos) {
|
||||
Some(node) => node,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
while let Some(parent) = node.parent() {
|
||||
if parent.start_byte() == node.start_byte() {
|
||||
node = parent
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Some(node)
|
||||
}
|
||||
|
||||
fn walk(node: Option<Node>) -> usize {
|
||||
let node = match node {
|
||||
Some(node) => node,
|
||||
None => return 0,
|
||||
};
|
||||
|
||||
let parent = match node.parent() {
|
||||
Some(node) => node,
|
||||
None => return 0,
|
||||
};
|
||||
|
||||
let mut increment = 0;
|
||||
|
||||
let not_first_or_last_sibling = node.next_sibling().is_some() && node.prev_sibling().is_some();
|
||||
let is_scope = true;
|
||||
|
||||
if not_first_or_last_sibling && is_scope {
|
||||
increment += 1;
|
||||
}
|
||||
|
||||
walk(Some(parent)) + increment
|
||||
}
|
||||
|
||||
// for_line_at_col
|
||||
fn suggested_indent_for_line(state: &State, line_num: usize) -> usize {
|
||||
let line = state.doc.line(line_num);
|
||||
let current = indent_level_for_line(line);
|
||||
|
||||
let mut byte_start = state.doc.line_to_byte(line_num);
|
||||
|
||||
// find first non-whitespace char
|
||||
for ch in line.chars() {
|
||||
// TODO: could use memchr with chunks?
|
||||
if ch != ' ' && ch != '\t' {
|
||||
break;
|
||||
}
|
||||
byte_start += 1;
|
||||
}
|
||||
|
||||
if let Some(syntax) = &state.syntax {
|
||||
let node = get_highest_syntax_node_at_bytepos(state.syntax.as_ref().unwrap(), byte_start);
|
||||
|
||||
// let indentation = walk()
|
||||
// special case for comments
|
||||
|
||||
// if preserve_leading_whitespace
|
||||
|
||||
unimplemented!()
|
||||
} else {
|
||||
// TODO: case for non-tree sitter grammars
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn indent_level() {
|
||||
let line = Rope::from(" fn new"); // 8 spaces
|
||||
assert_eq!(indent_level_for_line(line.slice(..)), 2);
|
||||
let line = Rope::from("\t\t\tfn new"); // 3 tabs
|
||||
assert_eq!(indent_level_for_line(line.slice(..)), 3);
|
||||
// mixed indentation
|
||||
let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
|
||||
assert_eq!(indent_level_for_line(line.slice(..)), 3);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#![allow(unused)]
|
||||
pub mod graphemes;
|
||||
mod history;
|
||||
mod indent;
|
||||
pub mod macros;
|
||||
mod position;
|
||||
pub mod register;
|
||||
|
@ -110,7 +110,6 @@ impl Range {
|
||||
|
||||
#[inline]
|
||||
pub fn fragment<'a>(&'a self, text: &'a RopeSlice) -> Cow<'a, str> {
|
||||
// end inclusive
|
||||
Cow::from(text.slice(self.from()..self.to() + 1))
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ impl State {
|
||||
#[must_use]
|
||||
pub fn new(doc: Rope) -> Self {
|
||||
let changes = ChangeSet::new(&doc);
|
||||
let old_state = Some((doc.clone(), Selection::single(0, 0)));
|
||||
|
||||
Self {
|
||||
path: None,
|
||||
@ -56,7 +57,7 @@ impl State {
|
||||
restore_cursor: false,
|
||||
syntax: None,
|
||||
changes,
|
||||
old_state: None,
|
||||
old_state,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,7 @@ pub struct Syntax {
|
||||
|
||||
config: Arc<HighlightConfiguration>,
|
||||
|
||||
root_layer: LanguageLayer,
|
||||
pub(crate) root_layer: LanguageLayer,
|
||||
}
|
||||
|
||||
impl Syntax {
|
||||
@ -309,7 +309,7 @@ pub struct LanguageLayer {
|
||||
// mode
|
||||
// grammar
|
||||
// depth
|
||||
tree: Option<Tree>,
|
||||
pub(crate) tree: Option<Tree>,
|
||||
}
|
||||
|
||||
use crate::state::coords_at_pos;
|
||||
|
@ -241,7 +241,7 @@ pub fn select_line(view: &mut View, _count: usize) {
|
||||
let text = view.state.doc();
|
||||
let line = text.char_to_line(pos.head);
|
||||
let start = text.line_to_char(line);
|
||||
let end = text.line_to_char(line + 1);
|
||||
let end = text.line_to_char(line + 1).saturating_sub(1);
|
||||
|
||||
// TODO: use a transaction
|
||||
view.state.selection = Selection::single(start, end);
|
||||
@ -249,7 +249,7 @@ pub fn select_line(view: &mut View, _count: usize) {
|
||||
|
||||
pub fn delete_selection(view: &mut View, _count: usize) {
|
||||
let transaction =
|
||||
Transaction::change_by_selection(&view.state, |range| (range.from(), range.to(), None));
|
||||
Transaction::change_by_selection(&view.state, |range| (range.from(), range.to() + 1, None));
|
||||
transaction.apply(&mut view.state);
|
||||
|
||||
append_changes_to_history(view);
|
||||
@ -267,6 +267,13 @@ pub fn collapse_selection(view: &mut View, _count: usize) {
|
||||
.transform(|range| Range::new(range.head, range.head))
|
||||
}
|
||||
|
||||
pub fn flip_selections(view: &mut View, _count: usize) {
|
||||
view.state.selection = view
|
||||
.state
|
||||
.selection
|
||||
.transform(|range| Range::new(range.head, range.anchor))
|
||||
}
|
||||
|
||||
fn enter_insert_mode(view: &mut View) {
|
||||
view.state.mode = Mode::Insert;
|
||||
|
||||
@ -463,7 +470,7 @@ pub fn delete_char_forward(view: &mut View, count: usize) {
|
||||
pub fn undo(view: &mut View, _count: usize) {
|
||||
view.history.undo(&mut view.state);
|
||||
|
||||
// TODO: each command should simply return a Option<transaction>, then the higher level handles storing it?
|
||||
// TODO: each command could simply return a Option<transaction>, then the higher level handles storing it?
|
||||
}
|
||||
|
||||
pub fn redo(view: &mut View, _count: usize) {
|
||||
@ -481,11 +488,15 @@ pub fn yank(view: &mut View, _count: usize) {
|
||||
.map(|cow| cow.into_owned())
|
||||
.collect();
|
||||
|
||||
register::set('"', values);
|
||||
// TODO: allow specifying reg
|
||||
let reg = '"';
|
||||
register::set(reg, values);
|
||||
}
|
||||
|
||||
pub fn paste(view: &mut View, _count: usize) {
|
||||
if let Some(values) = register::get('"') {
|
||||
// TODO: allow specifying reg
|
||||
let reg = '"';
|
||||
if let Some(values) = register::get(reg) {
|
||||
let repeat = std::iter::repeat(
|
||||
values
|
||||
.last()
|
||||
@ -493,13 +504,74 @@ pub fn paste(view: &mut View, _count: usize) {
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// TODO: if any of values ends \n it's linewise paste
|
||||
//
|
||||
// p => paste after
|
||||
// P => paste before
|
||||
// alt-p => paste every yanked selection after selected text
|
||||
// alt-P => paste every yanked selection before selected text
|
||||
// R => replace selected text with yanked text
|
||||
// alt-R => replace selected text with every yanked text
|
||||
//
|
||||
// append => insert at next line
|
||||
// insert => insert at start of line
|
||||
// replace => replace
|
||||
// default insert
|
||||
|
||||
let linewise = values.iter().any(|value| value.ends_with('\n'));
|
||||
|
||||
let mut values = values.into_iter().map(Tendril::from).chain(repeat);
|
||||
|
||||
let transaction = Transaction::change_by_selection(&view.state, |range| {
|
||||
(range.head + 1, range.head + 1, Some(values.next().unwrap()))
|
||||
});
|
||||
let transaction = if linewise {
|
||||
// paste on the next line
|
||||
// TODO: can simply take a range + modifier and compute the right pos without ifs
|
||||
let text = view.state.doc();
|
||||
Transaction::change_by_selection(&view.state, |range| {
|
||||
let line_end = text.line_to_char(text.char_to_line(range.head) + 1);
|
||||
(line_end, line_end, Some(values.next().unwrap()))
|
||||
})
|
||||
} else {
|
||||
Transaction::change_by_selection(&view.state, |range| {
|
||||
(range.head + 1, range.head + 1, Some(values.next().unwrap()))
|
||||
})
|
||||
};
|
||||
|
||||
transaction.apply(&mut view.state);
|
||||
append_changes_to_history(view);
|
||||
}
|
||||
}
|
||||
|
||||
const TAB_WIDTH: usize = 4;
|
||||
|
||||
pub fn indent(view: &mut View, _count: usize) {
|
||||
let mut lines = Vec::new();
|
||||
|
||||
// Get all line numbers
|
||||
for range in view.state.selection.ranges() {
|
||||
let start = view.state.doc.char_to_line(range.from());
|
||||
let end = view.state.doc.char_to_line(range.to());
|
||||
|
||||
for line in start..=end {
|
||||
lines.push(line)
|
||||
}
|
||||
}
|
||||
lines.sort_unstable(); // sorting by usize so _unstable is preferred
|
||||
lines.dedup();
|
||||
|
||||
// Indent by one level
|
||||
let indent = Tendril::from(" ".repeat(TAB_WIDTH));
|
||||
|
||||
let transaction = Transaction::change(
|
||||
&view.state,
|
||||
lines.into_iter().map(|line| {
|
||||
let pos = view.state.doc.line_to_char(line);
|
||||
(pos, pos, Some(indent.clone()))
|
||||
}),
|
||||
);
|
||||
transaction.apply(&mut view.state);
|
||||
append_changes_to_history(view);
|
||||
}
|
||||
|
||||
pub fn unindent(view: &mut View, _count: usize) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
@ -117,6 +117,15 @@ macro_rules! ctrl {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! alt {
|
||||
($ch:expr) => {
|
||||
Key {
|
||||
code: KeyCode::Char($ch),
|
||||
modifiers: Modifiers::ALT,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn default() -> Keymaps {
|
||||
hashmap!(
|
||||
state::Mode::Normal =>
|
||||
@ -145,11 +154,15 @@ pub fn default() -> Keymaps {
|
||||
vec![key!('c')] => commands::change_selection,
|
||||
vec![key!('s')] => commands::split_selection_on_newline,
|
||||
vec![key!(';')] => commands::collapse_selection,
|
||||
// TODO should be alt(;)
|
||||
vec![key!('%')] => commands::flip_selections,
|
||||
vec![key!('x')] => commands::select_line,
|
||||
vec![key!('u')] => commands::undo,
|
||||
vec![shift!('U')] => commands::redo,
|
||||
vec![key!('y')] => commands::yank,
|
||||
vec![key!('p')] => commands::paste,
|
||||
vec![key!('>')] => commands::indent,
|
||||
vec![key!('<')] => commands::unindent,
|
||||
vec![Key {
|
||||
code: KeyCode::Esc,
|
||||
modifiers: Modifiers::NONE
|
||||
|
Loading…
Reference in New Issue
Block a user