Add line_ending setting to control line-ending normalization (#54356)

Closes #49581

Adds a `line_ending` language setting that controls how line endings are
handled for new files and during format/save:

- `detect` (default) — detects existing line endings; new files use the
platform default
- `prefer_lf` / `prefer_crlf` — sets LF or CRLF for new files and files
with no existing convention, while preserving existing files
- `enforce_lf` / `enforce_crlf` — normalizes all line endings to LF or
CRLF on every format/save

The setting can be configured globally, per-language, or via
`.editorconfig`'s `end_of_line` property (which maps to `enforce_lf` /
`enforce_crlf`).

Release Notes:

- Added `line_ending` setting to control how line endings are handled
for new files and normalized on save.
- Added support for `.editorconfig` `end_of_line` property to enforce
line endings.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
meloceneandSmit Barmase authored and GitHub committed 2026-04-22 22:21:26 +05:30
1 parent c97442029a
commit 2a6ee04013
10 files changed
+518 -13

No files matched your search

+18
View File
@@ -1360,6 +1360,24 @@
// Removes any lines containing only whitespace at the end of the file and
// ensures just one newline at the end.
"ensure_final_newline_on_save": true,
// How line endings should be handled for new files and during format and save.
// This setting can take five values:
//
// 1. Detect existing line endings and otherwise use the platform default
// (`lf` on Unix, `crlf` on Windows):
// "line_ending": "detect"
// 2. Prefer LF (`\n`) for new files and files with no existing line ending:
// "line_ending": "prefer_lf"
// 3. Prefer CRLF (`\r\n`) for new files and files with no existing line ending:
// "line_ending": "prefer_crlf"
// 4. Enforce LF (`\n`) during format and save:
// "line_ending": "enforce_lf"
// 5. Enforce CRLF (`\r\n`) during format and save:
// "line_ending": "enforce_crlf"
//
// The EditorConfig `end_of_line` property overrides this setting and behaves
// like `enforce_lf` or `enforce_crlf`.
"line_ending": "detect",
// Whether or not to perform a buffer format before saving: [on, off]
// Keep in mind, if the autosave with delay is enabled, format_on_save will be ignored
"format_on_save": "on",
+15 -3
View File
@@ -6,7 +6,9 @@ use crate::{
use collections::{FxHashMap, HashMap, HashSet};
use ec4rs::{
Properties as EditorconfigProperties,
property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs},
property::{
EndOfLine, FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs,
},
};
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::{App, Modifiers, SharedString};
@@ -16,8 +18,8 @@ use settings::{DocumentFoldingRanges, DocumentSymbols, IntoGpui, SemanticTokens}
pub use settings::{
AutoIndentMode, CompletionSettingsContent, EditPredictionPromptFormat, EditPredictionProvider,
EditPredictionsMode, FormatOnSave, Formatter, FormatterList, InlayHintKind,
LanguageSettingsContent, LspInsertMode, RewrapBehavior, ShowWhitespaceSetting, SoftWrap,
WordsCompletionMode,
LanguageSettingsContent, LineEndingSetting, LspInsertMode, RewrapBehavior,
ShowWhitespaceSetting, SoftWrap, WordsCompletionMode,
};
use settings::{RegisterSetting, Settings, SettingsLocation, SettingsStore, merge_from::MergeFrom};
use shellexpand;
@@ -82,6 +84,9 @@ pub struct LanguageSettings {
/// Whether or not to ensure there's a single newline at the end of a buffer
/// when saving it.
pub ensure_final_newline_on_save: bool,
/// How line endings are initialized for new files and normalized during
/// format and save.
pub line_ending: LineEndingSetting,
/// How to perform a buffer format.
pub formatter: settings::FormatterList,
/// Zed's Prettier integration settings.
@@ -637,6 +642,11 @@ fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigPr
TrimTrailingWs::Value(b) => b,
})
.ok();
let line_ending = cfg.get::<EndOfLine>().ok().and_then(|v| match v {
EndOfLine::Lf => Some(LineEndingSetting::EnforceLf),
EndOfLine::CrLf => Some(LineEndingSetting::EnforceCrlf),
EndOfLine::Cr => None,
});
settings
.preferred_line_length
@@ -649,6 +659,7 @@ fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigPr
settings
.ensure_final_newline_on_save
.merge_from_option(ensure_final_newline_on_save.as_ref());
settings.line_ending.merge_from_option(line_ending.as_ref());
}
impl settings::Settings for AllLanguageSettings {
@@ -682,6 +693,7 @@ impl settings::Settings for AllLanguageSettings {
.remove_trailing_whitespace_on_save
.unwrap(),
ensure_final_newline_on_save: settings.ensure_final_newline_on_save.unwrap(),
line_ending: settings.line_ending.unwrap(),
formatter: settings.formatter.unwrap(),
prettier: PrettierSettings {
allowed: prettier.allowed.unwrap(),
+35 -7
View File
@@ -11,7 +11,8 @@ use gpui::{
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
};
use language::{
Buffer, BufferEvent, Capability, DiskState, File as _, Language, Operation,
Buffer, BufferEvent, Capability, DiskState, File as _, Language, LineEnding, Operation,
language_settings::{AllLanguageSettings, LineEndingSetting},
proto::{
deserialize_line_ending, deserialize_version, serialize_line_ending, serialize_version,
split_operations,
@@ -663,7 +664,7 @@ impl LocalBufferStore {
Err(error) if is_not_found_error(&error) => cx.new(|cx| {
let buffer_id = BufferId::from(cx.entity_id().as_non_zero_u64());
let text_buffer = text::Buffer::new(ReplicaId::LOCAL, buffer_id, "");
Buffer::build(
let mut buffer = Buffer::build(
text_buffer,
Some(Arc::new(File {
worktree,
@@ -674,7 +675,9 @@ impl LocalBufferStore {
is_private: false,
})),
Capability::ReadWrite,
)
);
apply_initial_line_ending(&mut buffer, cx);
buffer
}),
Err(e) => return Err(e),
};
@@ -724,8 +727,10 @@ impl LocalBufferStore {
) -> Task<Result<Entity<Buffer>>> {
cx.spawn(async move |buffer_store, cx| {
let buffer = cx.new(|cx| {
Buffer::local("", cx)
.with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
let mut buffer = Buffer::local("", cx)
.with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx);
apply_initial_line_ending(&mut buffer, cx);
buffer
});
buffer_store.update(cx, |buffer_store, cx| {
buffer_store.add_buffer(buffer.clone(), cx).log_err();
@@ -1628,8 +1633,10 @@ impl BufferStore {
cx: &mut Context<Self>,
) -> Entity<Buffer> {
let buffer = cx.new(|cx| {
Buffer::local(text, cx)
.with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
let mut buffer = Buffer::local(text, cx)
.with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx);
apply_initial_line_ending(&mut buffer, cx);
buffer
});
self.add_buffer(buffer.clone(), cx).log_err();
@@ -1799,3 +1806,24 @@ fn is_not_found_error(error: &anyhow::Error) -> bool {
.downcast_ref::<io::Error>()
.is_some_and(|err| err.kind() == io::ErrorKind::NotFound)
}
fn apply_initial_line_ending(buffer: &mut Buffer, cx: &mut Context<Buffer>) {
// Only applies for empty rope or a single line with no trailing newline.
if buffer.max_point().row > 0 {
return;
}
let location = buffer.file().map(|file| settings::SettingsLocation {
worktree_id: file.worktree_id(cx),
path: file.path().as_ref(),
});
let language = buffer.language().map(|l| l.name());
let settings = AllLanguageSettings::get(location, cx).language(location, language.as_ref(), cx);
let desired = match settings.line_ending {
LineEndingSetting::Detect => return,
LineEndingSetting::PreferLf | LineEndingSetting::EnforceLf => LineEnding::Unix,
LineEndingSetting::PreferCrlf | LineEndingSetting::EnforceCrlf => LineEnding::Windows,
};
if buffer.line_ending() != desired {
buffer.set_line_ending(desired, cx);
}
}
+29 -1
View File
@@ -77,7 +77,8 @@ use language::{
OffsetUtf16, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToOffsetUtf16, ToPointUtf16,
Toolchain, Transaction, Unclipped,
language_settings::{
AllLanguageSettings, FormatOnSave, Formatter, LanguageSettings, all_language_settings,
AllLanguageSettings, FormatOnSave, Formatter, LanguageSettings, LineEndingSetting,
all_language_settings,
},
modeline, point_to_lsp,
proto::{
@@ -1602,6 +1603,9 @@ impl LocalLspStore {
(adapters_and_servers, settings, request_timeout)
})
})?;
let had_existing_line_endings = buffer
.handle
.read_with(cx, |buffer, _| buffer.max_point().row > 0);
// handle whitespace formatting
if settings.remove_trailing_whitespace_on_save {
@@ -1622,6 +1626,30 @@ impl LocalLspStore {
})?;
}
let line_ending_policy = match settings.line_ending {
LineEndingSetting::Detect => None,
LineEndingSetting::PreferLf => Some((LineEnding::Unix, true)),
LineEndingSetting::PreferCrlf => Some((LineEnding::Windows, true)),
LineEndingSetting::EnforceLf => Some((LineEnding::Unix, false)),
LineEndingSetting::EnforceCrlf => Some((LineEnding::Windows, false)),
};
if let Some((desired_line_ending, preserve_existing)) = line_ending_policy {
buffer.handle.update(cx, |buffer, cx| {
if buffer.line_ending() == desired_line_ending {
return;
}
if preserve_existing && had_existing_line_endings {
zlog::trace!(
logger => "preserving existing line endings ({}) on save",
buffer.line_ending().label()
);
return;
}
zlog::trace!(logger => "normalizing line endings to {}", desired_line_ending.label());
buffer.set_line_ending(desired_line_ending, cx);
});
}
// Formatter for `code_actions_on_format` that runs before
// the rest of the formatters
let mut code_actions_on_format_formatters = None;
@@ -46,7 +46,9 @@ use language::{
LanguageConfig, LanguageMatcher, LanguageName, LineEnding, ManifestName, ManifestProvider,
ManifestQuery, OffsetRangeExt, Point, ToPoint, Toolchain, ToolchainList, ToolchainLister,
ToolchainMetadata,
language_settings::{Formatter, FormatterList, LanguageSettings, LanguageSettingsContent},
language_settings::{
Formatter, FormatterList, LanguageSettings, LanguageSettingsContent, LineEndingSetting,
},
markdown_lang, rust_lang, tree_sitter_typescript,
};
use lsp::{
@@ -318,6 +320,7 @@ async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) {
assert_eq!(settings_a.hard_tabs, true);
assert_eq!(settings_a.ensure_final_newline_on_save, true);
assert_eq!(settings_a.remove_trailing_whitespace_on_save, true);
assert_eq!(settings_a.line_ending, LineEndingSetting::EnforceLf);
assert_eq!(settings_a.preferred_line_length, 120);
// .editorconfig in b/ overrides .editorconfig in root
@@ -6420,6 +6423,289 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
);
}
#[gpui::test]
async fn test_line_ending_user_settings_on_format(cx: &mut gpui::TestAppContext) {
init_test(cx);
let cases = [
(
"default",
None,
[
("crlf_file.rs", LineEnding::Windows),
("lf_file.rs", LineEnding::Unix),
("no_newline.rs", LineEnding::default()),
],
),
(
"detect",
Some(LineEndingSetting::Detect),
[
("crlf_file.rs", LineEnding::Windows),
("lf_file.rs", LineEnding::Unix),
("no_newline.rs", LineEnding::default()),
],
),
(
"prefer_lf",
Some(LineEndingSetting::PreferLf),
[
("crlf_file.rs", LineEnding::Windows),
("lf_file.rs", LineEnding::Unix),
("no_newline.rs", LineEnding::Unix),
],
),
(
"prefer_crlf",
Some(LineEndingSetting::PreferCrlf),
[
("crlf_file.rs", LineEnding::Windows),
("lf_file.rs", LineEnding::Unix),
("no_newline.rs", LineEnding::Windows),
],
),
(
"enforce_lf",
Some(LineEndingSetting::EnforceLf),
[
("crlf_file.rs", LineEnding::Unix),
("lf_file.rs", LineEnding::Unix),
("no_newline.rs", LineEnding::Unix),
],
),
(
"enforce_crlf",
Some(LineEndingSetting::EnforceCrlf),
[
("crlf_file.rs", LineEnding::Windows),
("lf_file.rs", LineEnding::Windows),
("no_newline.rs", LineEnding::Windows),
],
),
];
for (case_name, line_ending_setting, expected_line_endings) in cases {
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({
"crlf_file.rs": "one\r\ntwo\r\nthree\r\n",
"lf_file.rs": "one\ntwo\nthree\n",
"no_newline.rs": "single line",
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
let worktree_id = project.update(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.all_languages.defaults.line_ending = line_ending_setting;
});
});
});
cx.executor().run_until_parked();
assert_line_endings_after_format(
cx,
&project,
worktree_id,
case_name,
&expected_line_endings,
)
.await;
}
}
#[gpui::test]
async fn test_line_ending_editorconfig_on_format_and_save(cx: &mut gpui::TestAppContext) {
init_test(cx);
let cases = [
(
"editorconfig lf",
"lf",
"crlf_file.rs",
LineEnding::Windows,
[
("crlf_file.rs", LineEnding::Unix),
("lf_file.rs", LineEnding::Unix),
("no_newline.rs", LineEnding::Unix),
],
"one\ntwo\nthree\n",
),
(
"editorconfig crlf",
"crlf",
"lf_file.rs",
LineEnding::Unix,
[
("crlf_file.rs", LineEnding::Windows),
("lf_file.rs", LineEnding::Windows),
("no_newline.rs", LineEnding::Windows),
],
"one\r\ntwo\r\nthree\r\n",
),
];
for (
case_name,
editorconfig_end_of_line,
buffer_path,
initial_line_ending,
expected_line_endings,
expected_saved_contents,
) in cases
{
let file_system = FakeFs::new(cx.executor());
file_system
.insert_tree(
path!("/dir"),
json!({
".editorconfig": format!("root = true\n[*.rs]\nend_of_line = {editorconfig_end_of_line}\n"),
"crlf_file.rs": "one\r\ntwo\r\nthree\r\n",
"lf_file.rs": "one\ntwo\nthree\n",
"no_newline.rs": "single line",
}),
)
.await;
let project = Project::test(file_system.clone(), [path!("/dir").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
cx.executor().run_until_parked();
let worktree_id = project.update(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
let buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, rel_path(buffer_path)), cx)
})
.await
.unwrap();
buffer.update(cx, |buffer, _| {
assert_eq!(buffer.line_ending(), initial_line_ending);
});
assert_line_endings_after_format(
cx,
&project,
worktree_id,
case_name,
&expected_line_endings,
)
.await;
project
.update(cx, |project, cx| project.save_buffer(buffer, cx))
.await
.unwrap();
let saved_path = PathBuf::from(path!("/dir")).join(buffer_path);
assert_eq!(
file_system.load(&saved_path).await.unwrap(),
expected_saved_contents,
);
}
}
#[gpui::test]
async fn test_line_ending_initialization_for_new_buffers(cx: &mut gpui::TestAppContext) {
init_test(cx);
let cases = [
(Some(LineEndingSetting::Detect), LineEnding::default()),
(Some(LineEndingSetting::PreferLf), LineEnding::Unix),
(Some(LineEndingSetting::PreferCrlf), LineEnding::Windows),
(Some(LineEndingSetting::EnforceLf), LineEnding::Unix),
(Some(LineEndingSetting::EnforceCrlf), LineEnding::Windows),
];
for (line_ending_setting, expected_line_ending) in cases {
let fs = FakeFs::new(cx.executor());
fs.insert_tree(path!("/dir"), json!({})).await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.all_languages.defaults.line_ending = line_ending_setting;
});
});
});
cx.executor().run_until_parked();
let created_buffer = project
.update(cx, |project, cx| project.create_buffer(None, false, cx))
.unwrap()
.await;
created_buffer.update(cx, |buffer, _| {
assert_eq!(buffer.line_ending(), expected_line_ending);
});
let local_buffer = project.update(cx, |project, cx| {
project.create_local_buffer("single line", None, false, cx)
});
local_buffer.update(cx, |buffer, _| {
assert_eq!(buffer.line_ending(), expected_line_ending);
});
let opened_missing_buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(path!("/dir/new_file.rs"), cx)
})
.await
.unwrap();
opened_missing_buffer.update(cx, |buffer, _| {
assert_eq!(buffer.line_ending(), expected_line_ending);
});
}
}
async fn assert_line_endings_after_format(
cx: &mut gpui::TestAppContext,
project: &Entity<Project>,
worktree_id: WorktreeId,
case_name: &str,
expected_line_endings: &[(&str, LineEnding)],
) {
for (path, expected_line_ending) in expected_line_endings {
let buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, rel_path(path)), cx)
})
.await
.unwrap();
let mut buffers = HashSet::default();
buffers.insert(buffer.clone());
project
.update(cx, |project, cx| {
project.format(
buffers,
project::lsp_store::LspFormatTarget::Buffers,
false,
project::lsp_store::FormatTrigger::Save,
cx,
)
})
.await
.unwrap();
buffer.update(cx, |buffer, _| {
assert_eq!(
buffer.line_ending(),
*expected_line_ending,
"unexpected line ending for {path} in {case_name}"
);
});
}
}
#[gpui::test]
async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx);
+6
View File
@@ -540,6 +540,12 @@ impl VsCodeSettings {
edit_predictions_disabled_in: None,
enable_language_server: None,
ensure_final_newline_on_save: self.read_bool("files.insertFinalNewline"),
line_ending: self.read_enum("files.eol", |s| match s {
"\n" => Some(LineEndingSetting::PreferLf),
"\r\n" => Some(LineEndingSetting::PreferCrlf),
"auto" => Some(LineEndingSetting::Detect),
_ => None,
}),
extend_comment_on_newline: None,
extend_list_on_newline: None,
indent_list_on_tab: None,
+53
View File
@@ -449,6 +449,23 @@ pub struct LanguageSettingsContent {
///
/// Default: true
pub ensure_final_newline_on_save: Option<bool>,
/// How line endings should be handled for new files and during format and
/// save operations.
///
/// - `detect`: Detect existing line endings and otherwise use the platform
/// default (`lf` on Unix, `crlf` on Windows).
/// - `prefer_lf`: Prefer LF for new files and files with no existing line
/// ending.
/// - `prefer_crlf`: Prefer CRLF for new files and files with no existing
/// line ending.
/// - `enforce_lf`: Enforce LF during format and save.
/// - `enforce_crlf`: Enforce CRLF during format and save.
///
/// The EditorConfig `end_of_line` property overrides this setting and
/// behaves like `enforce_lf` or `enforce_crlf`.
///
/// Default: detect
pub line_ending: Option<LineEndingSetting>,
/// How to perform a buffer format.
///
/// Default: auto
@@ -899,6 +916,42 @@ pub enum FormatOnSave {
Off,
}
/// Controls how line endings are normalized when a buffer is saved.
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum LineEndingSetting {
/// Preserve the existing line endings of the file. New files use the
/// platform default line ending.
#[strum(serialize = "Detect")]
Detect,
/// Use LF for new files and files with no existing line-ending
/// convention, while preserving existing LF or CRLF files.
#[strum(serialize = "Prefer LF")]
PreferLf,
/// Use CRLF for new files and files with no existing line-ending
/// convention, while preserving existing LF or CRLF files.
#[strum(serialize = "Prefer CRLF")]
PreferCrlf,
/// Normalize line endings to LF (`\n`) during format and save.
#[strum(serialize = "Enforce LF")]
EnforceLf,
/// Normalize line endings to CRLF (`\r\n`) during format and save.
#[strum(serialize = "Enforce CRLF")]
EnforceCrlf,
}
/// Controls which formatters should be used when formatting code.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
#[serde(untagged)]
+23 -1
View File
@@ -8083,7 +8083,7 @@ fn language_settings_data() -> Box<[SettingsPageItem]> {
]
}
fn formatting_section() -> [SettingsPageItem; 7] {
fn formatting_section() -> [SettingsPageItem; 8] {
[
SettingsPageItem::SectionHeader("Formatting"),
SettingsPageItem::SettingItem(SettingItem {
@@ -8150,6 +8150,28 @@ fn language_settings_data() -> Box<[SettingsPageItem]> {
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Line Ending",
description: "How line endings should be handled for new files and during format and save operations.",
field: Box::new(SettingField {
json_path: Some("languages.$(language).line_ending"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.line_ending.as_ref()
})
},
write: |settings_content, value| {
language_settings_field_mut(settings_content, value, |language, value| {
language.line_ending = value;
})
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
should_do_titlecase: Some(false),
..Default::default()
})),
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Formatter",
description: "How to perform a buffer format.",
+1
View File
@@ -491,6 +491,7 @@ fn init_renderers(cx: &mut App) {
.add_basic_renderer::<settings::ProjectPanelSortOrder>(render_dropdown)
.add_basic_renderer::<settings::RewrapBehavior>(render_dropdown)
.add_basic_renderer::<settings::FormatOnSave>(render_dropdown)
.add_basic_renderer::<settings::LineEndingSetting>(render_dropdown)
.add_basic_renderer::<settings::IndentGuideColoring>(render_dropdown)
.add_basic_renderer::<settings::IndentGuideBackgroundColoring>(render_dropdown)
.add_basic_renderer::<settings::FileFinderWidthContent>(render_dropdown)
+51
View File
@@ -1618,6 +1618,56 @@ This setting enables integration with macOS’s native window tabbing feature. W
`boolean` values
## Line Ending
- Description: How line endings should be handled for new files and during format and save. This can be specified on a per-language basis.
- Setting: `line_ending`
- Default: `detect`
**Options**
1. To detect existing line endings and otherwise use the platform default (`lf` on Unix, `crlf` on Windows), set it to `detect`:
```json [settings]
{
"line_ending": "detect"
}
```
2. To prefer LF (`\n`) for new files and files with no existing line ending, use `prefer_lf`:
```json [settings]
{
"line_ending": "prefer_lf"
}
```
3. To prefer CRLF (`\r\n`) for new files and files with no existing line ending, use `prefer_crlf`:
```json [settings]
{
"line_ending": "prefer_crlf"
}
```
4. To enforce LF (`\n`) during format and save, use `enforce_lf`:
```json [settings]
{
"line_ending": "enforce_lf"
}
```
5. To enforce CRLF (`\r\n`) during format and save, use `enforce_crlf`:
```json [settings]
{
"line_ending": "enforce_crlf"
}
```
The [`.editorconfig`](https://editorconfig.org) `end_of_line` property overrides this setting and behaves like `enforce_lf` or `enforce_crlf`.
## Expand Excerpt Lines
- Description: The default number of lines to expand excerpts in the multibuffer by
@@ -2772,6 +2822,7 @@ The following settings can be overridden for each specific language:
- [`enable_language_server`](#enable-language-server)
- [`ensure_final_newline_on_save`](#ensure-final-newline-on-save)
- [`line_ending`](#line-ending)
- [`format_on_save`](#format-on-save)
- [`formatter`](#formatter)
- [`hard_tabs`](#hard-tabs)