2375 lines · 81.6 KB · Rust
RawDownload
pub use crate::{    diagnostic_set::DiagnosticSet,    highlight_map::{HighlightId, HighlightMap},    proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, LanguageServerConfig,    PLAIN_TEXT,};use crate::{    diagnostic_set::{DiagnosticEntry, DiagnosticGroup},    outline::OutlineItem,    range_from_lsp, Outline,};use anyhow::{anyhow, Result};use clock::ReplicaId;use futures::FutureExt as _;use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task};use lazy_static::lazy_static;use lsp::LanguageServer;use parking_lot::Mutex;use postage::{prelude::Stream, sink::Sink, watch};use similar::{ChangeTag, TextDiff};use smol::future::yield_now;use std::{    any::Any,    cell::RefCell,    cmp::{self, Ordering},    collections::{BTreeMap, HashMap},    ffi::OsString,    future::Future,    iter::{Iterator, Peekable},    ops::{Deref, DerefMut, Range, Sub},    path::{Path, PathBuf},    str,    sync::Arc,    time::{Duration, Instant, SystemTime, UNIX_EPOCH},    vec,};use sum_tree::TreeMap;use text::{operation_queue::OperationQueue, rope::TextDimension};pub use text::{Buffer as TextBuffer, Operation as _, *};use theme::SyntaxTheme;use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};use util::{post_inc, TryFutureExt as _}; #[cfg(any(test, feature = "test-support"))]pub use tree_sitter_rust; pub use lsp::DiagnosticSeverity; thread_local! {    static PARSER: RefCell<Parser> = RefCell::new(Parser::new());} lazy_static! {    static ref QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Default::default();} // TODO - Make this configurableconst INDENT_SIZE: u32 = 4; pub struct Buffer {    text: TextBuffer,    file: Option<Box<dyn File>>,    saved_version: clock::Global,    saved_mtime: SystemTime,    language: Option<Arc<Language>>,    autoindent_requests: Vec<Arc<AutoindentRequest>>,    pending_autoindent: Option<Task<()>>,    sync_parse_timeout: Duration,    syntax_tree: Mutex<Option<SyntaxTree>>,    parsing_in_background: bool,    parse_count: usize,    diagnostics: DiagnosticSet,    remote_selections: TreeMap<ReplicaId, SelectionSet>,    selections_update_count: usize,    diagnostics_update_count: usize,    language_server: Option<LanguageServerState>,    deferred_ops: OperationQueue<Operation>,    #[cfg(test)]    pub(crate) operations: Vec<Operation>,} pub struct BufferSnapshot {    text: text::BufferSnapshot,    tree: Option<Tree>,    diagnostics: DiagnosticSet,    diagnostics_update_count: usize,    remote_selections: TreeMap<ReplicaId, SelectionSet>,    selections_update_count: usize,    is_parsing: bool,    language: Option<Arc<Language>>,    parse_count: usize,} #[derive(Clone, Debug)]struct SelectionSet {    selections: Arc<[Selection<Anchor>]>,    lamport_timestamp: clock::Lamport,} #[derive(Clone, Debug, PartialEq, Eq)]pub struct GroupId {    source: Arc<str>,    id: usize,} #[derive(Clone, Debug, PartialEq, Eq)]pub struct Diagnostic {    pub code: Option<String>,    pub severity: DiagnosticSeverity,    pub message: String,    pub group_id: usize,    pub is_valid: bool,    pub is_primary: bool,    pub is_disk_based: bool,} struct LanguageServerState {    server: Arc<LanguageServer>,    latest_snapshot: watch::Sender<Option<LanguageServerSnapshot>>,    pending_snapshots: BTreeMap<usize, LanguageServerSnapshot>,    next_version: usize,    _maintain_server: Task<Option<()>>,} #[derive(Clone)]struct LanguageServerSnapshot {    buffer_snapshot: text::BufferSnapshot,    version: usize,    path: Arc<Path>,} #[derive(Clone, Debug)]pub enum Operation {    Buffer(text::Operation),    UpdateDiagnostics {        diagnostics: Arc<[DiagnosticEntry<Anchor>]>,        lamport_timestamp: clock::Lamport,    },    UpdateSelections {        replica_id: ReplicaId,        selections: Arc<[Selection<Anchor>]>,        lamport_timestamp: clock::Lamport,    },} #[derive(Clone, Debug, Eq, PartialEq)]pub enum Event {    Edited,    Dirtied,    Saved,    FileHandleChanged,    Reloaded,    Reparsed,    DiagnosticsUpdated,    Closed,} pub trait File {    fn as_local(&self) -> Option<&dyn LocalFile>;     fn mtime(&self) -> SystemTime;     /// Returns the path of this file relative to the worktree's root directory.    fn path(&self) -> &Arc<Path>;     /// Returns the path of this file relative to the worktree's parent directory (this means it    /// includes the name of the worktree's root folder).    fn full_path(&self, cx: &AppContext) -> PathBuf;     /// Returns the last component of this handle's absolute path. If this handle refers to the root    /// of its worktree, then this method will return the name of the worktree itself.    fn file_name(&self, cx: &AppContext) -> OsString;     fn is_deleted(&self) -> bool;     fn save(        &self,        buffer_id: u64,        text: Rope,        version: clock::Global,        cx: &mut MutableAppContext,    ) -> Task<Result<(clock::Global, SystemTime)>>;     fn format_remote(&self, buffer_id: u64, cx: &mut MutableAppContext)        -> Option<Task<Result<()>>>;     fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);     fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);     fn as_any(&self) -> &dyn Any;     fn to_proto(&self) -> rpc::proto::File;} pub trait LocalFile: File {    /// Returns the absolute path of this file.    fn abs_path(&self, cx: &AppContext) -> PathBuf;     fn load(&self, cx: &AppContext) -> Task<Result<String>>;     fn buffer_reloaded(        &self,        buffer_id: u64,        version: &clock::Global,        mtime: SystemTime,        cx: &mut MutableAppContext,    );} pub(crate) struct QueryCursorHandle(Option<QueryCursor>); #[derive(Clone)]struct SyntaxTree {    tree: Tree,    version: clock::Global,} #[derive(Clone)]struct AutoindentRequest {    before_edit: BufferSnapshot,    edited: Vec<Anchor>,    inserted: Option<Vec<Range<Anchor>>>,} #[derive(Debug)]struct IndentSuggestion {    basis_row: u32,    indent: bool,} struct TextProvider<'a>(&'a Rope); struct BufferChunkHighlights<'a> {    captures: tree_sitter::QueryCaptures<'a, 'a, TextProvider<'a>>,    next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>,    stack: Vec<(usize, HighlightId)>,    highlight_map: HighlightMap,    theme: &'a SyntaxTheme,    _query_cursor: QueryCursorHandle,} pub struct BufferChunks<'a> {    range: Range<usize>,    chunks: rope::Chunks<'a>,    diagnostic_endpoints: Peekable<vec::IntoIter<DiagnosticEndpoint>>,    error_depth: usize,    warning_depth: usize,    information_depth: usize,    hint_depth: usize,    highlights: Option<BufferChunkHighlights<'a>>,} #[derive(Clone, Copy, Debug, Default)]pub struct Chunk<'a> {    pub text: &'a str,    pub highlight_style: Option<HighlightStyle>,    pub diagnostic: Option<DiagnosticSeverity>,} pub(crate) struct Diff {    base_version: clock::Global,    new_text: Arc<str>,    changes: Vec<(ChangeTag, usize)>,} #[derive(Clone, Copy)]struct DiagnosticEndpoint {    offset: usize,    is_start: bool,    severity: DiagnosticSeverity,} impl Buffer {    pub fn new<T: Into<Arc<str>>>(        replica_id: ReplicaId,        base_text: T,        cx: &mut ModelContext<Self>,    ) -> Self {        Self::build(            TextBuffer::new(                replica_id,                cx.model_id() as u64,                History::new(base_text.into()),            ),            None,        )    }     pub fn from_file<T: Into<Arc<str>>>(        replica_id: ReplicaId,        base_text: T,        file: Box<dyn File>,        cx: &mut ModelContext<Self>,    ) -> Self {        Self::build(            TextBuffer::new(                replica_id,                cx.model_id() as u64,                History::new(base_text.into()),            ),            Some(file),        )    }     pub fn from_proto(        replica_id: ReplicaId,        message: proto::BufferState,        file: Option<Box<dyn File>>,        cx: &mut ModelContext<Self>,    ) -> Result<Self> {        let fragments_len = message.fragments.len();        let buffer = TextBuffer::from_parts(            replica_id,            message.id,            &message.visible_text,            &message.deleted_text,            message                .undo_map                .into_iter()                .map(proto::deserialize_undo_map_entry),            message                .fragments                .into_iter()                .enumerate()                .map(|(i, fragment)| {                    proto::deserialize_buffer_fragment(fragment, i, fragments_len)                }),            message.lamport_timestamp,            From::from(message.version),        );        let mut this = Self::build(buffer, file);        for selection_set in message.selections {            this.remote_selections.insert(                selection_set.replica_id as ReplicaId,                SelectionSet {                    selections: proto::deserialize_selections(selection_set.selections),                    lamport_timestamp: clock::Lamport {                        replica_id: selection_set.replica_id as ReplicaId,                        value: selection_set.lamport_timestamp,                    },                },            );        }        let snapshot = this.snapshot();        let entries = proto::deserialize_diagnostics(message.diagnostics);        this.apply_diagnostic_update(            DiagnosticSet::from_sorted_entries(entries.into_iter().cloned(), &snapshot),            cx,        );         let deferred_ops = message            .deferred_operations            .into_iter()            .map(proto::deserialize_operation)            .collect::<Result<Vec<_>>>()?;        this.apply_ops(deferred_ops, cx)?;         Ok(this)    }     pub fn to_proto(&self) -> proto::BufferState {        proto::BufferState {            id: self.remote_id(),            file: self.file.as_ref().map(|f| f.to_proto()),            visible_text: self.text.text(),            deleted_text: self.text.deleted_text(),            undo_map: self                .text                .undo_history()                .map(proto::serialize_undo_map_entry)                .collect(),            version: From::from(&self.version),            lamport_timestamp: self.lamport_clock.value,            fragments: self                .text                .fragments()                .map(proto::serialize_buffer_fragment)                .collect(),            selections: self                .remote_selections                .iter()                .map(|(replica_id, set)| proto::SelectionSet {                    replica_id: *replica_id as u32,                    selections: proto::serialize_selections(&set.selections),                    lamport_timestamp: set.lamport_timestamp.value,                })                .collect(),            diagnostics: proto::serialize_diagnostics(self.diagnostics.iter()),            deferred_operations: self                .deferred_ops                .iter()                .map(proto::serialize_operation)                .chain(                    self.text                        .deferred_ops()                        .map(|op| proto::serialize_operation(&Operation::Buffer(op.clone()))),                )                .collect(),        }    }     pub fn with_language(mut self, language: Arc<Language>, cx: &mut ModelContext<Self>) -> Self {        self.set_language(Some(language), cx);        self    }     pub fn with_language_server(        mut self,        server: Arc<LanguageServer>,        cx: &mut ModelContext<Self>,    ) -> Self {        self.set_language_server(Some(server), cx);        self    }     fn build(buffer: TextBuffer, file: Option<Box<dyn File>>) -> Self {        let saved_mtime;        if let Some(file) = file.as_ref() {            saved_mtime = file.mtime();        } else {            saved_mtime = UNIX_EPOCH;        }         Self {            saved_mtime,            saved_version: buffer.version(),            text: buffer,            file,            syntax_tree: Mutex::new(None),            parsing_in_background: false,            parse_count: 0,            sync_parse_timeout: Duration::from_millis(1),            autoindent_requests: Default::default(),            pending_autoindent: Default::default(),            language: None,            remote_selections: Default::default(),            selections_update_count: 0,            diagnostics: Default::default(),            diagnostics_update_count: 0,            language_server: None,            deferred_ops: OperationQueue::new(),            #[cfg(test)]            operations: Default::default(),        }    }     pub fn snapshot(&self) -> BufferSnapshot {        BufferSnapshot {            text: self.text.snapshot(),            tree: self.syntax_tree(),            remote_selections: self.remote_selections.clone(),            diagnostics: self.diagnostics.clone(),            diagnostics_update_count: self.diagnostics_update_count,            is_parsing: self.parsing_in_background,            language: self.language.clone(),            parse_count: self.parse_count,            selections_update_count: self.selections_update_count,        }    }     pub fn file(&self) -> Option<&dyn File> {        self.file.as_deref()    }     pub fn format(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {        let file = if let Some(file) = self.file.as_ref() {            file        } else {            return Task::ready(Err(anyhow!("buffer has no file")));        };         if let Some(LanguageServerState { server, .. }) = self.language_server.as_ref() {            let server = server.clone();            let abs_path = file.as_local().unwrap().abs_path(cx);            let version = self.version();            cx.spawn(|this, mut cx| async move {                let edits = server                    .request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {                        text_document: lsp::TextDocumentIdentifier::new(                            lsp::Url::from_file_path(&abs_path).unwrap(),                        ),                        options: Default::default(),                        work_done_progress_params: Default::default(),                    })                    .await?;                 if let Some(edits) = edits {                    this.update(&mut cx, |this, cx| {                        if this.version == version {                            for edit in &edits {                                let range = range_from_lsp(edit.range);                                if this.clip_point_utf16(range.start, Bias::Left) != range.start                                    || this.clip_point_utf16(range.end, Bias::Left) != range.end                                {                                    return Err(anyhow!(                                        "invalid formatting edits received from language server"                                    ));                                }                            }

Lines 1–500 of 2375.