Add tab_width and indent_unit config.

This commit is contained in:
Blaž Hrastnik 2021-03-22 13:47:39 +09:00
parent 698e4ddea4
commit 5e6716c89c
6 changed files with 68 additions and 24 deletions

View File

@ -8,19 +8,17 @@ use crate::{
/// To determine indentation of a newly inserted line, figure out the indentation at the last col /// To determine indentation of a newly inserted line, figure out the indentation at the last col
/// of the previous line. /// of the previous line.
pub const TAB_WIDTH: usize = 4; fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
fn indent_level_for_line(line: RopeSlice) -> usize {
let mut len = 0; let mut len = 0;
for ch in line.chars() { for ch in line.chars() {
match ch { match ch {
'\t' => len += TAB_WIDTH, '\t' => len += tab_width,
' ' => len += 1, ' ' => len += 1,
_ => break, _ => break,
} }
} }
len / TAB_WIDTH len / tab_width
} }
/// Find the highest syntax node at position. /// Find the highest syntax node at position.
@ -162,9 +160,14 @@ fn calculate_indentation(node: Option<Node>, newline: bool) -> usize {
increment as usize increment as usize
} }
fn suggested_indent_for_line(syntax: Option<&Syntax>, text: RopeSlice, line_num: usize) -> usize { fn suggested_indent_for_line(
syntax: Option<&Syntax>,
text: RopeSlice,
line_num: usize,
tab_width: usize,
) -> usize {
let line = text.line(line_num); let line = text.line(line_num);
let current = indent_level_for_line(line); let current = indent_level_for_line(line, tab_width);
if let Some(start) = find_first_non_whitespace_char(text, line_num) { if let Some(start) = find_first_non_whitespace_char(text, line_num) {
return suggested_indent_for_pos(syntax, text, start, false); return suggested_indent_for_pos(syntax, text, start, false);
@ -202,13 +205,14 @@ mod test {
#[test] #[test]
fn test_indent_level() { fn test_indent_level() {
let tab_width = 4;
let line = Rope::from(" fn new"); // 8 spaces let line = Rope::from(" fn new"); // 8 spaces
assert_eq!(indent_level_for_line(line.slice(..)), 2); assert_eq!(indent_level_for_line(line.slice(..), tab_width), 2);
let line = Rope::from("\t\t\tfn new"); // 3 tabs let line = Rope::from("\t\t\tfn new"); // 3 tabs
assert_eq!(indent_level_for_line(line.slice(..)), 3); assert_eq!(indent_level_for_line(line.slice(..), tab_width), 3);
// mixed indentation // mixed indentation
let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
assert_eq!(indent_level_for_line(line.slice(..)), 3); assert_eq!(indent_level_for_line(line.slice(..), tab_width), 3);
} }
#[test] #[test]
@ -295,12 +299,13 @@ where
let highlight_config = language_config.highlight_config(&[]).unwrap(); let highlight_config = language_config.highlight_config(&[]).unwrap();
let syntax = Syntax::new(&doc, highlight_config.clone()); let syntax = Syntax::new(&doc, highlight_config.clone());
let text = doc.slice(..); let text = doc.slice(..);
let tab_width = 4;
for i in 0..doc.len_lines() { for i in 0..doc.len_lines() {
let line = text.line(i); let line = text.line(i);
let indent = indent_level_for_line(line); let indent = indent_level_for_line(line, tab_width);
assert_eq!( assert_eq!(
suggested_indent_for_line(Some(&syntax), text, i), suggested_indent_for_line(Some(&syntax), text, i, tab_width),
indent, indent,
"line {}: {}", "line {}: {}",
i, i,

View File

@ -29,6 +29,7 @@ pub struct LanguageConfiguration {
pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>, pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
// tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583 // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
pub language_server_config: Option<LanguageServerConfiguration>, pub language_server_config: Option<LanguageServerConfiguration>,
pub indent_config: Option<IndentationConfiguration>,
} }
pub struct LanguageServerConfiguration { pub struct LanguageServerConfiguration {
@ -36,6 +37,11 @@ pub struct LanguageServerConfiguration {
pub args: Vec<String>, pub args: Vec<String>,
} }
pub struct IndentationConfiguration {
pub tab_width: usize,
pub indent_unit: String,
}
impl LanguageConfiguration { impl LanguageConfiguration {
pub fn highlight_config(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> { pub fn highlight_config(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
self.highlight_config self.highlight_config
@ -104,6 +110,10 @@ impl Loader {
command: "rust-analyzer".to_string(), command: "rust-analyzer".to_string(),
args: vec![], args: vec![],
}), }),
indent_config: Some(IndentationConfiguration {
tab_width: 4,
indent_unit: String::from(" "),
}),
}, },
LanguageConfiguration { LanguageConfiguration {
scope: "source.toml".to_string(), scope: "source.toml".to_string(),
@ -114,6 +124,10 @@ impl Loader {
path: "../helix-syntax/languages/tree-sitter-toml".into(), path: "../helix-syntax/languages/tree-sitter-toml".into(),
roots: vec![], roots: vec![],
language_server_config: None, language_server_config: None,
indent_config: Some(IndentationConfiguration {
tab_width: 2,
indent_unit: String::from(" "),
}),
}, },
]; ];

View File

@ -1,6 +1,5 @@
use helix_core::{ use helix_core::{
comment, coords_at_pos, graphemes, comment, coords_at_pos, graphemes,
indent::TAB_WIDTH,
movement::{self, Direction}, movement::{self, Direction},
object, pos_at_coords, object, pos_at_coords,
regex::{self, Regex}, regex::{self, Regex},
@ -835,7 +834,7 @@ pub fn open_below(cx: &mut Context) {
// TODO: share logic with insert_newline for indentation // TODO: share logic with insert_newline for indentation
let indent_level = let indent_level =
helix_core::indent::suggested_indent_for_pos(doc.syntax(), text, index, true); helix_core::indent::suggested_indent_for_pos(doc.syntax(), text, index, true);
let indent = " ".repeat(TAB_WIDTH).repeat(indent_level); let indent = doc.indent_unit().repeat(indent_level);
let mut text = String::with_capacity(1 + indent.len()); let mut text = String::with_capacity(1 + indent.len());
text.push('\n'); text.push('\n');
text.push_str(&indent); text.push_str(&indent);
@ -1035,8 +1034,13 @@ pub mod insert {
} }
pub fn insert_tab(cx: &mut Context) { pub fn insert_tab(cx: &mut Context) {
// TODO: tab should insert either \t or indent width spaces let doc = cx.doc();
insert_char(cx, '\t'); // TODO: round out to nearest indentation level (for example a line with 3 spaces should
// indent by one to reach 4 spaces).
let indent = Tendril::from(doc.indent_unit());
let transaction = Transaction::insert(doc.text(), doc.selection(), indent);
doc.apply(&transaction);
} }
pub fn insert_newline(cx: &mut Context) { pub fn insert_newline(cx: &mut Context) {
@ -1045,7 +1049,7 @@ pub mod insert {
let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| { let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
let indent_level = let indent_level =
helix_core::indent::suggested_indent_for_pos(doc.syntax(), text, range.head, true); helix_core::indent::suggested_indent_for_pos(doc.syntax(), text, range.head, true);
let indent = " ".repeat(TAB_WIDTH).repeat(indent_level); let indent = doc.indent_unit().repeat(indent_level);
let mut text = String::with_capacity(1 + indent.len()); let mut text = String::with_capacity(1 + indent.len());
text.push('\n'); text.push('\n');
text.push_str(&indent); text.push_str(&indent);
@ -1185,7 +1189,7 @@ pub fn indent(cx: &mut Context) {
let lines = get_lines(doc); let lines = get_lines(doc);
// Indent by one level // Indent by one level
let indent = Tendril::from(" ".repeat(TAB_WIDTH)); let indent = Tendril::from(doc.indent_unit());
let transaction = Transaction::change( let transaction = Transaction::change(
doc.text(), doc.text(),
@ -1202,6 +1206,7 @@ pub fn unindent(cx: &mut Context) {
let doc = cx.doc(); let doc = cx.doc();
let lines = get_lines(doc); let lines = get_lines(doc);
let mut changes = Vec::with_capacity(lines.len()); let mut changes = Vec::with_capacity(lines.len());
let tab_width = doc.tab_width();
for line_idx in lines { for line_idx in lines {
let line = doc.text().line(line_idx); let line = doc.text().line(line_idx);
@ -1210,11 +1215,11 @@ pub fn unindent(cx: &mut Context) {
for ch in line.chars() { for ch in line.chars() {
match ch { match ch {
' ' => width += 1, ' ' => width += 1,
'\t' => width = (width / TAB_WIDTH + 1) * TAB_WIDTH, '\t' => width = (width / tab_width + 1) * tab_width,
_ => break, _ => break,
} }
if width >= TAB_WIDTH { if width >= tab_width {
break; break;
} }
} }

View File

@ -6,7 +6,6 @@ use crate::{
}; };
use helix_core::{ use helix_core::{
indent::TAB_WIDTH,
syntax::{self, HighlightEvent}, syntax::{self, HighlightEvent},
Position, Range, Position, Range,
}; };
@ -106,6 +105,7 @@ impl EditorView {
let mut spans = Vec::new(); let mut spans = Vec::new();
let mut visual_x = 0; let mut visual_x = 0;
let mut line = 0u16; let mut line = 0u16;
let tab_width = view.doc.tab_width();
'outer: for event in highlights { 'outer: for event in highlights {
match event.unwrap() { match event.unwrap() {
@ -152,7 +152,7 @@ impl EditorView {
break 'outer; break 'outer;
} }
} else if grapheme == "\t" { } else if grapheme == "\t" {
visual_x += (TAB_WIDTH as u16); visual_x += (tab_width as u16);
} else { } else {
if visual_x >= viewport.width { if visual_x >= viewport.width {
// if we're offscreen just keep going until we hit a new line // if we're offscreen just keep going until we hit a new line

View File

@ -296,6 +296,26 @@ impl Document {
self.syntax.as_ref() self.syntax.as_ref()
} }
/// Tab size in columns.
pub fn tab_width(&self) -> usize {
self.language
.as_ref()
.and_then(|config| config.indent_config.as_ref())
.map(|config| config.tab_width)
.unwrap_or(4) // fallback to 4 columns
}
/// Returns a string containing a single level of indentation.
pub fn indent_unit(&self) -> &str {
self.language
.as_ref()
.and_then(|config| config.indent_config.as_ref())
.map(|config| config.indent_unit.as_str())
.unwrap_or(" ") // fallback to 2 spaces
// " ".repeat(TAB_WIDTH)
}
#[inline] #[inline]
/// File path on disk. /// File path on disk.
pub fn path(&self) -> Option<&PathBuf> { pub fn path(&self) -> Option<&PathBuf> {

View File

@ -5,7 +5,6 @@ use std::borrow::Cow;
use crate::Document; use crate::Document;
use helix_core::{ use helix_core::{
graphemes::{grapheme_width, RopeGraphemes}, graphemes::{grapheme_width, RopeGraphemes},
indent::TAB_WIDTH,
Position, RopeSlice, Position, RopeSlice,
}; };
use slotmap::DefaultKey as Key; use slotmap::DefaultKey as Key;
@ -72,10 +71,11 @@ impl View {
let line_start = text.line_to_char(line); let line_start = text.line_to_char(line);
let line_slice = text.slice(line_start..pos); let line_slice = text.slice(line_start..pos);
let mut col = 0; let mut col = 0;
let tab_width = self.doc.tab_width();
for grapheme in RopeGraphemes::new(line_slice) { for grapheme in RopeGraphemes::new(line_slice) {
if grapheme == "\t" { if grapheme == "\t" {
col += TAB_WIDTH; col += tab_width;
} else { } else {
let grapheme = Cow::from(grapheme); let grapheme = Cow::from(grapheme);
col += grapheme_width(&grapheme); col += grapheme_width(&grapheme);