on master help sign in
Add tab_width and indent_unit config.
commit5e6716c89c0909bc374e26bedbba703427f9aa26 authorBlaž Hrastnik <blaz@mxxn.io> dateMon 22 Mar 2021, 13:47 (6 years ago) parent698e4dd
6 files changed+68−24
helix-core/src/indent.rs+17−12 helix-core/src/syntax.rs+14 helix-term/src/commands.rs+13−8 helix-term/src/ui/editor.rs+2−2 helix-view/src/document.rs+20 helix-view/src/view.rs+2−2
helix-core/src/indent.rs+17−12
@@ -8,19 +8,17 @@ use crate::{
88/// To determine indentation of a newly inserted line, figure out the indentation at the last col
99/// of the previous line.
1010
11−pub const TAB_WIDTH: usize = 4;
12−
13−fn indent_level_for_line(line: RopeSlice) -> usize {
11+fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
1412 let mut len = 0;
1513 for ch in line.chars() {
1614 match ch {
17− '\t' => len += TAB_WIDTH,
15+ '\t' => len += tab_width,
1816 ' ' => len += 1,
1917 _ => break,
2018 }
2119 }
2220
23− len / TAB_WIDTH
21+ len / tab_width
2422}
2523
2624/// Find the highest syntax node at position.
@@ -162,9 +160,14 @@ fn calculate_indentation(node: Option<Node>, newline: bool) -> usize {
162160 increment as usize
163161}
164162
165−fn suggested_indent_for_line(syntax: Option<&Syntax>, text: RopeSlice, line_num: usize) -> usize {
163+fn suggested_indent_for_line(
164+ syntax: Option<&Syntax>,
165+ text: RopeSlice,
166+ line_num: usize,
167+ tab_width: usize,
168+) -> usize {
166169 let line = text.line(line_num);
167− let current = indent_level_for_line(line);
170+ let current = indent_level_for_line(line, tab_width);
168171
169172 if let Some(start) = find_first_non_whitespace_char(text, line_num) {
170173 return suggested_indent_for_pos(syntax, text, start, false);
@@ -202,13 +205,14 @@ mod test {
202205
203206 #[test]
204207 fn test_indent_level() {
208+ let tab_width = 4;
205209 let line = Rope::from(" fn new"); // 8 spaces
206− assert_eq!(indent_level_for_line(line.slice(..)), 2);
210+ assert_eq!(indent_level_for_line(line.slice(..), tab_width), 2);
207211 let line = Rope::from("\t\t\tfn new"); // 3 tabs
208− assert_eq!(indent_level_for_line(line.slice(..)), 3);
212+ assert_eq!(indent_level_for_line(line.slice(..), tab_width), 3);
209213 // mixed indentation
210214 let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
211− assert_eq!(indent_level_for_line(line.slice(..)), 3);
215+ assert_eq!(indent_level_for_line(line.slice(..), tab_width), 3);
212216 }
213217
214218 #[test]
@@ -295,12 +299,13 @@ where
295299 let highlight_config = language_config.highlight_config(&[]).unwrap();
296300 let syntax = Syntax::new(&doc, highlight_config.clone());
297301 let text = doc.slice(..);
302+ let tab_width = 4;
298303
299304 for i in 0..doc.len_lines() {
300305 let line = text.line(i);
301− let indent = indent_level_for_line(line);
306+ let indent = indent_level_for_line(line, tab_width);
302307 assert_eq!(
303− suggested_indent_for_line(Some(&syntax), text, i),
308+ suggested_indent_for_line(Some(&syntax), text, i, tab_width),
304309 indent,
305310 "line {}: {}",
306311 i,
helix-core/src/syntax.rs+14
@@ -29,6 +29,7 @@ pub struct LanguageConfiguration {
2929 pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
3030 // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
3131 pub language_server_config: Option<LanguageServerConfiguration>,
32+ pub indent_config: Option<IndentationConfiguration>,
3233}
3334
3435pub struct LanguageServerConfiguration {
@@ -36,6 +37,11 @@ pub struct LanguageServerConfiguration {
3637 pub args: Vec<String>,
3738}
3839
40+pub struct IndentationConfiguration {
41+ pub tab_width: usize,
42+ pub indent_unit: String,
43+}
44+
3945impl LanguageConfiguration {
4046 pub fn highlight_config(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
4147 self.highlight_config
@@ -104,6 +110,10 @@ impl Loader {
104110 command: "rust-analyzer".to_string(),
105111 args: vec![],
106112 }),
113+ indent_config: Some(IndentationConfiguration {
114+ tab_width: 4,
115+ indent_unit: String::from(" "),
116+ }),
107117 },
108118 LanguageConfiguration {
109119 scope: "source.toml".to_string(),
@@ -114,6 +124,10 @@ impl Loader {
114124 path: "../helix-syntax/languages/tree-sitter-toml".into(),
115125 roots: vec![],
116126 language_server_config: None,
127+ indent_config: Some(IndentationConfiguration {
128+ tab_width: 2,
129+ indent_unit: String::from(" "),
130+ }),
117131 },
118132 ];
119133
helix-term/src/commands.rs+13−8
@@ -1,6 +1,5 @@
11use helix_core::{
22 comment, coords_at_pos, graphemes,
3− indent::TAB_WIDTH,
43 movement::{self, Direction},
54 object, pos_at_coords,
65 regex::{self, Regex},
@@ -835,7 +834,7 @@ pub fn open_below(cx: &mut Context) {
835834 // TODO: share logic with insert_newline for indentation
836835 let indent_level =
837836 helix_core::indent::suggested_indent_for_pos(doc.syntax(), text, index, true);
838− let indent = " ".repeat(TAB_WIDTH).repeat(indent_level);
837+ let indent = doc.indent_unit().repeat(indent_level);
839838 let mut text = String::with_capacity(1 + indent.len());
840839 text.push('\n');
841840 text.push_str(&indent);
@@ -1035,8 +1034,13 @@ pub mod insert {
10351034 }
10361035
10371036 pub fn insert_tab(cx: &mut Context) {
1038− // TODO: tab should insert either \t or indent width spaces
1039− insert_char(cx, '\t');
1037+ let doc = cx.doc();
1038+ // TODO: round out to nearest indentation level (for example a line with 3 spaces should
1039+ // indent by one to reach 4 spaces).
1040+
1041+ let indent = Tendril::from(doc.indent_unit());
1042+ let transaction = Transaction::insert(doc.text(), doc.selection(), indent);
1043+ doc.apply(&transaction);
10401044 }
10411045
10421046 pub fn insert_newline(cx: &mut Context) {
@@ -1045,7 +1049,7 @@ pub mod insert {
10451049 let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
10461050 let indent_level =
10471051 helix_core::indent::suggested_indent_for_pos(doc.syntax(), text, range.head, true);
1048− let indent = " ".repeat(TAB_WIDTH).repeat(indent_level);
1052+ let indent = doc.indent_unit().repeat(indent_level);
10491053 let mut text = String::with_capacity(1 + indent.len());
10501054 text.push('\n');
10511055 text.push_str(&indent);
@@ -1185,7 +1189,7 @@ pub fn indent(cx: &mut Context) {
11851189 let lines = get_lines(doc);
11861190
11871191 // Indent by one level
1188− let indent = Tendril::from(" ".repeat(TAB_WIDTH));
1192+ let indent = Tendril::from(doc.indent_unit());
11891193
11901194 let transaction = Transaction::change(
11911195 doc.text(),
@@ -1202,6 +1206,7 @@ pub fn unindent(cx: &mut Context) {
12021206 let doc = cx.doc();
12031207 let lines = get_lines(doc);
12041208 let mut changes = Vec::with_capacity(lines.len());
1209+ let tab_width = doc.tab_width();
12051210
12061211 for line_idx in lines {
12071212 let line = doc.text().line(line_idx);
@@ -1210,11 +1215,11 @@ pub fn unindent(cx: &mut Context) {
12101215 for ch in line.chars() {
12111216 match ch {
12121217 ' ' => width += 1,
1213− '\t' => width = (width / TAB_WIDTH + 1) * TAB_WIDTH,
1218+ '\t' => width = (width / tab_width + 1) * tab_width,
12141219 _ => break,
12151220 }
12161221
1217− if width >= TAB_WIDTH {
1222+ if width >= tab_width {
12181223 break;
12191224 }
12201225 }
helix-term/src/ui/editor.rs+2−2
@@ -6,7 +6,6 @@ use crate::{
66};
77
88use helix_core::{
9− indent::TAB_WIDTH,
109 syntax::{self, HighlightEvent},
1110 Position, Range,
1211};
@@ -106,6 +105,7 @@ impl EditorView {
106105 let mut spans = Vec::new();
107106 let mut visual_x = 0;
108107 let mut line = 0u16;
108+ let tab_width = view.doc.tab_width();
109109
110110 'outer: for event in highlights {
111111 match event.unwrap() {
@@ -152,7 +152,7 @@ impl EditorView {
152152 break 'outer;
153153 }
154154 } else if grapheme == "\t" {
155− visual_x += (TAB_WIDTH as u16);
155+ visual_x += (tab_width as u16);
156156 } else {
157157 if visual_x >= viewport.width {
158158 // if we're offscreen just keep going until we hit a new line
helix-view/src/document.rs+20
@@ -296,6 +296,26 @@ impl Document {
296296 self.syntax.as_ref()
297297 }
298298
299+ /// Tab size in columns.
300+ pub fn tab_width(&self) -> usize {
301+ self.language
302+ .as_ref()
303+ .and_then(|config| config.indent_config.as_ref())
304+ .map(|config| config.tab_width)
305+ .unwrap_or(4) // fallback to 4 columns
306+ }
307+
308+ /// Returns a string containing a single level of indentation.
309+ pub fn indent_unit(&self) -> &str {
310+ self.language
311+ .as_ref()
312+ .and_then(|config| config.indent_config.as_ref())
313+ .map(|config| config.indent_unit.as_str())
314+ .unwrap_or(" ") // fallback to 2 spaces
315+
316+ // " ".repeat(TAB_WIDTH)
317+ }
318+
299319 #[inline]
300320 /// File path on disk.
301321 pub fn path(&self) -> Option<&PathBuf> {
helix-view/src/view.rs+2−2
@@ -5,7 +5,6 @@ use std::borrow::Cow;
55use crate::Document;
66use helix_core::{
77 graphemes::{grapheme_width, RopeGraphemes},
8− indent::TAB_WIDTH,
98 Position, RopeSlice,
109};
1110use slotmap::DefaultKey as Key;
@@ -72,10 +71,11 @@ impl View {
7271 let line_start = text.line_to_char(line);
7372 let line_slice = text.slice(line_start..pos);
7473 let mut col = 0;
74+ let tab_width = self.doc.tab_width();
7575
7676 for grapheme in RopeGraphemes::new(line_slice) {
7777 if grapheme == "\t" {
78− col += TAB_WIDTH;
78+ col += tab_width;
7979 } else {
8080 let grapheme = Cow::from(grapheme);
8181 col += grapheme_width(&grapheme);