indenting full in steel now

pull/8675/merge^2
mattwparas 1 year ago
parent 08ab867b1b
commit 72aa2d9520

@ -760,144 +760,150 @@ static LISP_WORDS: Lazy<std::collections::HashSet<&'static str>> = Lazy::new(||
/// TODO: Come up with some elegant enough FFI for this, so that Steel can expose an API for this. /// TODO: Come up with some elegant enough FFI for this, so that Steel can expose an API for this.
/// Problem is - the issues with the `Any` type and using things with type id. /// Problem is - the issues with the `Any` type and using things with type id.
#[allow(clippy::too_many_arguments)] // #[allow(clippy::too_many_arguments)]
pub fn custom_indent_for_newline( // pub fn custom_indent_for_newline(
language_config: Option<&LanguageConfiguration>, // language_config: Option<&LanguageConfiguration>,
text: RopeSlice, // text: RopeSlice,
line_before: usize, // line_before: usize,
line_before_end_pos: usize, // line_before_end_pos: usize,
) -> Option<String> { // ) -> Option<String> {
if let Some(config) = language_config { // if let Some(config) = language_config {
// TODO: If possible, this would be very cool to be implemented in steel itself. If not, // // TODO: If possible, this would be very cool to be implemented in steel itself. If not,
// a rust native method that is embedded in a dylib that this uses would also be helpful // // a rust native method that is embedded in a dylib that this uses would also be helpful
if config.language_id == "scheme" { // if config.language_id == "scheme" {
log::info!("Implement better scheme indent mode!"); // log::info!("Implement better scheme indent mode!");
let byte_pos = text.char_to_byte(line_before_end_pos); // let byte_pos = text.char_to_byte(line_before_end_pos);
let text_up_to_cursor = text.byte_slice(0..byte_pos); // let text_up_to_cursor = text.byte_slice(0..byte_pos);
let mut cursor = line_before; // let mut cursor = line_before;
let mut depth = 0; // let mut depth = 0;
loop { // loop {
let line = text_up_to_cursor.line(cursor); // let line = text_up_to_cursor.line(cursor);
// We want to ignore comments // // We want to ignore comments
let l = std::borrow::Cow::from(line); // let l = std::borrow::Cow::from(line);
if l.trim_start().starts_with(";") { // if l.trim_start().starts_with(";") {
if cursor == 0 { // if cursor == 0 {
break; // break;
} // }
cursor -= 1; // cursor -= 1;
continue; // continue;
} // }
for (index, char) in line.chars_at(line.len_chars()).reversed().enumerate() { // for (index, char) in line.chars_at(line.len_chars()).reversed().enumerate() {
match char { // // log::info!("Char: {}, Index: {}", char, index);
')' | ']' | '}' => {
depth += 1; // match char {
} // ')' | ']' | '}' => {
'(' | '[' | '{' => { // depth += 1;
if depth == 0 { // }
log::info!( // '(' | '[' | '{' => {
"Found unmatched paren on line, index: {}, {}", // if depth == 0 {
line, // log::info!(
index // "Found unmatched paren on line, index: {}, {}",
); // line,
// index
// Here, then walk FORWARD, parsing the identifiers until there is a thing to line up with, for example: // );
// (define (foo-bar) RET) <-
// ^probably indent to here // // Here, then walk FORWARD, parsing the identifiers until there is a thing to line up with, for example:
// // (define (foo-bar) RET) <-
let offset = line.len_chars() - index; // // ^probably indent to here
let mut char_iter_from_paren = line.chars_at(offset).enumerate(); // let offset = line.len_chars() - index;
let end; // log::info!("Offset: {}", offset);
// Walk until we've found whitespace, and then crunch the whitespace until the start of the next symbol // let mut char_iter_from_paren = line.chars_at(offset).enumerate();
// if there is _no_ symbol after that, we should just default to the default behavior
while let Some((index, char)) = char_iter_from_paren.next() { // let end;
if char.is_whitespace() {
let mut last = index; // // Walk until we've found whitespace, and then crunch the whitespace until the start of the next symbol
// // if there is _no_ symbol after that, we should just default to the default behavior
// This is the end of our range // while let Some((index, char)) = char_iter_from_paren.next() {
end = index; // if char.is_whitespace() {
// let mut last = index;
// If we have multiple parens in a row, match to the start:
// for instance, (cond [(equal? x 10) RET]) // // This is the end of our range
// ^ We want to line up to this // end = index;
//
// To do so, just create an indent that is the width of the offset. // // If we have multiple parens in a row, match to the start:
match line.get_char(offset) { // // for instance, (cond [(equal? x 10) RET])
Some('(' | '[' | '{') => { // // ^ We want to line up to this
return Some(" ".repeat(offset)); // //
} // // To do so, just create an indent that is the width of the offset.
_ => {} // match line.get_char(offset) {
} // Some('(' | '[' | '{') => {
// return Some(" ".repeat(offset));
if line // }
.slice(offset..offset + end) // _ => {}
.as_str() // }
.map(|x| LISP_WORDS.contains(x))
.unwrap_or_default() // if line
{ // .slice(offset..offset + end)
return Some(" ".repeat(offset + 1)); // .as_str()
} // .map(|x| LISP_WORDS.contains(x))
// .unwrap_or_default()
for _ in char_iter_from_paren // {
.take_while(|(_, x)| x.is_whitespace()) // return Some(" ".repeat(offset + 1));
{ // }
last += 1;
} // for _ in char_iter_from_paren
// .take_while(|(_, x)| x.is_whitespace())
// If we have something like (list RET) // {
// We want the result to look like: // last += 1;
// (list // }
// )
// // // If we have something like (list RET)
// So we special case the lack of an additional word after // // We want the result to look like:
// the first symbol // // (list
if line.len_chars() == last + offset + 1 { // // )
if let Some(c) = line.get_char(last + offset) { // //
if c.is_whitespace() { // // So we special case the lack of an additional word after
return Some(" ".repeat(offset + 1)); // // the first symbol
} // if line.len_chars() == last + offset + 1 {
} // if let Some(c) = line.get_char(last + offset) {
} // if c.is_whitespace() {
// return Some(" ".repeat(offset + 1));
return Some(" ".repeat(last + offset + 1)); // }
} // }
} // }
log::info!("Found no symbol after the initial opening symbol"); // log::info!("Last + offset + 1: {}", last + offset + 1);
return Some(" ".repeat(offset + 1)); // return Some(" ".repeat(last + offset + 1));
} // }
// }
depth -= 1;
} // log::info!("Found no symbol after the initial opening symbol");
_ => {}
} // return Some(" ".repeat(offset + 1));
} // }
if cursor == 0 { // depth -= 1;
break; // }
} // _ => {}
// }
cursor -= 1; // }
}
// if cursor == 0 {
// TODO: Implement heuristic for large files so we don't necessarily traverse the entire file backwards to check the matched parens? // break;
return Some("".to_string()); // }
}
} // cursor -= 1;
// }
None
} // // TODO: Implement heuristic for large files so we don't necessarily traverse the entire file backwards to check the matched parens?
// return Some("".to_string());
// }
// }
// None
// }
/// Returns the indentation for a new line. /// Returns the indentation for a new line.
/// This is done either using treesitter, or if that's not available by copying the indentation from the current line /// This is done either using treesitter, or if that's not available by copying the indentation from the current line
@ -935,11 +941,11 @@ pub fn indent_for_newline(
// TODO: @Matt - see if we can shell out to the steel plugin to identify indentation length // TODO: @Matt - see if we can shell out to the steel plugin to identify indentation length
// Something naive for steel could work, use the parser and // Something naive for steel could work, use the parser and
if let Some(indent_level) = // if let Some(indent_level) =
custom_indent_for_newline(language_config, text, line_before, line_before_end_pos) // custom_indent_for_newline(language_config, text, line_before, line_before_end_pos)
{ // {
return indent_level; // return indent_level;
} // }
let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width); let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
indent_style.as_str().repeat(indent_level) indent_style.as_str().repeat(indent_level)

@ -1,9 +1,9 @@
use fuzzy_matcher::FuzzyMatcher; use fuzzy_matcher::FuzzyMatcher;
use helix_core::{ use helix_core::{
extensions::{rope_slice_module, SRopeSlice}, extensions::{rope_module, rope_slice_module, SRopeSlice, SteelRopeSlice},
graphemes, graphemes,
shellwords::Shellwords, shellwords::Shellwords,
Selection, Tendril, Range, Selection, Tendril,
}; };
use helix_view::{ use helix_view::{
document::Mode, document::Mode,
@ -52,6 +52,7 @@ use crate::{
use self::components::SteelDynamicComponent; use self::components::SteelDynamicComponent;
use super::{ use super::{
indent,
insert::{insert_char, insert_string}, insert::{insert_char, insert_string},
plugin::{DylibContainers, ExternalModule}, plugin::{DylibContainers, ExternalModule},
shell_impl, Context, MappableCommand, TYPABLE_COMMAND_LIST, shell_impl, Context, MappableCommand, TYPABLE_COMMAND_LIST,
@ -839,6 +840,11 @@ fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine:
engine.register_fn("hx.context?", |_: &mut Context| true); engine.register_fn("hx.context?", |_: &mut Context| true);
engine.register_fn("log::info!", |message: String| log::info!("{}", message));
engine.register_fn("hx.custom-insert-newline", custom_insert_newline);
engine.register_fn("hx.cx->pos", cx_pos_within_text);
// Load native modules from the directory. Another idea - have a separate dlopen loading system // Load native modules from the directory. Another idea - have a separate dlopen loading system
// in place that does not use the type id, and instead we generate the module after the dylib // in place that does not use the type id, and instead we generate the module after the dylib
// is added. That way functions _must_ have a specific signature, and then we add the integration // is added. That way functions _must_ have a specific signature, and then we add the integration
@ -855,22 +861,24 @@ fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine:
load_keymap_api(&mut engine, KeyMapApi::new()); load_keymap_api(&mut engine, KeyMapApi::new());
let mut rope_slice_module = rope_slice_module(); let mut rope_slice_module = rope_module();
rope_slice_module.register_fn("document->slice", document_to_text);
// Load the ropes + slice module // Load the ropes + slice module
// engine.register_module(rope_slice_module()); // engine.register_module(rope_slice_module());
// rope_slice_module.register_fn("document->slice", document_to_text); // rope_slice_module.register_fn("document->slice", document_to_text);
RegisterFnBorrowed::< // RegisterFnBorrowed::<
_, // _,
steel::steel_vm::register_fn::MarkerWrapper9<( // steel::steel_vm::register_fn::MarkerWrapper9<(
Document, // Document,
Document, // Document,
SRopeSlice<'_>, // SRopeSlice<'_>,
SRopeSlice<'static>, // SRopeSlice<'static>,
)>, // )>,
SRopeSlice, // SRopeSlice,
>::register_fn_borrowed(&mut rope_slice_module, "document->slice", document_to_text); // >::register_fn_borrowed(&mut rope_slice_module, "document->slice", document_to_text);
engine.register_module(rope_slice_module); engine.register_module(rope_slice_module);
@ -1490,8 +1498,8 @@ fn get_document(editor: &mut Editor, doc_id: DocumentId) -> &Document {
editor.documents.get(&doc_id).unwrap() editor.documents.get(&doc_id).unwrap()
} }
fn document_to_text(doc: &Document) -> SRopeSlice<'_> { fn document_to_text(doc: &Document) -> SteelRopeSlice {
SRopeSlice::new(doc.text().slice(..)) SteelRopeSlice::new(doc.text().clone())
} }
fn is_document_in_view(editor: &mut Editor, doc_id: DocumentId) -> Option<helix_view::ViewId> { fn is_document_in_view(editor: &mut Editor, doc_id: DocumentId) -> Option<helix_view::ViewId> {
@ -1746,3 +1754,143 @@ fn set_options(
.send(ConfigEvent::Update(config))?; .send(ConfigEvent::Update(config))?;
Ok(()) Ok(())
} }
pub fn cx_pos_within_text(cx: &mut Context) -> usize {
let (view, doc) = current_ref!(cx.editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id).clone();
let pos = selection.primary().cursor(text);
pos
}
// Special newline...
pub fn custom_insert_newline(cx: &mut Context, indent: String) {
let (view, doc) = current_ref!(cx.editor);
// let rope = doc.text().clone();
let text = doc.text().slice(..);
let contents = doc.text();
let selection = doc.selection(view.id).clone();
let mut ranges = helix_core::SmallVec::with_capacity(selection.len());
// TODO: this is annoying, but we need to do it to properly calculate pos after edits
let mut global_offs = 0;
let mut transaction =
helix_core::Transaction::change_by_selection(contents, &selection, |range| {
let pos = range.cursor(text);
let prev = if pos == 0 {
' '
} else {
contents.char(pos - 1)
};
let curr = contents.get_char(pos).unwrap_or(' ');
let current_line = text.char_to_line(pos);
let line_is_only_whitespace = text
.line(current_line)
.chars()
.all(|char| char.is_ascii_whitespace());
let mut new_text = String::new();
// If the current line is all whitespace, insert a line ending at the beginning of
// the current line. This makes the current line empty and the new line contain the
// indentation of the old line.
let (from, to, local_offs) = if line_is_only_whitespace {
let line_start = text.line_to_char(current_line);
new_text.push_str(doc.line_ending.as_str());
(line_start, line_start, new_text.chars().count())
} else {
// let indent = indent::indent_for_newline(
// doc.language_config(),
// doc.syntax(),
// &doc.indent_style,
// doc.tab_width(),
// text,
// current_line,
// pos,
// current_line,
// );
// let cloned_func = thunk.clone();
// let steel_rope = SteelRopeSlice::new(rope.clone()).into_steelval().unwrap();
// let indent = if let Ok(result) = ENGINE.with(|x| {
// x.borrow_mut().call_function_with_args(
// cloned_func,
// vec![
// steel_rope,
// current_line.into_steelval().unwrap(),
// pos.into_steelval().unwrap(),
// ],
// )
// }) {
// result.as_string().unwrap().to_string()
// } else {
// "".to_string()
// };
// If we are between pairs (such as brackets), we want to
// insert an additional line which is indented one level
// more and place the cursor there
let on_auto_pair = doc
.auto_pairs(cx.editor)
.and_then(|pairs| pairs.get(prev))
.map_or(false, |pair| pair.open == prev && pair.close == curr);
let local_offs = if on_auto_pair {
let inner_indent = indent.clone() + doc.indent_style.as_str();
new_text.reserve_exact(2 + indent.len() + inner_indent.len());
new_text.push_str(doc.line_ending.as_str());
new_text.push_str(&inner_indent);
let local_offs = new_text.chars().count();
new_text.push_str(doc.line_ending.as_str());
new_text.push_str(&indent);
local_offs
} else {
new_text.reserve_exact(1 + indent.len());
new_text.push_str(doc.line_ending.as_str());
new_text.push_str(&indent);
new_text.chars().count()
};
(pos, pos, local_offs)
};
let new_range = if doc.restore_cursor {
// when appending, extend the range by local_offs
Range::new(
range.anchor + global_offs,
range.head + local_offs + global_offs,
)
} else {
// when inserting, slide the range by local_offs
Range::new(
range.anchor + local_offs + global_offs,
range.head + local_offs + global_offs,
)
};
// TODO: range replace or extend
// range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
// can be used with cx.mode to do replace or extend on most changes
ranges.push(new_range);
global_offs += new_text.chars().count();
(from, to, Some(new_text.into()))
});
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
let (view, doc) = current!(cx.editor);
doc.apply(&transaction, view.id);
}

Loading…
Cancel
Save