use languageserver_types::{ Range, SymbolKind, Position, TextEdit, Location, Url, TextDocumentIdentifier, VersionedTextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, TextDocumentEdit, }; use ra_editor::{LineIndex, LineCol, Edit, AtomEdit}; use ra_syntax::{SyntaxKind, TextUnit, TextRange}; use ra_analysis::{FileId, SourceChange, SourceFileEdit, FileSystemEdit}; use crate::{ Result, server_world::ServerWorld, req, }; pub trait Conv { type Output; fn conv(self) -> Self::Output; } pub trait ConvWith { type Ctx; type Output; fn conv_with(self, ctx: &Self::Ctx) -> Self::Output; } pub trait TryConvWith { type Ctx; type Output; fn try_conv_with(self, ctx: &Self::Ctx) -> Result; } impl Conv for SyntaxKind { type Output = SymbolKind; fn conv(self) -> ::Output { match self { SyntaxKind::FN_DEF => SymbolKind::Function, SyntaxKind::STRUCT_DEF => SymbolKind::Struct, SyntaxKind::ENUM_DEF => SymbolKind::Enum, SyntaxKind::TRAIT_DEF => SymbolKind::Interface, SyntaxKind::MODULE => SymbolKind::Module, SyntaxKind::TYPE_DEF => SymbolKind::TypeParameter, SyntaxKind::STATIC_DEF => SymbolKind::Constant, SyntaxKind::CONST_DEF => SymbolKind::Constant, SyntaxKind::IMPL_ITEM => SymbolKind::Object, _ => SymbolKind::Variable, } } } impl ConvWith for Position { type Ctx = LineIndex; type Output = TextUnit; fn conv_with(self, line_index: &LineIndex) -> TextUnit { // TODO: UTF-16 let line_col = LineCol { line: self.line as u32, col: (self.character as u32).into(), }; line_index.offset(line_col) } } impl ConvWith for TextUnit { type Ctx = LineIndex; type Output = Position; fn conv_with(self, line_index: &LineIndex) -> Position { let line_col = line_index.line_col(self); // TODO: UTF-16 Position::new(line_col.line as u64, u32::from(line_col.col) as u64) } } impl ConvWith for TextRange { type Ctx = LineIndex; type Output = Range; fn conv_with(self, line_index: &LineIndex) -> Range { Range::new( self.start().conv_with(line_index), self.end().conv_with(line_index), ) } } impl ConvWith for Range { type Ctx = LineIndex; type Output = TextRange; fn conv_with(self, line_index: &LineIndex) -> TextRange { TextRange::from_to( self.start.conv_with(line_index), self.end.conv_with(line_index), ) } } impl ConvWith for Edit { type Ctx = LineIndex; type Output = Vec; fn conv_with(self, line_index: &LineIndex) -> Vec { self.into_atoms() .into_iter() .map_conv_with(line_index) .collect() } } impl ConvWith for AtomEdit { type Ctx = LineIndex; type Output = TextEdit; fn conv_with(self, line_index: &LineIndex) -> TextEdit { TextEdit { range: self.delete.conv_with(line_index), new_text: self.insert, } } } impl ConvWith for Option { type Ctx = ::Ctx; type Output = Option<::Output>; fn conv_with(self, ctx: &Self::Ctx) -> Self::Output { self.map(|x| ConvWith::conv_with(x, ctx)) } } impl<'a> TryConvWith for &'a Url { type Ctx = ServerWorld; type Output = FileId; fn try_conv_with(self, world: &ServerWorld) -> Result { world.uri_to_file_id(self) } } impl TryConvWith for FileId { type Ctx = ServerWorld; type Output = Url; fn try_conv_with(self, world: &ServerWorld) -> Result { world.file_id_to_uri(self) } } impl<'a> TryConvWith for &'a TextDocumentItem { type Ctx = ServerWorld; type Output = FileId; fn try_conv_with(self, world: &ServerWorld) -> Result { self.uri.try_conv_with(world) } } impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier { type Ctx = ServerWorld; type Output = FileId; fn try_conv_with(self, world: &ServerWorld) -> Result { self.uri.try_conv_with(world) } } impl<'a> TryConvWith for &'a TextDocumentIdentifier { type Ctx = ServerWorld; type Output = FileId; fn try_conv_with(self, world: &ServerWorld) -> Result { world.uri_to_file_id(&self.uri) } } impl TryConvWith for Vec { type Ctx = ::Ctx; type Output = Vec<::Output>; fn try_conv_with(self, ctx: &Self::Ctx) -> Result { let mut res = Vec::with_capacity(self.len()); for item in self { res.push(item.try_conv_with(ctx)?); } Ok(res) } } impl TryConvWith for SourceChange { type Ctx = ServerWorld; type Output = req::SourceChange; fn try_conv_with(self, world: &ServerWorld) -> Result { let cursor_position = match self.cursor_position { None => None, Some(pos) => { let line_index = world.analysis().file_line_index(pos.file_id); let edits = self.source_file_edits.iter().find(|it| it.file_id == pos.file_id) .map(|it| it.edits.as_slice()).unwrap_or(&[]); let line_col = translate_offset_with_edit(&*line_index, pos.offset, edits); let position = Position::new(line_col.line as u64, u32::from(line_col.col) as u64); Some(TextDocumentPositionParams { text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?), position, }) } }; let source_file_edits = self.source_file_edits.try_conv_with(world)?; let file_system_edits = self.file_system_edits.try_conv_with(world)?; Ok(req::SourceChange { label: self.label, source_file_edits, file_system_edits, cursor_position, }) } } // HACK: we should translate offset to line/column using linde_index *with edits applied*. // A naive version of this function would be to apply `edits` to the original text, // construct a new line index and use that, but it would be slow. // // Writing fast & correct version is issue #105, let's use a quick hack in the meantime fn translate_offset_with_edit( pre_edit_index: &LineIndex, offset: TextUnit, edits: &[AtomEdit], ) -> LineCol { let fallback = pre_edit_index.line_col(offset); let edit = match edits.first() { None => return fallback, Some(edit) => edit }; let end_offset = edit.delete.start() + TextUnit::of_str(&edit.insert); if !(edit.delete.start() <= offset && offset <= end_offset) { return fallback } let rel_offset = offset - edit.delete.start(); let in_edit_line_col = LineIndex::new(&edit.insert).line_col(rel_offset); let edit_line_col = pre_edit_index.line_col(edit.delete.start()); if in_edit_line_col.line == 0 { LineCol { line: edit_line_col.line, col: edit_line_col.col + in_edit_line_col.col, } } else { LineCol { line: edit_line_col.line + in_edit_line_col.line, col: in_edit_line_col.col, } } } impl TryConvWith for SourceFileEdit { type Ctx = ServerWorld; type Output = TextDocumentEdit; fn try_conv_with(self, world: &ServerWorld) -> Result { let text_document = VersionedTextDocumentIdentifier { uri: self.file_id.try_conv_with(world)?, version: None, }; let line_index = world.analysis().file_line_index(self.file_id); let edits = self.edits .into_iter() .map_conv_with(&line_index) .collect(); Ok(TextDocumentEdit { text_document, edits }) } } impl TryConvWith for FileSystemEdit { type Ctx = ServerWorld; type Output = req::FileSystemEdit; fn try_conv_with(self, world: &ServerWorld) -> Result { let res = match self { FileSystemEdit::CreateFile { anchor, path } => { let uri = world.file_id_to_uri(anchor)?; let path = &path.as_str()[3..]; // strip `../` b/c url is weird let uri = uri.join(path)?; req::FileSystemEdit::CreateFile { uri } }, FileSystemEdit::MoveFile { file, path } => { let src = world.file_id_to_uri(file)?; let path = &path.as_str()[3..]; // strip `../` b/c url is weird let dst = src.join(path)?; req::FileSystemEdit::MoveFile { src, dst } }, }; Ok(res) } } pub fn to_location( file_id: FileId, range: TextRange, world: &ServerWorld, line_index: &LineIndex, ) -> Result { let url = file_id.try_conv_with(world)?; let loc = Location::new( url, range.conv_with(line_index), ); Ok(loc) } pub trait MapConvWith<'a>: Sized + 'a { type Ctx; type Output; fn map_conv_with(self, ctx: &'a Self::Ctx) -> ConvWithIter<'a, Self, Self::Ctx> { ConvWithIter { iter: self, ctx } } } impl<'a, I> MapConvWith<'a> for I where I: Iterator + 'a, I::Item: ConvWith { type Ctx = ::Ctx; type Output = ::Output; } pub struct ConvWithIter<'a, I, Ctx: 'a> { iter: I, ctx: &'a Ctx, } impl<'a, I, Ctx> Iterator for ConvWithIter<'a, I, Ctx> where I: Iterator, I::Item: ConvWith, { type Item = ::Output; fn next(&mut self) -> Option { self.iter.next().map(|item| item.conv_with(self.ctx)) } }