mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-10 10:35:16 +03:00
Add tab_width and indent_unit config.
This commit is contained in:
parent
698e4ddea4
commit
5e6716c89c
@ -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,
|
||||||
|
@ -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(" "),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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> {
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user