Fix some issues with branch buffers (#18945)
* `Open Excerpts` command always opens the locations in the base buffer * LSP features like document-highlights, go-to-def, and inlay hints work correctly in branch buffers * Other LSP features like completions, code actions, and rename are disabled in branch buffers Release Notes: - N/A
This commit is contained in:
parent
cae548a50d
commit
53cc82b132
13 changed files with 554 additions and 267 deletions
|
@ -1560,7 +1560,7 @@ impl ContextEditor {
|
||||||
editor.set_show_runnables(false, cx);
|
editor.set_show_runnables(false, cx);
|
||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
editor.set_completion_provider(Box::new(completion_provider));
|
editor.set_completion_provider(Some(Box::new(completion_provider)));
|
||||||
editor.set_collaboration_hub(Box::new(project.clone()));
|
editor.set_collaboration_hub(Box::new(project.clone()));
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
|
|
@ -521,9 +521,9 @@ impl PromptLibrary {
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
editor.set_use_modal_editing(false);
|
editor.set_use_modal_editing(false);
|
||||||
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
|
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
|
||||||
editor.set_completion_provider(Box::new(
|
editor.set_completion_provider(Some(Box::new(
|
||||||
SlashCommandCompletionProvider::new(None, None),
|
SlashCommandCompletionProvider::new(None, None),
|
||||||
));
|
)));
|
||||||
if focus {
|
if focus {
|
||||||
editor.focus(cx);
|
editor.focus(cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ impl MessageEditor {
|
||||||
editor.set_show_gutter(false, cx);
|
editor.set_show_gutter(false, cx);
|
||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
editor.set_completion_provider(Box::new(MessageEditorCompletionProvider(this)));
|
editor.set_completion_provider(Some(Box::new(MessageEditorCompletionProvider(this))));
|
||||||
editor.set_auto_replace_emoji_shortcode(
|
editor.set_auto_replace_emoji_shortcode(
|
||||||
MessageEditorSettings::get_global(cx)
|
MessageEditorSettings::get_global(cx)
|
||||||
.auto_replace_emoji_shortcode
|
.auto_replace_emoji_shortcode
|
||||||
|
|
|
@ -121,10 +121,11 @@ use multi_buffer::{
|
||||||
};
|
};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use project::project_settings::{GitGutterSetting, ProjectSettings};
|
|
||||||
use project::{
|
use project::{
|
||||||
lsp_store::FormatTrigger, CodeAction, Completion, CompletionIntent, Item, Location, Project,
|
lsp_store::FormatTrigger,
|
||||||
ProjectPath, ProjectTransaction, TaskSourceKind,
|
project_settings::{GitGutterSetting, ProjectSettings},
|
||||||
|
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Item, Location,
|
||||||
|
LocationLink, Project, ProjectPath, ProjectTransaction, TaskSourceKind,
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rpc::{proto::*, ErrorExt};
|
use rpc::{proto::*, ErrorExt};
|
||||||
|
@ -546,6 +547,7 @@ pub struct Editor {
|
||||||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||||
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
|
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
|
||||||
project: Option<Model<Project>>,
|
project: Option<Model<Project>>,
|
||||||
|
semantics_provider: Option<Rc<dyn SemanticsProvider>>,
|
||||||
completion_provider: Option<Box<dyn CompletionProvider>>,
|
completion_provider: Option<Box<dyn CompletionProvider>>,
|
||||||
collaboration_hub: Option<Box<dyn CollaborationHub>>,
|
collaboration_hub: Option<Box<dyn CollaborationHub>>,
|
||||||
blink_manager: Model<BlinkManager>,
|
blink_manager: Model<BlinkManager>,
|
||||||
|
@ -884,12 +886,12 @@ enum ContextMenu {
|
||||||
impl ContextMenu {
|
impl ContextMenu {
|
||||||
fn select_first(
|
fn select_first(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Option<&Model<Project>>,
|
provider: Option<&dyn CompletionProvider>,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if self.visible() {
|
if self.visible() {
|
||||||
match self {
|
match self {
|
||||||
ContextMenu::Completions(menu) => menu.select_first(project, cx),
|
ContextMenu::Completions(menu) => menu.select_first(provider, cx),
|
||||||
ContextMenu::CodeActions(menu) => menu.select_first(cx),
|
ContextMenu::CodeActions(menu) => menu.select_first(cx),
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -900,12 +902,12 @@ impl ContextMenu {
|
||||||
|
|
||||||
fn select_prev(
|
fn select_prev(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Option<&Model<Project>>,
|
provider: Option<&dyn CompletionProvider>,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if self.visible() {
|
if self.visible() {
|
||||||
match self {
|
match self {
|
||||||
ContextMenu::Completions(menu) => menu.select_prev(project, cx),
|
ContextMenu::Completions(menu) => menu.select_prev(provider, cx),
|
||||||
ContextMenu::CodeActions(menu) => menu.select_prev(cx),
|
ContextMenu::CodeActions(menu) => menu.select_prev(cx),
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -916,12 +918,12 @@ impl ContextMenu {
|
||||||
|
|
||||||
fn select_next(
|
fn select_next(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Option<&Model<Project>>,
|
provider: Option<&dyn CompletionProvider>,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if self.visible() {
|
if self.visible() {
|
||||||
match self {
|
match self {
|
||||||
ContextMenu::Completions(menu) => menu.select_next(project, cx),
|
ContextMenu::Completions(menu) => menu.select_next(provider, cx),
|
||||||
ContextMenu::CodeActions(menu) => menu.select_next(cx),
|
ContextMenu::CodeActions(menu) => menu.select_next(cx),
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -932,12 +934,12 @@ impl ContextMenu {
|
||||||
|
|
||||||
fn select_last(
|
fn select_last(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Option<&Model<Project>>,
|
provider: Option<&dyn CompletionProvider>,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if self.visible() {
|
if self.visible() {
|
||||||
match self {
|
match self {
|
||||||
ContextMenu::Completions(menu) => menu.select_last(project, cx),
|
ContextMenu::Completions(menu) => menu.select_last(provider, cx),
|
||||||
ContextMenu::CodeActions(menu) => menu.select_last(cx),
|
ContextMenu::CodeActions(menu) => menu.select_last(cx),
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -991,39 +993,55 @@ struct CompletionsMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompletionsMenu {
|
impl CompletionsMenu {
|
||||||
fn select_first(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
|
fn select_first(
|
||||||
|
&mut self,
|
||||||
|
provider: Option<&dyn CompletionProvider>,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
self.selected_item = 0;
|
self.selected_item = 0;
|
||||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||||
self.attempt_resolve_selected_completion_documentation(project, cx);
|
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_prev(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
|
fn select_prev(
|
||||||
|
&mut self,
|
||||||
|
provider: Option<&dyn CompletionProvider>,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
if self.selected_item > 0 {
|
if self.selected_item > 0 {
|
||||||
self.selected_item -= 1;
|
self.selected_item -= 1;
|
||||||
} else {
|
} else {
|
||||||
self.selected_item = self.matches.len() - 1;
|
self.selected_item = self.matches.len() - 1;
|
||||||
}
|
}
|
||||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||||
self.attempt_resolve_selected_completion_documentation(project, cx);
|
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_next(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
|
fn select_next(
|
||||||
|
&mut self,
|
||||||
|
provider: Option<&dyn CompletionProvider>,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
if self.selected_item + 1 < self.matches.len() {
|
if self.selected_item + 1 < self.matches.len() {
|
||||||
self.selected_item += 1;
|
self.selected_item += 1;
|
||||||
} else {
|
} else {
|
||||||
self.selected_item = 0;
|
self.selected_item = 0;
|
||||||
}
|
}
|
||||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||||
self.attempt_resolve_selected_completion_documentation(project, cx);
|
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_last(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
|
fn select_last(
|
||||||
|
&mut self,
|
||||||
|
provider: Option<&dyn CompletionProvider>,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
self.selected_item = self.matches.len() - 1;
|
self.selected_item = self.matches.len() - 1;
|
||||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||||
self.attempt_resolve_selected_completion_documentation(project, cx);
|
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1059,7 +1077,7 @@ impl CompletionsMenu {
|
||||||
|
|
||||||
fn attempt_resolve_selected_completion_documentation(
|
fn attempt_resolve_selected_completion_documentation(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Option<&Model<Project>>,
|
provider: Option<&dyn CompletionProvider>,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) {
|
) {
|
||||||
let settings = EditorSettings::get_global(cx);
|
let settings = EditorSettings::get_global(cx);
|
||||||
|
@ -1068,18 +1086,16 @@ impl CompletionsMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
let completion_index = self.matches[self.selected_item].candidate_id;
|
let completion_index = self.matches[self.selected_item].candidate_id;
|
||||||
let Some(project) = project else {
|
let Some(provider) = provider else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolve_task = project.update(cx, |project, cx| {
|
let resolve_task = provider.resolve_completions(
|
||||||
project.resolve_completions(
|
self.buffer.clone(),
|
||||||
self.buffer.clone(),
|
vec![completion_index],
|
||||||
vec![completion_index],
|
self.completions.clone(),
|
||||||
self.completions.clone(),
|
cx,
|
||||||
cx,
|
);
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let delay_ms =
|
let delay_ms =
|
||||||
EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce;
|
EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce;
|
||||||
|
@ -1671,7 +1687,7 @@ pub(crate) struct NavigationData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum GotoDefinitionKind {
|
pub enum GotoDefinitionKind {
|
||||||
Symbol,
|
Symbol,
|
||||||
Declaration,
|
Declaration,
|
||||||
Type,
|
Type,
|
||||||
|
@ -1937,6 +1953,7 @@ impl Editor {
|
||||||
active_diagnostics: None,
|
active_diagnostics: None,
|
||||||
soft_wrap_mode_override,
|
soft_wrap_mode_override,
|
||||||
completion_provider: project.clone().map(|project| Box::new(project) as _),
|
completion_provider: project.clone().map(|project| Box::new(project) as _),
|
||||||
|
semantics_provider: project.clone().map(|project| Rc::new(project) as _),
|
||||||
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
|
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
|
||||||
project,
|
project,
|
||||||
blink_manager: blink_manager.clone(),
|
blink_manager: blink_manager.clone(),
|
||||||
|
@ -2305,8 +2322,16 @@ impl Editor {
|
||||||
self.custom_context_menu = Some(Box::new(f))
|
self.custom_context_menu = Some(Box::new(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_completion_provider(&mut self, provider: Box<dyn CompletionProvider>) {
|
pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
|
||||||
self.completion_provider = Some(provider);
|
self.completion_provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
|
||||||
|
self.semantics_provider.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
|
||||||
|
self.semantics_provider = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_inline_completion_provider<T>(
|
pub fn set_inline_completion_provider<T>(
|
||||||
|
@ -4041,7 +4066,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext<Self>) {
|
fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext<Self>) {
|
||||||
if self.project.is_none() || self.mode != EditorMode::Full {
|
if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4942,6 +4967,11 @@ impl Editor {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_code_action_providers(&mut self) {
|
||||||
|
self.code_action_providers.clear();
|
||||||
|
self.available_code_actions.take();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push_code_action_provider(
|
pub fn push_code_action_provider(
|
||||||
&mut self,
|
&mut self,
|
||||||
provider: Arc<dyn CodeActionProvider>,
|
provider: Arc<dyn CodeActionProvider>,
|
||||||
|
@ -5029,7 +5059,7 @@ impl Editor {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let project = self.project.clone()?;
|
let provider = self.semantics_provider.clone()?;
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
let newest_selection = self.selections.newest_anchor().clone();
|
let newest_selection = self.selections.newest_anchor().clone();
|
||||||
let cursor_position = newest_selection.head();
|
let cursor_position = newest_selection.head();
|
||||||
|
@ -5045,11 +5075,12 @@ impl Editor {
|
||||||
.timer(DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT)
|
.timer(DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let highlights = if let Some(highlights) = project
|
let highlights = if let Some(highlights) = cx
|
||||||
.update(&mut cx, |project, cx| {
|
.update(|cx| {
|
||||||
project.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
|
provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
|
||||||
})
|
})
|
||||||
.log_err()
|
.ok()
|
||||||
|
.flatten()
|
||||||
{
|
{
|
||||||
highlights.await.log_err()
|
highlights.await.log_err()
|
||||||
} else {
|
} else {
|
||||||
|
@ -7471,7 +7502,7 @@ impl Editor {
|
||||||
.context_menu
|
.context_menu
|
||||||
.write()
|
.write()
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|menu| menu.select_first(self.project.as_ref(), cx))
|
.map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -7580,7 +7611,7 @@ impl Editor {
|
||||||
.context_menu
|
.context_menu
|
||||||
.write()
|
.write()
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|menu| menu.select_last(self.project.as_ref(), cx))
|
.map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -7632,25 +7663,25 @@ impl Editor {
|
||||||
|
|
||||||
pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
|
pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(context_menu) = self.context_menu.write().as_mut() {
|
if let Some(context_menu) = self.context_menu.write().as_mut() {
|
||||||
context_menu.select_first(self.project.as_ref(), cx);
|
context_menu.select_first(self.completion_provider.as_deref(), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
|
pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(context_menu) = self.context_menu.write().as_mut() {
|
if let Some(context_menu) = self.context_menu.write().as_mut() {
|
||||||
context_menu.select_prev(self.project.as_ref(), cx);
|
context_menu.select_prev(self.completion_provider.as_deref(), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
|
pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(context_menu) = self.context_menu.write().as_mut() {
|
if let Some(context_menu) = self.context_menu.write().as_mut() {
|
||||||
context_menu.select_next(self.project.as_ref(), cx);
|
context_menu.select_next(self.completion_provider.as_deref(), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
|
pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(context_menu) = self.context_menu.write().as_mut() {
|
if let Some(context_menu) = self.context_menu.write().as_mut() {
|
||||||
context_menu.select_last(self.project.as_ref(), cx);
|
context_menu.select_last(self.completion_provider.as_deref(), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9623,7 +9654,7 @@ impl Editor {
|
||||||
split: bool,
|
split: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<Navigated>> {
|
) -> Task<Result<Navigated>> {
|
||||||
let Some(workspace) = self.workspace() else {
|
let Some(provider) = self.semantics_provider.clone() else {
|
||||||
return Task::ready(Ok(Navigated::No));
|
return Task::ready(Ok(Navigated::No));
|
||||||
};
|
};
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
|
@ -9634,13 +9665,9 @@ impl Editor {
|
||||||
return Task::ready(Ok(Navigated::No));
|
return Task::ready(Ok(Navigated::No));
|
||||||
};
|
};
|
||||||
|
|
||||||
let project = workspace.read(cx).project().clone();
|
let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
|
||||||
let definitions = project.update(cx, |project, cx| match kind {
|
return Task::ready(Ok(Navigated::No));
|
||||||
GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
|
};
|
||||||
GotoDefinitionKind::Declaration => project.declaration(&buffer, head, cx),
|
|
||||||
GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
|
|
||||||
GotoDefinitionKind::Implementation => project.implementation(&buffer, head, cx),
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn(|editor, mut cx| async move {
|
cx.spawn(|editor, mut cx| async move {
|
||||||
let definitions = definitions.await?;
|
let definitions = definitions.await?;
|
||||||
|
@ -9697,9 +9724,7 @@ impl Editor {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(project) = self.project.clone() else {
|
let project = self.project.clone();
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
let result = find_file(&buffer, project, buffer_position, &mut cx).await;
|
let result = find_file(&buffer, project, buffer_position, &mut cx).await;
|
||||||
|
@ -10101,7 +10126,7 @@ impl Editor {
|
||||||
pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
|
pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
|
||||||
use language::ToOffset as _;
|
use language::ToOffset as _;
|
||||||
|
|
||||||
let project = self.project.clone()?;
|
let provider = self.semantics_provider.clone()?;
|
||||||
let selection = self.selections.newest_anchor().clone();
|
let selection = self.selections.newest_anchor().clone();
|
||||||
let (cursor_buffer, cursor_buffer_position) = self
|
let (cursor_buffer, cursor_buffer_position) = self
|
||||||
.buffer
|
.buffer
|
||||||
|
@ -10118,9 +10143,9 @@ impl Editor {
|
||||||
let snapshot = cursor_buffer.read(cx).snapshot();
|
let snapshot = cursor_buffer.read(cx).snapshot();
|
||||||
let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
|
let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
|
||||||
let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
|
let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
|
||||||
let prepare_rename = project.update(cx, |project, cx| {
|
let prepare_rename = provider
|
||||||
project.prepare_rename(cursor_buffer.clone(), cursor_buffer_offset, cx)
|
.range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
|
||||||
});
|
.unwrap_or_else(|| Task::ready(Ok(None)));
|
||||||
drop(snapshot);
|
drop(snapshot);
|
||||||
|
|
||||||
Some(cx.spawn(|this, mut cx| async move {
|
Some(cx.spawn(|this, mut cx| async move {
|
||||||
|
@ -10291,32 +10316,28 @@ impl Editor {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<Task<Result<()>>> {
|
) -> Option<Task<Result<()>>> {
|
||||||
let rename = self.take_rename(false, cx)?;
|
let rename = self.take_rename(false, cx)?;
|
||||||
let workspace = self.workspace()?;
|
let workspace = self.workspace()?.downgrade();
|
||||||
let (start_buffer, start) = self
|
let (buffer, start) = self
|
||||||
.buffer
|
.buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.text_anchor_for_position(rename.range.start, cx)?;
|
.text_anchor_for_position(rename.range.start, cx)?;
|
||||||
let (end_buffer, end) = self
|
let (end_buffer, _) = self
|
||||||
.buffer
|
.buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.text_anchor_for_position(rename.range.end, cx)?;
|
.text_anchor_for_position(rename.range.end, cx)?;
|
||||||
if start_buffer != end_buffer {
|
if buffer != end_buffer {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let buffer = start_buffer;
|
|
||||||
let range = start..end;
|
|
||||||
let old_name = rename.old_name;
|
let old_name = rename.old_name;
|
||||||
let new_name = rename.editor.read(cx).text(cx);
|
let new_name = rename.editor.read(cx).text(cx);
|
||||||
|
|
||||||
let rename = workspace
|
let rename = self.semantics_provider.as_ref()?.perform_rename(
|
||||||
.read(cx)
|
&buffer,
|
||||||
.project()
|
start,
|
||||||
.clone()
|
new_name.clone(),
|
||||||
.update(cx, |project, cx| {
|
cx,
|
||||||
project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
|
)?;
|
||||||
});
|
|
||||||
let workspace = workspace.downgrade();
|
|
||||||
|
|
||||||
Some(cx.spawn(|editor, mut cx| async move {
|
Some(cx.spawn(|editor, mut cx| async move {
|
||||||
let project_transaction = rename.await?;
|
let project_transaction = rename.await?;
|
||||||
|
@ -12371,14 +12392,22 @@ impl Editor {
|
||||||
|
|
||||||
let mut new_selections_by_buffer = HashMap::default();
|
let mut new_selections_by_buffer = HashMap::default();
|
||||||
for selection in self.selections.all::<usize>(cx) {
|
for selection in self.selections.all::<usize>(cx) {
|
||||||
for (buffer, mut range, _) in
|
for (mut buffer_handle, mut range, _) in
|
||||||
buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
|
buffer.range_to_buffer_ranges(selection.range(), cx)
|
||||||
{
|
{
|
||||||
|
// When editing branch buffers, jump to the corresponding location
|
||||||
|
// in their base buffer.
|
||||||
|
let buffer = buffer_handle.read(cx);
|
||||||
|
if let Some(base_buffer) = buffer.diff_base_buffer() {
|
||||||
|
range = buffer.range_to_version(range, &base_buffer.read(cx).version());
|
||||||
|
buffer_handle = base_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
if selection.reversed {
|
if selection.reversed {
|
||||||
mem::swap(&mut range.start, &mut range.end);
|
mem::swap(&mut range.start, &mut range.end);
|
||||||
}
|
}
|
||||||
new_selections_by_buffer
|
new_selections_by_buffer
|
||||||
.entry(buffer)
|
.entry(buffer_handle)
|
||||||
.or_insert(Vec::new())
|
.or_insert(Vec::new())
|
||||||
.push(range)
|
.push(range)
|
||||||
}
|
}
|
||||||
|
@ -12663,24 +12692,13 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
|
pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
|
||||||
let Some(project) = self.project.as_ref() else {
|
let Some(provider) = self.semantics_provider.as_ref() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let project = project.read(cx);
|
|
||||||
|
|
||||||
let mut supports = false;
|
let mut supports = false;
|
||||||
self.buffer().read(cx).for_each_buffer(|buffer| {
|
self.buffer().read(cx).for_each_buffer(|buffer| {
|
||||||
if !supports {
|
supports |= provider.supports_inlay_hints(buffer, cx);
|
||||||
supports = project
|
|
||||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
|
||||||
.any(
|
|
||||||
|(_, server)| match server.capabilities().inlay_hint_provider {
|
|
||||||
Some(lsp::OneOf::Left(enabled)) => enabled,
|
|
||||||
Some(lsp::OneOf::Right(_)) => true,
|
|
||||||
None => false,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
supports
|
supports
|
||||||
}
|
}
|
||||||
|
@ -12946,6 +12964,62 @@ impl CollaborationHub for Model<Project> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait SemanticsProvider {
|
||||||
|
fn hover(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Vec<project::Hover>>>;
|
||||||
|
|
||||||
|
fn inlay_hints(
|
||||||
|
&self,
|
||||||
|
buffer_handle: Model<Buffer>,
|
||||||
|
range: Range<text::Anchor>,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
|
||||||
|
|
||||||
|
fn resolve_inlay_hint(
|
||||||
|
&self,
|
||||||
|
hint: InlayHint,
|
||||||
|
buffer_handle: Model<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<anyhow::Result<InlayHint>>>;
|
||||||
|
|
||||||
|
fn supports_inlay_hints(&self, buffer: &Model<Buffer>, cx: &AppContext) -> bool;
|
||||||
|
|
||||||
|
fn document_highlights(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
|
||||||
|
|
||||||
|
fn definitions(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
kind: GotoDefinitionKind,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Result<Vec<LocationLink>>>>;
|
||||||
|
|
||||||
|
fn range_for_rename(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
|
||||||
|
|
||||||
|
fn perform_rename(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
new_name: String,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Result<ProjectTransaction>>>;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait CompletionProvider {
|
pub trait CompletionProvider {
|
||||||
fn completions(
|
fn completions(
|
||||||
&self,
|
&self,
|
||||||
|
@ -13197,6 +13271,102 @@ impl CompletionProvider for Model<Project> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SemanticsProvider for Model<Project> {
|
||||||
|
fn hover(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Vec<project::Hover>>> {
|
||||||
|
Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn document_highlights(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
|
||||||
|
Some(self.update(cx, |project, cx| {
|
||||||
|
project.document_highlights(buffer, position, cx)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn definitions(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
kind: GotoDefinitionKind,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Result<Vec<LocationLink>>>> {
|
||||||
|
Some(self.update(cx, |project, cx| match kind {
|
||||||
|
GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
|
||||||
|
GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
|
||||||
|
GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
|
||||||
|
GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_inlay_hints(&self, buffer: &Model<Buffer>, cx: &AppContext) -> bool {
|
||||||
|
// TODO: make this work for remote projects
|
||||||
|
self.read(cx)
|
||||||
|
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||||
|
.any(
|
||||||
|
|(_, server)| match server.capabilities().inlay_hint_provider {
|
||||||
|
Some(lsp::OneOf::Left(enabled)) => enabled,
|
||||||
|
Some(lsp::OneOf::Right(_)) => true,
|
||||||
|
None => false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inlay_hints(
|
||||||
|
&self,
|
||||||
|
buffer_handle: Model<Buffer>,
|
||||||
|
range: Range<text::Anchor>,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
|
||||||
|
Some(self.update(cx, |project, cx| {
|
||||||
|
project.inlay_hints(buffer_handle, range, cx)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_inlay_hint(
|
||||||
|
&self,
|
||||||
|
hint: InlayHint,
|
||||||
|
buffer_handle: Model<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<anyhow::Result<InlayHint>>> {
|
||||||
|
Some(self.update(cx, |project, cx| {
|
||||||
|
project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn range_for_rename(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
|
||||||
|
Some(self.update(cx, |project, cx| {
|
||||||
|
project.prepare_rename(buffer.clone(), position, cx)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_rename(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
new_name: String,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Result<ProjectTransaction>>> {
|
||||||
|
Some(self.update(cx, |project, cx| {
|
||||||
|
project.perform_rename(buffer.clone(), position, new_name, cx)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn inlay_hint_settings(
|
fn inlay_hint_settings(
|
||||||
location: Anchor,
|
location: Anchor,
|
||||||
snapshot: &MultiBufferSnapshot,
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
hover_popover::{self, InlayHover},
|
hover_popover::{self, InlayHover},
|
||||||
scroll::ScrollAmount,
|
scroll::ScrollAmount,
|
||||||
Anchor, Editor, EditorSnapshot, FindAllReferences, GoToDefinition, GoToTypeDefinition, InlayId,
|
Anchor, Editor, EditorSnapshot, FindAllReferences, GoToDefinition, GoToTypeDefinition,
|
||||||
Navigated, PointForPosition, SelectPhase,
|
GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase,
|
||||||
};
|
};
|
||||||
use gpui::{px, AppContext, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
|
use gpui::{px, AppContext, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
|
||||||
use language::{Bias, ToOffset};
|
use language::{Bias, ToOffset};
|
||||||
|
@ -14,12 +14,12 @@ use project::{
|
||||||
};
|
};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use theme::ActiveTheme as _;
|
use theme::ActiveTheme as _;
|
||||||
use util::{maybe, ResultExt, TryFutureExt};
|
use util::{maybe, ResultExt, TryFutureExt as _};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HoveredLinkState {
|
pub struct HoveredLinkState {
|
||||||
pub last_trigger_point: TriggerPoint,
|
pub last_trigger_point: TriggerPoint,
|
||||||
pub preferred_kind: LinkDefinitionKind,
|
pub preferred_kind: GotoDefinitionKind,
|
||||||
pub symbol_range: Option<RangeInEditor>,
|
pub symbol_range: Option<RangeInEditor>,
|
||||||
pub links: Vec<HoverLink>,
|
pub links: Vec<HoverLink>,
|
||||||
pub task: Option<Task<Option<()>>>,
|
pub task: Option<Task<Option<()>>>,
|
||||||
|
@ -428,12 +428,6 @@ pub fn update_inlay_link_and_hover_points(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum LinkDefinitionKind {
|
|
||||||
Symbol,
|
|
||||||
Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_link_definition(
|
pub fn show_link_definition(
|
||||||
shift_held: bool,
|
shift_held: bool,
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
|
@ -442,8 +436,8 @@ pub fn show_link_definition(
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) {
|
) {
|
||||||
let preferred_kind = match trigger_point {
|
let preferred_kind = match trigger_point {
|
||||||
TriggerPoint::Text(_) if !shift_held => LinkDefinitionKind::Symbol,
|
TriggerPoint::Text(_) if !shift_held => GotoDefinitionKind::Symbol,
|
||||||
_ => LinkDefinitionKind::Type,
|
_ => GotoDefinitionKind::Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut hovered_link_state, is_cached) =
|
let (mut hovered_link_state, is_cached) =
|
||||||
|
@ -505,6 +499,7 @@ pub fn show_link_definition(
|
||||||
editor.hide_hovered_link(cx)
|
editor.hide_hovered_link(cx)
|
||||||
}
|
}
|
||||||
let project = editor.project.clone();
|
let project = editor.project.clone();
|
||||||
|
let provider = editor.semantics_provider.clone();
|
||||||
|
|
||||||
let snapshot = snapshot.buffer_snapshot.clone();
|
let snapshot = snapshot.buffer_snapshot.clone();
|
||||||
hovered_link_state.task = Some(cx.spawn(|this, mut cx| {
|
hovered_link_state.task = Some(cx.spawn(|this, mut cx| {
|
||||||
|
@ -522,54 +517,40 @@ pub fn show_link_definition(
|
||||||
(range, vec![HoverLink::Url(url)])
|
(range, vec![HoverLink::Url(url)])
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
} else if let Some(project) = project {
|
} else if let Some((filename_range, filename)) =
|
||||||
if let Some((filename_range, filename)) =
|
find_file(&buffer, project.clone(), buffer_position, &mut cx).await
|
||||||
find_file(&buffer, project.clone(), buffer_position, &mut cx).await
|
{
|
||||||
{
|
let range = maybe!({
|
||||||
let range = maybe!({
|
let start =
|
||||||
let start =
|
snapshot.anchor_in_excerpt(excerpt_id, filename_range.start)?;
|
||||||
snapshot.anchor_in_excerpt(excerpt_id, filename_range.start)?;
|
let end = snapshot.anchor_in_excerpt(excerpt_id, filename_range.end)?;
|
||||||
let end =
|
Some(RangeInEditor::Text(start..end))
|
||||||
snapshot.anchor_in_excerpt(excerpt_id, filename_range.end)?;
|
});
|
||||||
Some(RangeInEditor::Text(start..end))
|
|
||||||
});
|
|
||||||
|
|
||||||
Some((range, vec![HoverLink::File(filename)]))
|
Some((range, vec![HoverLink::File(filename)]))
|
||||||
|
} else if let Some(provider) = provider {
|
||||||
|
let task = cx.update(|cx| {
|
||||||
|
provider.definitions(&buffer, buffer_position, preferred_kind, cx)
|
||||||
|
})?;
|
||||||
|
if let Some(task) = task {
|
||||||
|
task.await.ok().map(|definition_result| {
|
||||||
|
(
|
||||||
|
definition_result.iter().find_map(|link| {
|
||||||
|
link.origin.as_ref().and_then(|origin| {
|
||||||
|
let start = snapshot.anchor_in_excerpt(
|
||||||
|
excerpt_id,
|
||||||
|
origin.range.start,
|
||||||
|
)?;
|
||||||
|
let end = snapshot
|
||||||
|
.anchor_in_excerpt(excerpt_id, origin.range.end)?;
|
||||||
|
Some(RangeInEditor::Text(start..end))
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
definition_result.into_iter().map(HoverLink::Text).collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// query the LSP for definition info
|
None
|
||||||
project
|
|
||||||
.update(&mut cx, |project, cx| match preferred_kind {
|
|
||||||
LinkDefinitionKind::Symbol => {
|
|
||||||
project.definition(&buffer, buffer_position, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkDefinitionKind::Type => {
|
|
||||||
project.type_definition(&buffer, buffer_position, cx)
|
|
||||||
}
|
|
||||||
})?
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.map(|definition_result| {
|
|
||||||
(
|
|
||||||
definition_result.iter().find_map(|link| {
|
|
||||||
link.origin.as_ref().and_then(|origin| {
|
|
||||||
let start = snapshot.anchor_in_excerpt(
|
|
||||||
excerpt_id,
|
|
||||||
origin.range.start,
|
|
||||||
)?;
|
|
||||||
let end = snapshot.anchor_in_excerpt(
|
|
||||||
excerpt_id,
|
|
||||||
origin.range.end,
|
|
||||||
)?;
|
|
||||||
Some(RangeInEditor::Text(start..end))
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
definition_result
|
|
||||||
.into_iter()
|
|
||||||
.map(HoverLink::Text)
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -708,10 +689,11 @@ pub(crate) fn find_url(
|
||||||
|
|
||||||
pub(crate) async fn find_file(
|
pub(crate) async fn find_file(
|
||||||
buffer: &Model<language::Buffer>,
|
buffer: &Model<language::Buffer>,
|
||||||
project: Model<Project>,
|
project: Option<Model<Project>>,
|
||||||
position: text::Anchor,
|
position: text::Anchor,
|
||||||
cx: &mut AsyncWindowContext,
|
cx: &mut AsyncWindowContext,
|
||||||
) -> Option<(Range<text::Anchor>, ResolvedPath)> {
|
) -> Option<(Range<text::Anchor>, ResolvedPath)> {
|
||||||
|
let project = project?;
|
||||||
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()).ok()?;
|
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()).ok()?;
|
||||||
let scope = snapshot.language_scope_at(position);
|
let scope = snapshot.language_scope_at(position);
|
||||||
let (range, candidate_file_path) = surrounding_filename(snapshot, position)?;
|
let (range, candidate_file_path) = surrounding_filename(snapshot, position)?;
|
||||||
|
|
|
@ -195,32 +195,22 @@ fn show_hover(
|
||||||
anchor: Anchor,
|
anchor: Anchor,
|
||||||
ignore_timeout: bool,
|
ignore_timeout: bool,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) {
|
) -> Option<()> {
|
||||||
if editor.pending_rename.is_some() {
|
if editor.pending_rename.is_some() {
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let snapshot = editor.snapshot(cx);
|
let snapshot = editor.snapshot(cx);
|
||||||
|
|
||||||
let (buffer, buffer_position) =
|
let (buffer, buffer_position) = editor
|
||||||
if let Some(output) = editor.buffer.read(cx).text_anchor_for_position(anchor, cx) {
|
.buffer
|
||||||
output
|
.read(cx)
|
||||||
} else {
|
.text_anchor_for_position(anchor, cx)?;
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let excerpt_id =
|
let (excerpt_id, _, _) = editor.buffer().read(cx).excerpt_containing(anchor, cx)?;
|
||||||
if let Some((excerpt_id, _, _)) = editor.buffer().read(cx).excerpt_containing(anchor, cx) {
|
|
||||||
excerpt_id
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let project = if let Some(project) = editor.project.clone() {
|
let language_registry = editor.project.as_ref()?.read(cx).languages().clone();
|
||||||
project
|
let provider = editor.semantics_provider.clone()?;
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !ignore_timeout {
|
if !ignore_timeout {
|
||||||
if same_info_hover(editor, &snapshot, anchor)
|
if same_info_hover(editor, &snapshot, anchor)
|
||||||
|
@ -228,7 +218,7 @@ fn show_hover(
|
||||||
|| editor.hover_state.diagnostic_popover.is_some()
|
|| editor.hover_state.diagnostic_popover.is_some()
|
||||||
{
|
{
|
||||||
// Hover triggered from same location as last time. Don't show again.
|
// Hover triggered from same location as last time. Don't show again.
|
||||||
return;
|
return None;
|
||||||
} else {
|
} else {
|
||||||
hide_hover(editor, cx);
|
hide_hover(editor, cx);
|
||||||
}
|
}
|
||||||
|
@ -240,7 +230,7 @@ fn show_hover(
|
||||||
.cmp(&anchor, &snapshot.buffer_snapshot)
|
.cmp(&anchor, &snapshot.buffer_snapshot)
|
||||||
.is_eq()
|
.is_eq()
|
||||||
{
|
{
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,12 +252,7 @@ fn show_hover(
|
||||||
total_delay
|
total_delay
|
||||||
};
|
};
|
||||||
|
|
||||||
// query the LSP for hover info
|
let hover_request = cx.update(|cx| provider.hover(&buffer, buffer_position, cx))?;
|
||||||
let hover_request = cx.update(|cx| {
|
|
||||||
project.update(cx, |project, cx| {
|
|
||||||
project.hover(&buffer, buffer_position, cx)
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(delay) = delay {
|
if let Some(delay) = delay {
|
||||||
delay.await;
|
delay.await;
|
||||||
|
@ -377,8 +362,11 @@ fn show_hover(
|
||||||
this.hover_state.diagnostic_popover = diagnostic_popover;
|
this.hover_state.diagnostic_popover = diagnostic_popover;
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let hovers_response = hover_request.await;
|
let hovers_response = if let Some(hover_request) = hover_request {
|
||||||
let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
|
hover_request.await
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
|
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
|
||||||
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
|
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
|
||||||
let mut info_popovers = Vec::with_capacity(hovers_response.len());
|
let mut info_popovers = Vec::with_capacity(hovers_response.len());
|
||||||
|
@ -451,6 +439,7 @@ fn show_hover(
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.hover_state.info_task = Some(task);
|
editor.hover_state.info_task = Some(task);
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn same_info_hover(editor: &Editor, snapshot: &EditorSnapshot, anchor: Anchor) -> bool {
|
fn same_info_hover(editor: &Editor, snapshot: &EditorSnapshot, anchor: Anchor) -> bool {
|
||||||
|
|
|
@ -591,21 +591,13 @@ impl InlayHintCache {
|
||||||
drop(guard);
|
drop(guard);
|
||||||
cx.spawn(|editor, mut cx| async move {
|
cx.spawn(|editor, mut cx| async move {
|
||||||
let resolved_hint_task = editor.update(&mut cx, |editor, cx| {
|
let resolved_hint_task = editor.update(&mut cx, |editor, cx| {
|
||||||
editor
|
let buffer = editor.buffer().read(cx).buffer(buffer_id)?;
|
||||||
.buffer()
|
editor.semantics_provider.as_ref()?.resolve_inlay_hint(
|
||||||
.read(cx)
|
hint_to_resolve,
|
||||||
.buffer(buffer_id)
|
buffer,
|
||||||
.and_then(|buffer| {
|
server_id,
|
||||||
let project = editor.project.as_ref()?;
|
cx,
|
||||||
Some(project.update(cx, |project, cx| {
|
)
|
||||||
project.resolve_inlay_hint(
|
|
||||||
hint_to_resolve,
|
|
||||||
buffer,
|
|
||||||
server_id,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
})?;
|
})?;
|
||||||
if let Some(resolved_hint_task) = resolved_hint_task {
|
if let Some(resolved_hint_task) = resolved_hint_task {
|
||||||
let mut resolved_hint =
|
let mut resolved_hint =
|
||||||
|
@ -895,11 +887,13 @@ fn fetch_and_update_hints(
|
||||||
) -> Task<anyhow::Result<()>> {
|
) -> Task<anyhow::Result<()>> {
|
||||||
cx.spawn(|editor, mut cx| async move {
|
cx.spawn(|editor, mut cx| async move {
|
||||||
let buffer_snapshot = excerpt_buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
|
let buffer_snapshot = excerpt_buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
|
||||||
let (lsp_request_limiter, multi_buffer_snapshot) = editor.update(&mut cx, |editor, cx| {
|
let (lsp_request_limiter, multi_buffer_snapshot) =
|
||||||
let multi_buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
|
editor.update(&mut cx, |editor, cx| {
|
||||||
let lsp_request_limiter = Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter);
|
let multi_buffer_snapshot =
|
||||||
(lsp_request_limiter, multi_buffer_snapshot)
|
editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
|
||||||
})?;
|
let lsp_request_limiter = Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter);
|
||||||
|
(lsp_request_limiter, multi_buffer_snapshot)
|
||||||
|
})?;
|
||||||
|
|
||||||
let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() {
|
let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() {
|
||||||
(None, false)
|
(None, false)
|
||||||
|
@ -909,12 +903,15 @@ fn fetch_and_update_hints(
|
||||||
None => (Some(lsp_request_limiter.acquire().await), true),
|
None => (Some(lsp_request_limiter.acquire().await), true),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let fetch_range_to_log =
|
let fetch_range_to_log = fetch_range.start.to_point(&buffer_snapshot)
|
||||||
fetch_range.start.to_point(&buffer_snapshot)..fetch_range.end.to_point(&buffer_snapshot);
|
..fetch_range.end.to_point(&buffer_snapshot);
|
||||||
let inlay_hints_fetch_task = editor
|
let inlay_hints_fetch_task = editor
|
||||||
.update(&mut cx, |editor, cx| {
|
.update(&mut cx, |editor, cx| {
|
||||||
if got_throttled {
|
if got_throttled {
|
||||||
let query_not_around_visible_range = match editor.excerpts_for_inlay_hints_query(None, cx).remove(&query.excerpt_id) {
|
let query_not_around_visible_range = match editor
|
||||||
|
.excerpts_for_inlay_hints_query(None, cx)
|
||||||
|
.remove(&query.excerpt_id)
|
||||||
|
{
|
||||||
Some((_, _, current_visible_range)) => {
|
Some((_, _, current_visible_range)) => {
|
||||||
let visible_offset_length = current_visible_range.len();
|
let visible_offset_length = current_visible_range.len();
|
||||||
let double_visible_range = current_visible_range
|
let double_visible_range = current_visible_range
|
||||||
|
@ -928,11 +925,11 @@ fn fetch_and_update_hints(
|
||||||
.contains(&fetch_range.start.to_offset(&buffer_snapshot))
|
.contains(&fetch_range.start.to_offset(&buffer_snapshot))
|
||||||
&& !double_visible_range
|
&& !double_visible_range
|
||||||
.contains(&fetch_range.end.to_offset(&buffer_snapshot))
|
.contains(&fetch_range.end.to_offset(&buffer_snapshot))
|
||||||
},
|
}
|
||||||
None => true,
|
None => true,
|
||||||
};
|
};
|
||||||
if query_not_around_visible_range {
|
if query_not_around_visible_range {
|
||||||
log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
|
// log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
|
||||||
if let Some(task_ranges) = editor
|
if let Some(task_ranges) = editor
|
||||||
.inlay_hint_cache
|
.inlay_hint_cache
|
||||||
.update_tasks
|
.update_tasks
|
||||||
|
@ -943,16 +940,12 @@ fn fetch_and_update_hints(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let buffer = editor.buffer().read(cx).buffer(query.buffer_id)?;
|
||||||
editor
|
editor
|
||||||
.buffer()
|
.semantics_provider
|
||||||
.read(cx)
|
.as_ref()?
|
||||||
.buffer(query.buffer_id)
|
.inlay_hints(buffer, fetch_range.clone(), cx)
|
||||||
.and_then(|buffer| {
|
|
||||||
let project = editor.project.as_ref()?;
|
|
||||||
Some(project.update(cx, |project, cx| {
|
|
||||||
project.inlay_hints(buffer, fetch_range.clone(), cx)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
.flatten();
|
.flatten();
|
||||||
|
@ -1004,12 +997,12 @@ fn fetch_and_update_hints(
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
if let Some(new_update) = new_update {
|
if let Some(new_update) = new_update {
|
||||||
log::debug!(
|
// log::debug!(
|
||||||
"Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}",
|
// "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}",
|
||||||
new_update.remove_from_visible.len(),
|
// new_update.remove_from_visible.len(),
|
||||||
new_update.remove_from_cache.len(),
|
// new_update.remove_from_cache.len(),
|
||||||
new_update.add_to_cache.len()
|
// new_update.add_to_cache.len()
|
||||||
);
|
// );
|
||||||
log::trace!("New update: {new_update:?}");
|
log::trace!("New update: {new_update:?}");
|
||||||
editor
|
editor
|
||||||
.update(&mut cx, |editor, cx| {
|
.update(&mut cx, |editor, cx| {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Editor, EditorEvent};
|
use crate::{Editor, EditorEvent, SemanticsProvider};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use futures::{channel::mpsc, future::join_all};
|
use futures::{channel::mpsc, future::join_all};
|
||||||
use gpui::{AppContext, EventEmitter, FocusableView, Model, Render, Subscription, Task, View};
|
use gpui::{AppContext, EventEmitter, FocusableView, Model, Render, Subscription, Task, View};
|
||||||
|
@ -6,7 +6,7 @@ use language::{Buffer, BufferEvent, Capability};
|
||||||
use multi_buffer::{ExcerptRange, MultiBuffer};
|
use multi_buffer::{ExcerptRange, MultiBuffer};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::{any::TypeId, ops::Range, time::Duration};
|
use std::{any::TypeId, ops::Range, rc::Rc, time::Duration};
|
||||||
use text::ToOffset;
|
use text::ToOffset;
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
@ -35,6 +35,12 @@ struct RecalculateDiff {
|
||||||
debounce: bool,
|
debounce: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A provider of code semantics for branch buffers.
|
||||||
|
///
|
||||||
|
/// Requests in edited regions will return nothing, but requests in unchanged
|
||||||
|
/// regions will be translated into the base buffer's coordinates.
|
||||||
|
struct BranchBufferSemanticsProvider(Rc<dyn SemanticsProvider>);
|
||||||
|
|
||||||
impl ProposedChangesEditor {
|
impl ProposedChangesEditor {
|
||||||
pub fn new<T: ToOffset>(
|
pub fn new<T: ToOffset>(
|
||||||
buffers: Vec<ProposedChangesBuffer<T>>,
|
buffers: Vec<ProposedChangesBuffer<T>>,
|
||||||
|
@ -66,6 +72,13 @@ impl ProposedChangesEditor {
|
||||||
editor: cx.new_view(|cx| {
|
editor: cx.new_view(|cx| {
|
||||||
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx);
|
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx);
|
||||||
editor.set_expand_all_diff_hunks();
|
editor.set_expand_all_diff_hunks();
|
||||||
|
editor.set_completion_provider(None);
|
||||||
|
editor.clear_code_action_providers();
|
||||||
|
editor.set_semantics_provider(
|
||||||
|
editor
|
||||||
|
.semantics_provider()
|
||||||
|
.map(|provider| Rc::new(BranchBufferSemanticsProvider(provider)) as _),
|
||||||
|
);
|
||||||
editor
|
editor
|
||||||
}),
|
}),
|
||||||
recalculate_diffs_tx,
|
recalculate_diffs_tx,
|
||||||
|
@ -76,7 +89,7 @@ impl ProposedChangesEditor {
|
||||||
|
|
||||||
while recalculate_diff.debounce {
|
while recalculate_diff.debounce {
|
||||||
cx.background_executor()
|
cx.background_executor()
|
||||||
.timer(Duration::from_millis(250))
|
.timer(Duration::from_millis(50))
|
||||||
.await;
|
.await;
|
||||||
let mut had_further_changes = false;
|
let mut had_further_changes = false;
|
||||||
while let Ok(next_recalculate_diff) = recalculate_diffs_rx.try_next() {
|
while let Ok(next_recalculate_diff) = recalculate_diffs_rx.try_next() {
|
||||||
|
@ -245,3 +258,103 @@ impl ToolbarItemView for ProposedChangesEditorToolbar {
|
||||||
self.get_toolbar_item_location()
|
self.get_toolbar_item_location()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BranchBufferSemanticsProvider {
|
||||||
|
fn to_base(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
positions: &[text::Anchor],
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> Option<Model<Buffer>> {
|
||||||
|
let base_buffer = buffer.read(cx).diff_base_buffer()?;
|
||||||
|
let version = base_buffer.read(cx).version();
|
||||||
|
if positions
|
||||||
|
.iter()
|
||||||
|
.any(|position| !version.observed(position.timestamp))
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(base_buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SemanticsProvider for BranchBufferSemanticsProvider {
|
||||||
|
fn hover(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<Vec<project::Hover>>> {
|
||||||
|
let buffer = self.to_base(buffer, &[position], cx)?;
|
||||||
|
self.0.hover(&buffer, position, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inlay_hints(
|
||||||
|
&self,
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
range: Range<text::Anchor>,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<anyhow::Result<Vec<project::InlayHint>>>> {
|
||||||
|
let buffer = self.to_base(&buffer, &[range.start, range.end], cx)?;
|
||||||
|
self.0.inlay_hints(buffer, range, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_inlay_hint(
|
||||||
|
&self,
|
||||||
|
hint: project::InlayHint,
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
server_id: lsp::LanguageServerId,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<anyhow::Result<project::InlayHint>>> {
|
||||||
|
let buffer = self.to_base(&buffer, &[], cx)?;
|
||||||
|
self.0.resolve_inlay_hint(hint, buffer, server_id, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_inlay_hints(&self, buffer: &Model<Buffer>, cx: &AppContext) -> bool {
|
||||||
|
if let Some(buffer) = self.to_base(&buffer, &[], cx) {
|
||||||
|
self.0.supports_inlay_hints(&buffer, cx)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn document_highlights(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<gpui::Result<Vec<project::DocumentHighlight>>>> {
|
||||||
|
let buffer = self.to_base(&buffer, &[position], cx)?;
|
||||||
|
self.0.document_highlights(&buffer, position, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn definitions(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<Buffer>,
|
||||||
|
position: text::Anchor,
|
||||||
|
kind: crate::GotoDefinitionKind,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Task<gpui::Result<Vec<project::LocationLink>>>> {
|
||||||
|
let buffer = self.to_base(&buffer, &[position], cx)?;
|
||||||
|
self.0.definitions(&buffer, position, kind, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn range_for_rename(
|
||||||
|
&self,
|
||||||
|
_: &Model<Buffer>,
|
||||||
|
_: text::Anchor,
|
||||||
|
_: &mut AppContext,
|
||||||
|
) -> Option<Task<gpui::Result<Option<Range<text::Anchor>>>>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_rename(
|
||||||
|
&self,
|
||||||
|
_: &Model<Buffer>,
|
||||||
|
_: text::Anchor,
|
||||||
|
_: String,
|
||||||
|
_: &mut AppContext,
|
||||||
|
) -> Option<Task<gpui::Result<project::ProjectTransaction>>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2381,20 +2381,14 @@ async fn test_find_matching_indent(cx: &mut TestAppContext) {
|
||||||
fn test_branch_and_merge(cx: &mut TestAppContext) {
|
fn test_branch_and_merge(cx: &mut TestAppContext) {
|
||||||
cx.update(|cx| init_settings(cx, |_| {}));
|
cx.update(|cx| init_settings(cx, |_| {}));
|
||||||
|
|
||||||
let base_buffer = cx.new_model(|cx| Buffer::local("one\ntwo\nthree\n", cx));
|
let base = cx.new_model(|cx| Buffer::local("one\ntwo\nthree\n", cx));
|
||||||
|
|
||||||
// Create a remote replica of the base buffer.
|
// Create a remote replica of the base buffer.
|
||||||
let base_buffer_replica = cx.new_model(|cx| {
|
let base_replica = cx.new_model(|cx| {
|
||||||
Buffer::from_proto(
|
Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
|
||||||
1,
|
|
||||||
Capability::ReadWrite,
|
|
||||||
base_buffer.read(cx).to_proto(cx),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
base_buffer.update(cx, |_buffer, cx| {
|
base.update(cx, |_buffer, cx| {
|
||||||
cx.subscribe(&base_buffer_replica, |this, _, event, cx| {
|
cx.subscribe(&base_replica, |this, _, event, cx| {
|
||||||
if let BufferEvent::Operation {
|
if let BufferEvent::Operation {
|
||||||
operation,
|
operation,
|
||||||
is_local: true,
|
is_local: true,
|
||||||
|
@ -2407,14 +2401,14 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a branch, which initially has the same state as the base buffer.
|
// Create a branch, which initially has the same state as the base buffer.
|
||||||
let branch_buffer = base_buffer.update(cx, |buffer, cx| buffer.branch(cx));
|
let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
|
||||||
branch_buffer.read_with(cx, |buffer, _| {
|
branch.read_with(cx, |buffer, _| {
|
||||||
assert_eq!(buffer.text(), "one\ntwo\nthree\n");
|
assert_eq!(buffer.text(), "one\ntwo\nthree\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Edits to the branch are not applied to the base.
|
// Edits to the branch are not applied to the base.
|
||||||
branch_buffer.update(cx, |branch_buffer, cx| {
|
branch.update(cx, |buffer, cx| {
|
||||||
branch_buffer.edit(
|
buffer.edit(
|
||||||
[
|
[
|
||||||
(Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
|
(Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
|
||||||
(Point::new(2, 0)..Point::new(2, 5), "THREE"),
|
(Point::new(2, 0)..Point::new(2, 5), "THREE"),
|
||||||
|
@ -2423,64 +2417,74 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
branch.read_with(cx, |buffer, cx| {
|
||||||
assert_eq!(base_buffer.read(cx).text(), "one\ntwo\nthree\n");
|
assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
|
||||||
assert_eq!(branch_buffer.text(), "one\n1.5\ntwo\nTHREE\n");
|
assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert from branch buffer ranges to the corresoponing ranges in the
|
||||||
|
// base buffer.
|
||||||
|
branch.read_with(cx, |buffer, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
buffer.range_to_version(4..7, &base.read(cx).version()),
|
||||||
|
4..4
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
buffer.range_to_version(2..9, &base.read(cx).version()),
|
||||||
|
2..5
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// The branch buffer maintains a diff with respect to its base buffer.
|
// The branch buffer maintains a diff with respect to its base buffer.
|
||||||
start_recalculating_diff(&branch_buffer, cx);
|
start_recalculating_diff(&branch, cx);
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
assert_diff_hunks(
|
assert_diff_hunks(
|
||||||
&branch_buffer,
|
&branch,
|
||||||
cx,
|
cx,
|
||||||
&[(1..2, "", "1.5\n"), (3..4, "three\n", "THREE\n")],
|
&[(1..2, "", "1.5\n"), (3..4, "three\n", "THREE\n")],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Edits to the base are applied to the branch.
|
// Edits to the base are applied to the branch.
|
||||||
base_buffer.update(cx, |buffer, cx| {
|
base.update(cx, |buffer, cx| {
|
||||||
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
|
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
|
||||||
});
|
});
|
||||||
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
branch.read_with(cx, |buffer, cx| {
|
||||||
assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\nthree\n");
|
assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
|
||||||
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
|
assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Until the git diff recalculation is complete, the git diff references
|
// Until the git diff recalculation is complete, the git diff references
|
||||||
// the previous content of the base buffer, so that it stays in sync.
|
// the previous content of the base buffer, so that it stays in sync.
|
||||||
start_recalculating_diff(&branch_buffer, cx);
|
start_recalculating_diff(&branch, cx);
|
||||||
assert_diff_hunks(
|
assert_diff_hunks(
|
||||||
&branch_buffer,
|
&branch,
|
||||||
cx,
|
cx,
|
||||||
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
|
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
|
||||||
);
|
);
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
assert_diff_hunks(
|
assert_diff_hunks(
|
||||||
&branch_buffer,
|
&branch,
|
||||||
cx,
|
cx,
|
||||||
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
|
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Edits to any replica of the base are applied to the branch.
|
// Edits to any replica of the base are applied to the branch.
|
||||||
base_buffer_replica.update(cx, |buffer, cx| {
|
base_replica.update(cx, |buffer, cx| {
|
||||||
buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
|
buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
|
||||||
});
|
});
|
||||||
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
branch.read_with(cx, |buffer, cx| {
|
||||||
assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
|
assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
|
||||||
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
|
assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Merging the branch applies all of its changes to the base.
|
// Merging the branch applies all of its changes to the base.
|
||||||
branch_buffer.update(cx, |branch_buffer, cx| {
|
branch.update(cx, |buffer, cx| {
|
||||||
branch_buffer.merge_into_base(Vec::new(), cx);
|
buffer.merge_into_base(Vec::new(), cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
branch_buffer.update(cx, |branch_buffer, cx| {
|
branch.update(cx, |buffer, cx| {
|
||||||
assert_eq!(
|
assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
|
||||||
base_buffer.read(cx).text(),
|
assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
|
||||||
"ZERO\none\n1.5\ntwo\n2.5\nTHREE\n"
|
|
||||||
);
|
|
||||||
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2724,16 +2724,16 @@ impl Project {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_rename<T: ToPointUtf16>(
|
pub fn perform_rename<T: ToPointUtf16>(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: Model<Buffer>,
|
buffer: Model<Buffer>,
|
||||||
position: T,
|
position: T,
|
||||||
new_name: String,
|
new_name: String,
|
||||||
push_to_history: bool,
|
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<ProjectTransaction>> {
|
) -> Task<Result<ProjectTransaction>> {
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.perform_rename_impl(buffer, position, new_name, push_to_history, cx)
|
self.perform_rename_impl(buffer, position, new_name, true, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_type_format<T: ToPointUtf16>(
|
pub fn on_type_format<T: ToPointUtf16>(
|
||||||
|
|
|
@ -3892,7 +3892,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(range, 6..9);
|
assert_eq!(range, 6..9);
|
||||||
|
|
||||||
let response = project.update(cx, |project, cx| {
|
let response = project.update(cx, |project, cx| {
|
||||||
project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
|
project.perform_rename(buffer.clone(), 7, "THREE".to_string(), cx)
|
||||||
});
|
});
|
||||||
fake_server
|
fake_server
|
||||||
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
|
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
|
||||||
|
|
|
@ -434,7 +434,7 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
|
||||||
|
|
||||||
project
|
project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
project.perform_rename(buffer.clone(), 3, "two".to_string(), true, cx)
|
project.perform_rename(buffer.clone(), 3, "two".to_string(), cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -2439,6 +2439,42 @@ impl BufferSnapshot {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn range_to_version(&self, range: Range<usize>, version: &clock::Global) -> Range<usize> {
|
||||||
|
let mut offsets = self.offsets_to_version([range.start, range.end], version);
|
||||||
|
offsets.next().unwrap()..offsets.next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the given sequence of offsets into their corresponding offsets
|
||||||
|
/// at a prior version of this buffer.
|
||||||
|
pub fn offsets_to_version<'a>(
|
||||||
|
&'a self,
|
||||||
|
offsets: impl 'a + IntoIterator<Item = usize>,
|
||||||
|
version: &'a clock::Global,
|
||||||
|
) -> impl 'a + Iterator<Item = usize> {
|
||||||
|
let mut edits = self.edits_since(version).peekable();
|
||||||
|
let mut last_old_end = 0;
|
||||||
|
let mut last_new_end = 0;
|
||||||
|
offsets.into_iter().map(move |new_offset| {
|
||||||
|
while let Some(edit) = edits.peek() {
|
||||||
|
if edit.new.start > new_offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if edit.new.end <= new_offset {
|
||||||
|
last_new_end = edit.new.end;
|
||||||
|
last_old_end = edit.old.end;
|
||||||
|
edits.next();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let overshoot = new_offset - edit.new.start;
|
||||||
|
return (edit.old.start + overshoot).min(edit.old.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_old_end + new_offset.saturating_sub(last_new_end)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RopeBuilder<'a> {
|
struct RopeBuilder<'a> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue