@@ -9,8 +9,8 @@ use std::str::FromStr;
9use std::sync::Arc;
9use std::sync::Arc;
11use helix_core::{
11use helix_core::{
12− chars::{char_is_line_ending, char_is_whitespace},
13 history::History,
12 history::History,
13+ indent::{auto_detect_indent_style, IndentStyle},
14 line_ending::auto_detect_line_ending,
14 line_ending::auto_detect_line_ending,
15 syntax::{self, LanguageConfiguration},
15 syntax::{self, LanguageConfiguration},
16 ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
16 ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
@@ -63,12 +63,6 @@ impl<'de> Deserialize<'de> for Mode {
66−#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
67−pub enum IndentStyle {
72pub struct Document {
66pub struct Document {
73 pub(crate) id: DocumentId,
67 pub(crate) id: DocumentId,
74 text: Rope,
68 text: Rope,
@@ -460,9 +454,7 @@ impl Document {
460 doc.detect_language(theme, loader);
454 doc.detect_language(theme, loader);
463− // Detect indentation style and line ending.
457+ doc.detect_indent_and_line_ending();
464− doc.detect_indent_style();
465− doc.line_ending = auto_detect_line_ending(&doc.text).unwrap_or(DEFAULT_LINE_ENDING);
@@ -580,6 +572,18 @@ impl Document {
575+ pub fn detect_indent_and_line_ending(&mut self) {
576+ self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
577+ IndentStyle::from_str(
580+ .and_then(|config| config.indent.as_ref())
581+ .map_or(" ", |config| config.unit.as_str()), // Fallback to 2 spaces.
584+ self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING);
583 /// Reload the document from its path.
587 /// Reload the document from its path.
584 pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> {
588 pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> {
585 let encoding = &self.encoding;
589 let encoding = &self.encoding;
@@ -598,9 +602,7 @@ impl Document {
598 self.append_changes_to_history(view_id);
602 self.append_changes_to_history(view_id);
599 self.reset_modified();
603 self.reset_modified();
601− // Detect indentation style and line ending.
605+ self.detect_indent_and_line_ending();
602− self.detect_indent_style();
603− self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING);
@@ -619,132 +621,6 @@ impl Document {
619 self.encoding
621 self.encoding
622− fn detect_indent_style(&mut self) {
623− // Build a histogram of the indentation *increases* between
624− // subsequent lines, ignoring lines that are all whitespace.
626− // Index 0 is for tabs, the rest are 1-8 spaces.
627− let histogram: [usize; 9] = {
628− let mut histogram = [0; 9];
629− let mut prev_line_is_tabs = false;
630− let mut prev_line_leading_count = 0usize;
632− // Loop through the lines, checking for and recording indentation
633− // increases as we go.
634− 'outer: for line in self.text.lines().take(1000) {
635− let mut c_iter = line.chars();
637− // Is first character a tab or space?
638− let is_tabs = match c_iter.next() {
642− // Ignore blank lines.
643− Some(c) if char_is_line_ending(c) => continue,
646− prev_line_is_tabs = false;
647− prev_line_leading_count = 0;
652− // Count the line's total leading tab/space characters.
653− let mut leading_count = 1;
654− let mut count_is_done = false;
657− '\t' if is_tabs && !count_is_done => leading_count += 1,
658− ' ' if !is_tabs && !count_is_done => leading_count += 1,
660− // We stop counting if we hit whitespace that doesn't
661− // qualify as indent or doesn't match the leading
662− // whitespace, but we don't exit the loop yet because
663− // we still want to determine if the line is blank.
664− c if char_is_whitespace(c) => count_is_done = true,
666− // Ignore blank lines.
667− c if char_is_line_ending(c) => continue 'outer,
672− // Bound the worst-case execution time for weird text files.
673− if leading_count > 256 {
678− // If there was an increase in indentation over the previous
679− // line, update the histogram with that increase.
680− if (prev_line_is_tabs == is_tabs || prev_line_leading_count == 0)
681− && prev_line_leading_count < leading_count
686− let amount = leading_count - prev_line_leading_count;
688− histogram[amount] += 1;
693− // Store this line's leading whitespace info for use with
695− prev_line_is_tabs = is_tabs;
696− prev_line_leading_count = leading_count;
699− // Give more weight to tabs, because their presence is a very
700− // strong indicator.
706− // Find the most frequent indent, its frequency, and the frequency of
707− // the next-most frequent indent.
708− let indent = histogram
711− .max_by_key(|kv| kv.1)
714− let indent_freq = histogram[indent];
715− let indent_freq_2 = *histogram
718− .filter(|kv| kv.0 != indent)
723− // Use the auto-detected result if we're confident enough in its
724− // accuracy, based on some heuristics. Otherwise fall back to
725− // the language-based setting.
726− if indent_freq >= 1 && (indent_freq_2 as f64 / indent_freq as f64) < 0.66 {
727− // Use the auto-detected setting.
728− self.indent_style = match indent {
729− 0 => IndentStyle::Tabs,
730− _ => IndentStyle::Spaces(indent as u8),
733− // Fall back to language-based setting.
737− .and_then(|config| config.indent.as_ref())
738− .map_or(" ", |config| config.unit.as_str()); // fallback to 2 spaces
740− self.indent_style = if indent.starts_with(' ') {
741− IndentStyle::Spaces(indent.len() as u8)
748 pub fn set_path(&mut self, path: &Path) -> Result<(), std::io::Error> {
624 pub fn set_path(&mut self, path: &Path) -> Result<(), std::io::Error> {
749 let path = canonicalize_path(path)?;
625 let path = canonicalize_path(path)?;
@@ -1002,21 +878,7 @@ impl Document {
1002 /// TODO: we might not need this function anymore, since the information
878 /// TODO: we might not need this function anymore, since the information
1003 /// is conveniently available in `Document::indent_style` now.
879 /// is conveniently available in `Document::indent_style` now.
1004 pub fn indent_unit(&self) -> &'static str {
880 pub fn indent_unit(&self) -> &'static str {
1005− match self.indent_style {
881+ self.indent_style.as_str()
1006− IndentStyle::Tabs => "\t",
1007− IndentStyle::Spaces(1) => " ",
1008− IndentStyle::Spaces(2) => " ",
1009− IndentStyle::Spaces(3) => " ",
1010− IndentStyle::Spaces(4) => " ",
1011− IndentStyle::Spaces(5) => " ",
1012− IndentStyle::Spaces(6) => " ",
1013− IndentStyle::Spaces(7) => " ",
1014− IndentStyle::Spaces(8) => " ",
1016− // Unsupported indentation style. This should never happen,
1017− // but just in case fall back to two spaces.
1022 #[inline]
884 #[inline]