Repository: rust-analyzer/rust-analyzer
Pull request #1706 (2019-08-20 16:20:47)

Normalize line endings to Unix flavor on IO bounraries

matklad: bors r+
matklad: cc @kjeremy: this risk breaking windows (but it shouldn't, we have a test for this), so if something weird happens, this PR might be to blame
bors[bot]: # Build failed
  * [continuous-integration/travis-ci/push](https://travis-ci.org/rust-analyzer/rust-analyzer/builds/574446267?utm_source=github_status&utm_medium=notification)
matklad: bors r+
bors[bot]: # Build failed (retrying...)
  * [continuous-integration/travis-ci/push](https://travis-ci.org/rust-analyzer/rust-analyzer/builds/574454671?utm_source=github_status&utm_medium=notification)

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 3a6f0cc82a..85db6d1797 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1092,7 +1092,7 @@ dependencies = [
  "ra_hir 0.1.0",
  "ra_ide_api 0.1.0",
  "ra_project_model 0.1.0",
- "ra_vfs 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ra_vfs 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "ra_vfs_glob 0.1.0",
  "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -1198,7 +1198,7 @@ dependencies = [
  "ra_project_model 0.1.0",
  "ra_syntax 0.1.0",
  "ra_text_edit 0.1.0",
- "ra_vfs 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ra_vfs 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "ra_vfs_glob 0.1.0",
  "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1256,7 +1256,7 @@ dependencies = [
 
 [[package]]
 name = "ra_rustc_lexer"
-version = "0.1.0-pre.2"
+version = "0.1.0-pre.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1268,7 +1268,7 @@ version = "0.1.0"
 dependencies = [
  "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ra_parser 0.1.0",
- "ra_rustc_lexer 0.1.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ra_rustc_lexer 0.1.0-pre.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "ra_text_edit 0.1.0",
  "rowan 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "smol_str 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1307,7 +1307,7 @@ dependencies = [
 
 [[package]]
 name = "ra_vfs"
-version = "0.2.6"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1324,7 +1324,7 @@ name = "ra_vfs_glob"
 version = "0.1.0"
 dependencies = [
  "globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "ra_vfs 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ra_vfs 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2134,8 +2134,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
 "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
 "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
-"checksum ra_rustc_lexer 0.1.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6baccda91574dfadd7f8a0bc8f9f110f874b6b484289b2536d3dbf4f0d5d97bb"
-"checksum ra_vfs 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "865bb9e0f71916f7c7527af4843a2a67d1b0789f7c91c512a6b4ded69af98249"
+"checksum ra_rustc_lexer 0.1.0-pre.3 (registry+https://github.com/rust-lang/crates.io-index)" = "04371af481820ff8d35c7d12b503eb09cf9e1bd246269bf4a33e3d8c54fa3a4a"
+"checksum ra_vfs 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6918c38f6ab45101f1ddd6110eda831a735b5e9ca6c96d1ceedb7d13ecaeb0f4"
 "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
 "checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c"
 "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
diff --git a/crates/ra_assists/src/introduce_variable.rs b/crates/ra_assists/src/introduce_variable.rs
index 5eb7083103..95c18d0e3d 100644
--- a/crates/ra_assists/src/introduce_variable.rs
+++ b/crates/ra_assists/src/introduce_variable.rs
@@ -56,10 +56,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option
             // but we do not want to duplicate possible
             // extra newlines in the indent block
             let text = indent.text();
-            if text.starts_with("\r\n") {
-                buf.push_str("\r\n");
-                buf.push_str(text.trim_start_matches("\r\n"));
-            } else if text.starts_with('\n') {
+            if text.starts_with('\n') {
                 buf.push_str("\n");
                 buf.push_str(text.trim_start_matches('\n'));
             } else {
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml
index 2c69a6993e..afeac0d8a4 100644
--- a/crates/ra_lsp_server/Cargo.toml
+++ b/crates/ra_lsp_server/Cargo.toml
@@ -16,7 +16,7 @@ lsp-types = { version = "0.60.0", features = ["proposed"] }
 rustc-hash = "1.0"
 parking_lot = "0.9.0"
 
-ra_vfs = "0.2.0"
+ra_vfs = "0.2.7"
 thread_worker = { path = "../thread_worker" }
 ra_syntax = { path = "../ra_syntax" }
 ra_text_edit = { path = "../ra_text_edit" }
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs
index df8ea6e0d1..0ad2ea10f4 100644
--- a/crates/ra_lsp_server/src/conv.rs
+++ b/crates/ra_lsp_server/src/conv.rs
@@ -11,6 +11,7 @@ use ra_ide_api::{
 };
 use ra_syntax::{SyntaxKind, TextRange, TextUnit};
 use ra_text_edit::{AtomTextEdit, TextEdit};
+use ra_vfs::LineEndings;
 
 use crate::{req, world::WorldSnapshot, Result};
 
@@ -19,16 +20,14 @@ pub trait Conv {
     fn conv(self) -> Self::Output;
 }
 
-pub trait ConvWith {
-    type Ctx;
+pub trait ConvWith<CTX> {
     type Output;
-    fn conv_with(self, ctx: &Self::Ctx) -> Self::Output;
+    fn conv_with(self, ctx: CTX) -> Self::Output;
 }
 
-pub trait TryConvWith {
-    type Ctx;
+pub trait TryConvWith<CTX> {
     type Output;
-    fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output>;
+    fn try_conv_with(self, ctx: CTX) -> Result<Self::Output>;
 }
 
 impl Conv for SyntaxKind {
@@ -89,11 +88,10 @@ impl Conv for Severity {
     }
 }
 
-impl ConvWith for CompletionItem {
-    type Ctx = LineIndex;
+impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
     type Output = ::lsp_types::CompletionItem;
 
-    fn conv_with(self, ctx: &LineIndex) -> ::lsp_types::CompletionItem {
+    fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> ::lsp_types::CompletionItem {
         let mut additional_text_edits = Vec::new();
         let mut text_edit = None;
         // LSP does not allow arbitrary edits in completion, so we have to do a
@@ -138,8 +136,7 @@ impl ConvWith for CompletionItem {
     }
 }
 
-impl ConvWith for Position {
-    type Ctx = LineIndex;
+impl ConvWith<&LineIndex> for Position {
     type Output = TextUnit;
 
     fn conv_with(self, line_index: &LineIndex) -> TextUnit {
@@ -148,8 +145,7 @@ impl ConvWith for Position {
     }
 }
 
-impl ConvWith for TextUnit {
-    type Ctx = LineIndex;
+impl ConvWith<&LineIndex> for TextUnit {
     type Output = Position;
 
     fn conv_with(self, line_index: &LineIndex) -> Position {
@@ -158,8 +154,7 @@ impl ConvWith for TextUnit {
     }
 }
 
-impl ConvWith for TextRange {
-    type Ctx = LineIndex;
+impl ConvWith<&LineIndex> for TextRange {
     type Output = Range;
 
     fn conv_with(self, line_index: &LineIndex) -> Range {
@@ -167,8 +162,7 @@ impl ConvWith for TextRange {
     }
 }
 
-impl ConvWith for Range {
-    type Ctx = LineIndex;
+impl ConvWith<&LineIndex> for Range {
     type Output = TextRange;
 
     fn conv_with(self, line_index: &LineIndex) -> TextRange {
@@ -208,77 +202,73 @@ impl Conv for ra_ide_api::FunctionSignature {
     }
 }
 
-impl ConvWith for TextEdit {
-    type Ctx = LineIndex;
+impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
     type Output = Vec<lsp_types::TextEdit>;
 
-    fn conv_with(self, line_index: &LineIndex) -> Vec<lsp_types::TextEdit> {
-        self.as_atoms().iter().map_conv_with(line_index).collect()
+    fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
+        self.as_atoms().iter().map_conv_with(ctx).collect()
     }
 }
 
-impl<'a> ConvWith for &'a AtomTextEdit {
-    type Ctx = LineIndex;
+impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
     type Output = lsp_types::TextEdit;
 
-    fn conv_with(self, line_index: &LineIndex) -> lsp_types::TextEdit {
-        lsp_types::TextEdit {
-            range: self.delete.conv_with(line_index),
-            new_text: self.insert.clone(),
+    fn conv_with(
+        self,
+        (line_index, line_endings): (&LineIndex, LineEndings),
+    ) -> lsp_types::TextEdit {
+        let mut new_text = self.insert.clone();
+        if line_endings == LineEndings::Dos {
+            new_text = new_text.replace('\n', "\r\n");
         }
+        lsp_types::TextEdit { range: self.delete.conv_with(line_index), new_text }
     }
 }
 
-impl<T: ConvWith> ConvWith for Option<T> {
-    type Ctx = <T as ConvWith>::Ctx;
-    type Output = Option<<T as ConvWith>::Output>;
-    fn conv_with(self, ctx: &Self::Ctx) -> Self::Output {
+impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> {
+    type Output = Option<T::Output>;
+
+    fn conv_with(self, ctx: CTX) -> Self::Output {
         self.map(|x| ConvWith::conv_with(x, ctx))
     }
 }
 
-impl<'a> TryConvWith for &'a Url {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for &Url {
     type Output = FileId;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
         world.uri_to_file_id(self)
     }
 }
 
-impl TryConvWith for FileId {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for FileId {
     type Output = Url;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<Url> {
         world.file_id_to_uri(self)
     }
 }
 
-impl<'a> TryConvWith for &'a TextDocumentItem {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for &TextDocumentItem {
     type Output = FileId;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
         self.uri.try_conv_with(world)
     }
 }
 
-impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for &VersionedTextDocumentIdentifier {
     type Output = FileId;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
         self.uri.try_conv_with(world)
     }
 }
 
-impl<'a> TryConvWith for &'a TextDocumentIdentifier {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for &TextDocumentIdentifier {
     type Output = FileId;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
         world.uri_to_file_id(&self.uri)
     }
 }
 
-impl<'a> TryConvWith for &'a TextDocumentPositionParams {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for &TextDocumentPositionParams {
     type Output = FilePosition;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<FilePosition> {
         let file_id = self.text_document.try_conv_with(world)?;
@@ -288,8 +278,7 @@ impl<'a> TryConvWith for &'a TextDocumentPositionParams {
     }
 }
 
-impl<'a> TryConvWith for (&'a TextDocumentIdentifier, Range) {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for (&TextDocumentIdentifier, Range) {
     type Output = FileRange;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileRange> {
         let file_id = self.0.try_conv_with(world)?;
@@ -299,10 +288,9 @@ impl<'a> TryConvWith for (&'a TextDocumentIdentifier, Range) {
     }
 }
 
-impl<T: TryConvWith> TryConvWith for Vec<T> {
-    type Ctx = <T as TryConvWith>::Ctx;
-    type Output = Vec<<T as TryConvWith>::Output>;
-    fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output> {
+impl<T: TryConvWith<CTX>, CTX: Copy> TryConvWith<CTX> for Vec<T> {
+    type Output = Vec<<T as TryConvWith<CTX>>::Output>;
+    fn try_conv_with(self, ctx: CTX) -> Result<Self::Output> {
         let mut res = Vec::with_capacity(self.len());
         for item in self {
             res.push(item.try_conv_with(ctx)?);
@@ -311,8 +299,7 @@ impl<T: TryConvWith> TryConvWith for Vec<T> {
     }
 }
 
-impl TryConvWith for SourceChange {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for SourceChange {
     type Output = req::SourceChange;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::SourceChange> {
         let cursor_position = match self.cursor_position {
@@ -351,8 +338,7 @@ impl TryConvWith for SourceChange {
     }
 }
 
-impl TryConvWith for SourceFileEdit {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
     type Output = TextDocumentEdit;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<TextDocumentEdit> {
         let text_document = VersionedTextDocumentIdentifier {
@@ -360,13 +346,14 @@ impl TryConvWith for SourceFileEdit {
             version: None,
         };
         let line_index = world.analysis().file_line_index(self.file_id)?;
-        let edits = self.edit.as_atoms().iter().map_conv_with(&line_index).collect();
+        let line_endings = world.file_line_endings(self.file_id);
+        let edits =
+            self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect();
         Ok(TextDocumentEdit { text_document, edits })
     }
 }
 
-impl TryConvWith for FileSystemEdit {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for FileSystemEdit {
     type Output = ResourceOp;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<ResourceOp> {
         let res = match self {
@@ -384,8 +371,7 @@ impl TryConvWith for FileSystemEdit {
     }
 }
 
-impl TryConvWith for &NavigationTarget {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for &NavigationTarget {
     type Output = Location;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<Location> {
         let line_index = world.analysis().file_line_index(self.file_id())?;
@@ -394,8 +380,7 @@ impl TryConvWith for &NavigationTarget {
     }
 }
 
-impl TryConvWith for (FileId, RangeInfo<NavigationTarget>) {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<NavigationTarget>) {
     type Output = LocationLink;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<LocationLink> {
         let (src_file_id, target) = self;
@@ -422,8 +407,7 @@ impl TryConvWith for (FileId, RangeInfo<NavigationTarget>) {
     }
 }
 
-impl TryConvWith for (FileId, RangeInfo<Vec<NavigationTarget>>) {
-    type Ctx = WorldSnapshot;
+impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) {
     type Output = req::GotoDefinitionResponse;
     fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::GotoTypeDefinitionResponse> {
         let (file_id, RangeInfo { range, info: navs }) = self;
@@ -454,57 +438,55 @@ pub fn to_location(
     Ok(loc)
 }
 
-pub trait MapConvWith<'a>: Sized + 'a {
-    type Ctx;
+pub trait MapConvWith<CTX>: Sized {
     type Output;
 
-    fn map_conv_with(self, ctx: &'a Self::Ctx) -> ConvWithIter<'a, Self, Self::Ctx> {
+    fn map_conv_with(self, ctx: CTX) -> ConvWithIter<Self, CTX> {
         ConvWithIter { iter: self, ctx }
     }
 }
 
-impl<'a, I> MapConvWith<'a> for I
+impl<CTX, I> MapConvWith<CTX> for I
 where
-    I: Iterator + 'a,
-    I::Item: ConvWith,
+    I: Iterator,
+    I::Item: ConvWith<CTX>,
 {
-    type Ctx = <I::Item as ConvWith>::Ctx;
-    type Output = <I::Item as ConvWith>::Output;
+    type Output = <I::Item as ConvWith<CTX>>::Output;
 }
 
-pub struct ConvWithIter<'a, I, Ctx: 'a> {
+pub struct ConvWithIter<I, CTX> {
     iter: I,
-    ctx: &'a Ctx,
+    ctx: CTX,
 }
 
-impl<'a, I, Ctx> Iterator for ConvWithIter<'a, I, Ctx>
+impl<I, CTX> Iterator for ConvWithIter<I, CTX>
 where
     I: Iterator,
-    I::Item: ConvWith<Ctx = Ctx>,
+    I::Item: ConvWith<CTX>,
+    CTX: Copy,
 {
-    type Item = <I::Item as ConvWith>::Output;
+    type Item = <I::Item as ConvWith<CTX>>::Output;
 
     fn next(&mut self) -> Option<Self::Item> {
         self.iter.next().map(|item| item.conv_with(self.ctx))
     }
 }
 
-pub trait TryConvWithToVec<'a>: Sized + 'a {
-    type Ctx;
+pub trait TryConvWithToVec<CTX>: Sized {
     type Output;
 
-    fn try_conv_with_to_vec(self, ctx: &'a Self::Ctx) -> Result<Vec<Self::Output>>;
+    fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>>;
 }
 
-impl<'a, I> TryConvWithToVec<'a> for I
+impl<I, CTX> TryConvWithToVec<CTX> for I
 where
-    I: Iterator + 'a,
-    I::Item: TryConvWith,
+    I: Iterator,
+    I::Item: TryConvWith<CTX>,
+    CTX: Copy,
 {
-    type Ctx = <I::Item as TryConvWith>::Ctx;
-    type Output = <I::Item as TryConvWith>::Output;
+    type Output = <I::Item as TryConvWith<CTX>>::Output;
 
-    fn try_conv_with_to_vec(self, ctx: &'a Self::Ctx) -> Result<Vec<Self::Output>> {
+    fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>> {
         self.map(|it| it.try_conv_with(ctx)).collect()
     }
 }
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index b465707f8c..3a559e845d 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -138,6 +138,7 @@ pub fn handle_on_type_formatting(
     let _p = profile("handle_on_type_formatting");
     let mut position = params.text_document_position.try_conv_with(&world)?;
     let line_index = world.analysis().file_line_index(position.file_id)?;
+    let line_endings = world.file_line_endings(position.file_id);
 
     // in `ra_ide_api`, the `on_type` invariant is that
     // `text.char_at(position) == typed_char`.
@@ -156,7 +157,7 @@ pub fn handle_on_type_formatting(
     // This should be a single-file edit
     let edit = edit.source_file_edits.pop().unwrap();
 
-    let change: Vec<TextEdit> = edit.edit.conv_with(&line_index);
+    let change: Vec<TextEdit> = edit.edit.conv_with((&line_index, line_endings));
     Ok(Some(change))
 }
 
@@ -370,8 +371,9 @@ pub fn handle_completion(
         Some(items) => items,
     };
     let line_index = world.analysis().file_line_index(position.file_id)?;
+    let line_endings = world.file_line_endings(position.file_id);
     let items: Vec<CompletionItem> =
-        items.into_iter().map(|item| item.conv_with(&line_index)).collect();
+        items.into_iter().map(|item| item.conv_with((&line_index, line_endings))).collect();
 
     Ok(Some(items.into()))
 }
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 9990ef62e1..10f96812fb 100644
--- a/crates/ra_lsp_server/src/world.rs
+++ b/crates/ra_lsp_server/src/world.rs
@@ -9,7 +9,7 @@ use parking_lot::RwLock;
 use ra_ide_api::{
     Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId,
 };
-use ra_vfs::{RootEntry, Vfs, VfsChange, VfsFile, VfsRoot};
+use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot};
 use ra_vfs_glob::{Glob, RustPackageFilterBuilder};
 use relative_path::RelativePathBuf;
 
@@ -210,6 +210,10 @@ impl WorldSnapshot {
         Ok(url)
     }
 
+    pub fn file_line_endings(&self, id: FileId) -> LineEndings {
+        self.vfs.read().file_line_endings(VfsFile(id.0))
+    }
+
     pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result<Url> {
         let base = self.vfs.read().root2path(VfsRoot(root.0));
         let path = path.to_path(base);
diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml
index 5f85858786..0ead277b2f 100644
--- a/crates/ra_syntax/Cargo.toml
+++ b/crates/ra_syntax/Cargo.toml
@@ -10,7 +10,7 @@ repository = "https://github.com/rust-analyzer/rust-analyzer"
 [dependencies]
 itertools = "0.8.0"
 rowan = "0.6.1"
-ra_rustc_lexer = { version = "0.1.0-pre.2" }
+ra_rustc_lexer = { version = "0.1.0-pre.3", features = ["unicode-xid"] }
 
 # ideally, `serde` should be enabled by `ra_lsp_server`, but we enable it here
 # to reduce number of compilations
diff --git a/crates/ra_syntax/src/parsing/lexer.rs b/crates/ra_syntax/src/parsing/lexer.rs
index 06822ea917..bdb01d40b3 100644
--- a/crates/ra_syntax/src/parsing/lexer.rs
+++ b/crates/ra_syntax/src/parsing/lexer.rs
@@ -12,16 +12,16 @@ pub struct Token {
     pub len: TextUnit,
 }
 
-fn match_literal_kind(kind: ra_rustc_lexer::LiteralKind) -> SyntaxKind {
+fn match_literal_kind(kind: rustc_lexer::LiteralKind) -> SyntaxKind {
     match kind {
-        ra_rustc_lexer::LiteralKind::Int { .. } => INT_NUMBER,
-        ra_rustc_lexer::LiteralKind::Float { .. } => FLOAT_NUMBER,
-        ra_rustc_lexer::LiteralKind::Char { .. } => CHAR,
-        ra_rustc_lexer::LiteralKind::Byte { .. } => BYTE,
-        ra_rustc_lexer::LiteralKind::Str { .. } => STRING,
-        ra_rustc_lexer::LiteralKind::ByteStr { .. } => BYTE_STRING,
-        ra_rustc_lexer::LiteralKind::RawStr { .. } => RAW_STRING,
-        ra_rustc_lexer::LiteralKind::RawByteStr { .. } => RAW_BYTE_STRING,
+        rustc_lexer::LiteralKind::Int { .. } => INT_NUMBER,
+        rustc_lexer::LiteralKind::Float { .. } => FLOAT_NUMBER,
+        rustc_lexer::LiteralKind::Char { .. } => CHAR,
+        rustc_lexer::LiteralKind::Byte { .. } => BYTE,
+        rustc_lexer::LiteralKind::Str { .. } => STRING,
+        rustc_lexer::LiteralKind::ByteStr { .. } => BYTE_STRING,
+        rustc_lexer::LiteralKind::RawStr { .. } => RAW_STRING,
+        rustc_lexer::LiteralKind::RawByteStr { .. } => RAW_BYTE_STRING,
     }
 }
 
@@ -32,32 +32,17 @@ pub fn tokenize(text: &str) -> Vec<Token> {
     }
     let mut text = text;
     let mut acc = Vec::new();
-    if let Some(len) = ra_rustc_lexer::strip_shebang(text) {
+    if let Some(len) = rustc_lexer::strip_shebang(text) {
         acc.push(Token { kind: SHEBANG, len: TextUnit::from_usize(len) });
         text = &text[len..];
     }
     while !text.is_empty() {
-        let rustc_token = ra_rustc_lexer::first_token(text);
-        macro_rules! decompose {
-            ($t1:expr, $t2:expr) => {{
-                acc.push(Token { kind: $t1, len: 1.into() });
-                acc.push(Token { kind: $t2, len: 1.into() });
-                text = &text[2..];
-                continue;
-            }};
-            ($t1:expr, $t2:expr, $t3:expr) => {{
-                acc.push(Token { kind: $t1, len: 1.into() });
-                acc.push(Token { kind: $t2, len: 1.into() });
-                acc.push(Token { kind: $t3, len: 1.into() });
-                text = &text[3..];
-                continue;
-            }};
-        }
+        let rustc_token = rustc_lexer::first_token(text);
         let kind = match rustc_token.kind {
-            ra_rustc_lexer::TokenKind::LineComment => COMMENT,
-            ra_rustc_lexer::TokenKind::BlockComment { .. } => COMMENT,
-            ra_rustc_lexer::TokenKind::Whitespace => WHITESPACE,
-            ra_rustc_lexer::TokenKind::Ident => {
+            rustc_lexer::TokenKind::LineComment => COMMENT,
+            rustc_lexer::TokenKind::BlockComment { .. } => COMMENT,
+            rustc_lexer::TokenKind::Whitespace => WHITESPACE,
+            rustc_lexer::TokenKind::Ident => {
                 let token_text = &text[..rustc_token.len];
                 if token_text == "_" {
                     UNDERSCORE
@@ -65,62 +50,37 @@ pub fn tokenize(text: &str) -> Vec<Token> {
                     SyntaxKind::from_keyword(&text[..rustc_token.len]).unwrap_or(IDENT)
                 }
             }
-            ra_rustc_lexer::TokenKind::RawIdent => IDENT,
-            ra_rustc_lexer::TokenKind::Literal { kind, .. } => match_literal_kind(kind),
-            ra_rustc_lexer::TokenKind::Lifetime { .. } => LIFETIME,
-            ra_rustc_lexer::TokenKind::Semi => SEMI,
-            ra_rustc_lexer::TokenKind::Comma => COMMA,
-            ra_rustc_lexer::TokenKind::DotDotDot => decompose!(DOT, DOT, DOT),
-            ra_rustc_lexer::TokenKind::DotDotEq => decompose!(DOT, DOT, EQ),
-            ra_rustc_lexer::TokenKind::DotDot => decompose!(DOT, DOT),
-            ra_rustc_lexer::TokenKind::Dot => DOT,
-            ra_rustc_lexer::TokenKind::OpenParen => L_PAREN,
-            ra_rustc_lexer::TokenKind::CloseParen => R_PAREN,
-            ra_rustc_lexer::TokenKind::OpenBrace => L_CURLY,
-            ra_rustc_lexer::TokenKind::CloseBrace => R_CURLY,
-            ra_rustc_lexer::TokenKind::OpenBracket => L_BRACK,
-            ra_rustc_lexer::TokenKind::CloseBracket => R_BRACK,
-            ra_rustc_lexer::TokenKind::At => AT,
-            ra_rustc_lexer::TokenKind::Pound => POUND,
-            ra_rustc_lexer::TokenKind::Tilde => TILDE,
-            ra_rustc_lexer::TokenKind::Question => QUESTION,
-            ra_rustc_lexer::TokenKind::ColonColon => decompose!(COLON, COLON),
-            ra_rustc_lexer::TokenKind::Colon => COLON,
-            ra_rustc_lexer::TokenKind::Dollar => DOLLAR,
-            ra_rustc_lexer::TokenKind::EqEq => decompose!(EQ, EQ),
-            ra_rustc_lexer::TokenKind::Eq => EQ,
-            ra_rustc_lexer::TokenKind::FatArrow => decompose!(EQ, R_ANGLE),
-            ra_rustc_lexer::TokenKind::Ne => decompose!(EXCL, EQ),
-            ra_rustc_lexer::TokenKind::Not => EXCL,
-            ra_rustc_lexer::TokenKind::Le => decompose!(L_ANGLE, EQ),
-            ra_rustc_lexer::TokenKind::LArrow => decompose!(COLON, MINUS),
-            ra_rustc_lexer::TokenKind::Lt => L_ANGLE,
-            ra_rustc_lexer::TokenKind::ShlEq => decompose!(L_ANGLE, L_ANGLE, EQ),
-            ra_rustc_lexer::TokenKind::Shl => decompose!(L_ANGLE, L_ANGLE),
-            ra_rustc_lexer::TokenKind::Ge => decompose!(R_ANGLE, EQ),
-            ra_rustc_lexer::TokenKind::Gt => R_ANGLE,
-            ra_rustc_lexer::TokenKind::ShrEq => decompose!(R_ANGLE, R_ANGLE, EQ),
-            ra_rustc_lexer::TokenKind::Shr => decompose!(R_ANGLE, R_ANGLE),
-            ra_rustc_lexer::TokenKind::RArrow => decompose!(MINUS, R_ANGLE),
-            ra_rustc_lexer::TokenKind::Minus => MINUS,
-            ra_rustc_lexer::TokenKind::MinusEq => decompose!(MINUS, EQ),
-            ra_rustc_lexer::TokenKind::And => AMP,
-            ra_rustc_lexer::TokenKind::AndAnd => decompose!(AMP, AMP),
-            ra_rustc_lexer::TokenKind::AndEq => decompose!(AMP, EQ),
-            ra_rustc_lexer::TokenKind::Or => PIPE,
-            ra_rustc_lexer::TokenKind::OrOr => decompose!(PIPE, PIPE),
-            ra_rustc_lexer::TokenKind::OrEq => decompose!(PIPE, EQ),
-            ra_rustc_lexer::TokenKind::PlusEq => decompose!(PLUS, EQ),
-            ra_rustc_lexer::TokenKind::Plus => PLUS,
-            ra_rustc_lexer::TokenKind::StarEq => decompose!(STAR, EQ),
-            ra_rustc_lexer::TokenKind::Star => STAR,
-            ra_rustc_lexer::TokenKind::SlashEq => decompose!(SLASH, EQ),
-            ra_rustc_lexer::TokenKind::Slash => SLASH,
-            ra_rustc_lexer::TokenKind::CaretEq => decompose!(CARET, EQ),
-            ra_rustc_lexer::TokenKind::Caret => CARET,
-            ra_rustc_lexer::TokenKind::PercentEq => decompose!(PERCENT, EQ),
-            ra_rustc_lexer::TokenKind::Percent => PERCENT,
-            ra_rustc_lexer::TokenKind::Unknown => ERROR,
+            rustc_lexer::TokenKind::RawIdent => IDENT,
+            rustc_lexer::TokenKind::Literal { kind, .. } => match_literal_kind(kind),
+            rustc_lexer::TokenKind::Lifetime { .. } => LIFETIME,
+            rustc_lexer::TokenKind::Semi => SEMI,
+            rustc_lexer::TokenKind::Comma => COMMA,
+            rustc_lexer::TokenKind::Dot => DOT,
+            rustc_lexer::TokenKind::OpenParen => L_PAREN,
+            rustc_lexer::TokenKind::CloseParen => R_PAREN,
+            rustc_lexer::TokenKind::OpenBrace => L_CURLY,
+            rustc_lexer::TokenKind::CloseBrace => R_CURLY,
+            rustc_lexer::TokenKind::OpenBracket => L_BRACK,
+            rustc_lexer::TokenKind::CloseBracket => R_BRACK,
+            rustc_lexer::TokenKind::At => AT,
+            rustc_lexer::TokenKind::Pound => POUND,
+            rustc_lexer::TokenKind::Tilde => TILDE,
+            rustc_lexer::TokenKind::Question => QUESTION,
+            rustc_lexer::TokenKind::Colon => COLON,
+            rustc_lexer::TokenKind::Dollar => DOLLAR,
+            rustc_lexer::TokenKind::Eq => EQ,
+            rustc_lexer::TokenKind::Not => EXCL,
+            rustc_lexer::TokenKind::Lt => L_ANGLE,
+            rustc_lexer::TokenKind::Gt => R_ANGLE,
+            rustc_lexer::TokenKind::Minus => MINUS,
+            rustc_lexer::TokenKind::And => AMP,
+            rustc_lexer::TokenKind::Or => PIPE,
+            rustc_lexer::TokenKind::Plus => PLUS,
+            rustc_lexer::TokenKind::Star => STAR,
+            rustc_lexer::TokenKind::Slash => SLASH,
+            rustc_lexer::TokenKind::Caret => CARET,
+            rustc_lexer::TokenKind::Percent => PERCENT,
+            rustc_lexer::TokenKind::Unknown => ERROR,
         };
         let token = Token { kind, len: TextUnit::from_usize(rustc_token.len) };
         acc.push(token);
@@ -130,12 +90,12 @@ pub fn tokenize(text: &str) -> Vec<Token> {
 }
 
 pub fn classify_literal(text: &str) -> Option<Token> {
-    let t = ra_rustc_lexer::first_token(text);
+    let t = rustc_lexer::first_token(text);
     if t.len != text.len() {
         return None;
     }
     let kind = match t.kind {
-        ra_rustc_lexer::TokenKind::Literal { kind, .. } => match_literal_kind(kind),
+        rustc_lexer::TokenKind::Literal { kind, .. } => match_literal_kind(kind),
         _ => return None,
     };
     Some(Token { kind, len: TextUnit::from_usize(t.len) })
diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs
index 2bb3c0a03e..a8c789e0ca 100644
--- a/crates/ra_syntax/src/validation.rs
+++ b/crates/ra_syntax/src/validation.rs
@@ -1,6 +1,6 @@
 mod block;
 
-use ra_rustc_lexer::unescape;
+use rustc_lexer::unescape;
 
 use crate::{
     algo::visit::{visitor_ctx, VisitorCtx},
@@ -32,64 +32,62 @@ pub enum EscapeError {
     NonAsciiCharInByte,
 }
 
-impl From<ra_rustc_lexer::unescape::EscapeError> for EscapeError {
-    fn from(err: ra_rustc_lexer::unescape::EscapeError) -> Self {
+impl From<rustc_lexer::unescape::EscapeError> for EscapeError {
+    fn from(err: rustc_lexer::unescape::EscapeError) -> Self {
         match err {
-            ra_rustc_lexer::unescape::EscapeError::ZeroChars => EscapeError::ZeroChars,
-            ra_rustc_lexer::unescape::EscapeError::MoreThanOneChar => EscapeError::MoreThanOneChar,
-            ra_rustc_lexer::unescape::EscapeError::LoneSlash => EscapeError::LoneSlash,
-            ra_rustc_lexer::unescape::EscapeError::InvalidEscape => EscapeError::InvalidEscape,
-            ra_rustc_lexer::unescape::EscapeError::BareCarriageReturn
-            | ra_rustc_lexer::unescape::EscapeError::BareCarriageReturnInRawString => {
+            rustc_lexer::unescape::EscapeError::ZeroChars => EscapeError::ZeroChars,
+            rustc_lexer::unescape::EscapeError::MoreThanOneChar => EscapeError::MoreThanOneChar,
+            rustc_lexer::unescape::EscapeError::LoneSlash => EscapeError::LoneSlash,
+            rustc_lexer::unescape::EscapeError::InvalidEscape => EscapeError::InvalidEscape,
+            rustc_lexer::unescape::EscapeError::BareCarriageReturn
+            | rustc_lexer::unescape::EscapeError::BareCarriageReturnInRawString => {
                 EscapeError::BareCarriageReturn
             }
-            ra_rustc_lexer::unescape::EscapeError::EscapeOnlyChar => EscapeError::EscapeOnlyChar,
-            ra_rustc_lexer::unescape::EscapeError::TooShortHexEscape => {
-                EscapeError::TooShortHexEscape
-            }
-            ra_rustc_lexer::unescape::EscapeError::InvalidCharInHexEscape => {
+            rustc_lexer::unescape::EscapeError::EscapeOnlyChar => EscapeError::EscapeOnlyChar,
+            rustc_lexer::unescape::EscapeError::TooShortHexEscape => EscapeError::TooShortHexEscape,
+            rustc_lexer::unescape::EscapeError::InvalidCharInHexEscape => {
                 EscapeError::InvalidCharInHexEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::OutOfRangeHexEscape => {
+            rustc_lexer::unescape::EscapeError::OutOfRangeHexEscape => {
                 EscapeError::OutOfRangeHexEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::NoBraceInUnicodeEscape => {
+            rustc_lexer::unescape::EscapeError::NoBraceInUnicodeEscape => {
                 EscapeError::NoBraceInUnicodeEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::InvalidCharInUnicodeEscape => {
+            rustc_lexer::unescape::EscapeError::InvalidCharInUnicodeEscape => {
                 EscapeError::InvalidCharInUnicodeEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::EmptyUnicodeEscape => {
+            rustc_lexer::unescape::EscapeError::EmptyUnicodeEscape => {
                 EscapeError::EmptyUnicodeEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::UnclosedUnicodeEscape => {
+            rustc_lexer::unescape::EscapeError::UnclosedUnicodeEscape => {
                 EscapeError::UnclosedUnicodeEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::LeadingUnderscoreUnicodeEscape => {
+            rustc_lexer::unescape::EscapeError::LeadingUnderscoreUnicodeEscape => {
                 EscapeError::LeadingUnderscoreUnicodeEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::OverlongUnicodeEscape => {
+            rustc_lexer::unescape::EscapeError::OverlongUnicodeEscape => {
                 EscapeError::OverlongUnicodeEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::LoneSurrogateUnicodeEscape => {
+            rustc_lexer::unescape::EscapeError::LoneSurrogateUnicodeEscape => {
                 EscapeError::LoneSurrogateUnicodeEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::OutOfRangeUnicodeEscape => {
+            rustc_lexer::unescape::EscapeError::OutOfRangeUnicodeEscape => {
                 EscapeError::OutOfRangeUnicodeEscape
             }
-            ra_rustc_lexer::unescape::EscapeError::UnicodeEscapeInByte => {
+            rustc_lexer::unescape::EscapeError::UnicodeEscapeInByte => {
                 EscapeError::UnicodeEscapeInByte
             }
-            ra_rustc_lexer::unescape::EscapeError::NonAsciiCharInByte
-            | ra_rustc_lexer::unescape::EscapeError::NonAsciiCharInByteString => {
+            rustc_lexer::unescape::EscapeError::NonAsciiCharInByte
+            | rustc_lexer::unescape::EscapeError::NonAsciiCharInByteString => {
                 EscapeError::NonAsciiCharInByte
             }
         }
     }
 }
 
-impl From<ra_rustc_lexer::unescape::EscapeError> for SyntaxErrorKind {
-    fn from(err: ra_rustc_lexer::unescape::EscapeError) -> Self {
+impl From<rustc_lexer::unescape::EscapeError> for SyntaxErrorKind {
+    fn from(err: rustc_lexer::unescape::EscapeError) -> Self {
         SyntaxErrorKind::EscapeError(err.into())
     }
 }


Test patch:
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs
index de3bd5bc55..152681062e 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/main.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs
@@ -208,7 +208,7 @@ pub use std::collections::HashMap;
                 "range": {
                     "end": {
                         "character": 0,
-                        "line": 6
+                        "line": 7
                     },
                     "start": {
                         "character": 0,
@@ -414,3 +414,49 @@ fn main() {{}}
     let elapsed = start.elapsed();
     assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
 }
+
+#[test]
+fn preserves_dos_line_endings() {
+    let server = Project::with_fixture(
+        &"
+//- Cargo.toml
+[package]
+name = \"foo\"
+version = \"0.0.0\"
+
+//- src/main.rs
+/// Some Docs\r\nfn main() {}
+",
+    )
+    .server();
+
+    server.request::<OnEnter>(
+        TextDocumentPositionParams {
+            text_document: server.doc_id("src/main.rs"),
+            position: Position { line: 0, character: 8 },
+        },
+        json!({
+          "cursorPosition": {
+            "position": { "line": 1, "character": 4 },
+            "textDocument": { "uri": "file:///[..]src/main.rs" }
+          },
+          "label": "on enter",
+          "workspaceEdit": {
+            "documentChanges": [
+              {
+                "edits": [
+                  {
+                    "newText": "\r\n/// ",
+                    "range": {
+                      "end": { "line": 0, "character": 8 },
+                      "start": { "line": 0, "character": 8 }
+                    }
+                  }
+                ],
+                "textDocument": { "uri": "file:///[..]src/main.rs", "version": null }
+              }
+            ]
+          }
+        }),
+    );
+}
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index ea99ac0629..816d01f09e 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -134,21 +134,25 @@ pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> {
             }
         };
     };
+
     let margin = fixture
         .lines()
         .filter(|it| it.trim_start().starts_with("//-"))
         .map(|it| it.len() - it.trim_start().len())
         .next()
         .expect("empty fixture");
-    let lines = fixture.lines().filter_map(|line| {
-        if line.len() >= margin {
-            assert!(line[..margin].trim().is_empty());
-            Some(&line[margin..])
-        } else {
-            assert!(line.trim().is_empty());
-            None
-        }
-    });
+
+    let lines = fixture
+        .split('\n') // don't use `.lines` to not drop `\r\n`
+        .filter_map(|line| {
+            if line.len() >= margin {
+                assert!(line[..margin].trim().is_empty());
+                Some(&line[margin..])
+            } else {
+                assert!(line.trim().is_empty());
+                None
+            }
+        });
 
     for line in lines {
         if line.starts_with("//-") {