Merge pull request #2393 from zed-industries/relay-change-events-to-copilot
Relay buffer change events to Copilot language server
This commit is contained in:
commit
370875b1d4
9 changed files with 786 additions and 171 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1341,14 +1341,17 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"async-tar",
|
"async-tar",
|
||||||
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"context_menu",
|
"context_menu",
|
||||||
|
"fs",
|
||||||
"futures 0.3.25",
|
"futures 0.3.25",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
"lsp",
|
||||||
"node_runtime",
|
"node_runtime",
|
||||||
|
"rpc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"settings",
|
"settings",
|
||||||
|
@ -4687,6 +4690,7 @@ dependencies = [
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
|
"copilot",
|
||||||
"ctor",
|
"ctor",
|
||||||
"db",
|
"db",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|
|
@ -38,10 +38,13 @@ smol = "1.2.5"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
|
fs = { path = "../fs", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
settings = { path = "../settings", features = ["test-support"] }
|
|
||||||
lsp = { path = "../lsp", features = ["test-support"] }
|
lsp = { path = "../lsp", features = ["test-support"] }
|
||||||
|
rpc = { path = "../rpc", features = ["test-support"] }
|
||||||
|
settings = { path = "../settings", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
|
|
|
@ -5,9 +5,14 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use async_compression::futures::bufread::GzipDecoder;
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
use async_tar::Archive;
|
use async_tar::Archive;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::{future::Shared, Future, FutureExt, TryFutureExt};
|
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||||
use gpui::{actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
|
use gpui::{
|
||||||
use language::{point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, Language, ToPointUtf16};
|
actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle,
|
||||||
|
};
|
||||||
|
use language::{
|
||||||
|
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
|
||||||
|
ToPointUtf16,
|
||||||
|
};
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use lsp::LanguageServer;
|
use lsp::LanguageServer;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
|
@ -16,6 +21,7 @@ use settings::Settings;
|
||||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
use smol::{fs, io::BufReader, stream::StreamExt};
|
||||||
use std::{
|
use std::{
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
|
mem,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -98,15 +104,38 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
|
||||||
|
|
||||||
enum CopilotServer {
|
enum CopilotServer {
|
||||||
Disabled,
|
Disabled,
|
||||||
Starting {
|
Starting { task: Shared<Task<()>> },
|
||||||
task: Shared<Task<()>>,
|
|
||||||
},
|
|
||||||
Error(Arc<str>),
|
Error(Arc<str>),
|
||||||
Started {
|
Running(RunningCopilotServer),
|
||||||
server: Arc<LanguageServer>,
|
}
|
||||||
status: SignInStatus,
|
|
||||||
subscriptions_by_buffer_id: HashMap<usize, gpui::Subscription>,
|
impl CopilotServer {
|
||||||
},
|
fn as_authenticated(&mut self) -> Result<&mut RunningCopilotServer> {
|
||||||
|
let server = self.as_running()?;
|
||||||
|
if matches!(server.sign_in_status, SignInStatus::Authorized { .. }) {
|
||||||
|
Ok(server)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("must sign in before using copilot"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_running(&mut self) -> Result<&mut RunningCopilotServer> {
|
||||||
|
match self {
|
||||||
|
CopilotServer::Starting { .. } => Err(anyhow!("copilot is still starting")),
|
||||||
|
CopilotServer::Disabled => Err(anyhow!("copilot is disabled")),
|
||||||
|
CopilotServer::Error(error) => Err(anyhow!(
|
||||||
|
"copilot was not started because of an error: {}",
|
||||||
|
error
|
||||||
|
)),
|
||||||
|
CopilotServer::Running(server) => Ok(server),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RunningCopilotServer {
|
||||||
|
lsp: Arc<LanguageServer>,
|
||||||
|
sign_in_status: SignInStatus,
|
||||||
|
registered_buffers: HashMap<usize, RegisteredBuffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -141,8 +170,104 @@ impl Status {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
struct RegisteredBuffer {
|
||||||
|
id: usize,
|
||||||
|
uri: lsp::Url,
|
||||||
|
language_id: String,
|
||||||
|
snapshot: BufferSnapshot,
|
||||||
|
snapshot_version: i32,
|
||||||
|
_subscriptions: [gpui::Subscription; 2],
|
||||||
|
pending_buffer_change: Task<Option<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegisteredBuffer {
|
||||||
|
fn report_changes(
|
||||||
|
&mut self,
|
||||||
|
buffer: &ModelHandle<Buffer>,
|
||||||
|
cx: &mut ModelContext<Copilot>,
|
||||||
|
) -> oneshot::Receiver<(i32, BufferSnapshot)> {
|
||||||
|
let id = self.id;
|
||||||
|
let (done_tx, done_rx) = oneshot::channel();
|
||||||
|
|
||||||
|
if buffer.read(cx).version() == self.snapshot.version {
|
||||||
|
let _ = done_tx.send((self.snapshot_version, self.snapshot.clone()));
|
||||||
|
} else {
|
||||||
|
let buffer = buffer.downgrade();
|
||||||
|
let prev_pending_change =
|
||||||
|
mem::replace(&mut self.pending_buffer_change, Task::ready(None));
|
||||||
|
self.pending_buffer_change = cx.spawn_weak(|copilot, mut cx| async move {
|
||||||
|
prev_pending_change.await;
|
||||||
|
|
||||||
|
let old_version = copilot.upgrade(&cx)?.update(&mut cx, |copilot, _| {
|
||||||
|
let server = copilot.server.as_authenticated().log_err()?;
|
||||||
|
let buffer = server.registered_buffers.get_mut(&id)?;
|
||||||
|
Some(buffer.snapshot.version.clone())
|
||||||
|
})?;
|
||||||
|
let new_snapshot = buffer
|
||||||
|
.upgrade(&cx)?
|
||||||
|
.read_with(&cx, |buffer, _| buffer.snapshot());
|
||||||
|
|
||||||
|
let content_changes = cx
|
||||||
|
.background()
|
||||||
|
.spawn({
|
||||||
|
let new_snapshot = new_snapshot.clone();
|
||||||
|
async move {
|
||||||
|
new_snapshot
|
||||||
|
.edits_since::<(PointUtf16, usize)>(&old_version)
|
||||||
|
.map(|edit| {
|
||||||
|
let edit_start = edit.new.start.0;
|
||||||
|
let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
|
||||||
|
let new_text = new_snapshot
|
||||||
|
.text_for_range(edit.new.start.1..edit.new.end.1)
|
||||||
|
.collect();
|
||||||
|
lsp::TextDocumentContentChangeEvent {
|
||||||
|
range: Some(lsp::Range::new(
|
||||||
|
point_to_lsp(edit_start),
|
||||||
|
point_to_lsp(edit_end),
|
||||||
|
)),
|
||||||
|
range_length: None,
|
||||||
|
text: new_text,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
copilot.upgrade(&cx)?.update(&mut cx, |copilot, _| {
|
||||||
|
let server = copilot.server.as_authenticated().log_err()?;
|
||||||
|
let buffer = server.registered_buffers.get_mut(&id)?;
|
||||||
|
if !content_changes.is_empty() {
|
||||||
|
buffer.snapshot_version += 1;
|
||||||
|
buffer.snapshot = new_snapshot;
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidChangeTextDocument>(
|
||||||
|
lsp::DidChangeTextDocumentParams {
|
||||||
|
text_document: lsp::VersionedTextDocumentIdentifier::new(
|
||||||
|
buffer.uri.clone(),
|
||||||
|
buffer.snapshot_version,
|
||||||
|
),
|
||||||
|
content_changes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone()));
|
||||||
|
Some(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
done_rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Completion {
|
pub struct Completion {
|
||||||
|
uuid: String,
|
||||||
pub range: Range<Anchor>,
|
pub range: Range<Anchor>,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
@ -151,6 +276,7 @@ pub struct Copilot {
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
node_runtime: Arc<NodeRuntime>,
|
node_runtime: Arc<NodeRuntime>,
|
||||||
server: CopilotServer,
|
server: CopilotServer,
|
||||||
|
buffers: HashMap<usize, WeakModelHandle<Buffer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Copilot {
|
impl Entity for Copilot {
|
||||||
|
@ -212,12 +338,14 @@ impl Copilot {
|
||||||
http,
|
http,
|
||||||
node_runtime,
|
node_runtime,
|
||||||
server: CopilotServer::Starting { task: start_task },
|
server: CopilotServer::Starting { task: start_task },
|
||||||
|
buffers: Default::default(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self {
|
Self {
|
||||||
http,
|
http,
|
||||||
node_runtime,
|
node_runtime,
|
||||||
server: CopilotServer::Disabled,
|
server: CopilotServer::Disabled,
|
||||||
|
buffers: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,11 +358,12 @@ impl Copilot {
|
||||||
let this = cx.add_model(|cx| Self {
|
let this = cx.add_model(|cx| Self {
|
||||||
http: http.clone(),
|
http: http.clone(),
|
||||||
node_runtime: NodeRuntime::new(http, cx.background().clone()),
|
node_runtime: NodeRuntime::new(http, cx.background().clone()),
|
||||||
server: CopilotServer::Started {
|
server: CopilotServer::Running(RunningCopilotServer {
|
||||||
server: Arc::new(server),
|
lsp: Arc::new(server),
|
||||||
status: SignInStatus::Authorized,
|
sign_in_status: SignInStatus::Authorized,
|
||||||
subscriptions_by_buffer_id: Default::default(),
|
registered_buffers: Default::default(),
|
||||||
},
|
}),
|
||||||
|
buffers: Default::default(),
|
||||||
});
|
});
|
||||||
(this, fake_server)
|
(this, fake_server)
|
||||||
}
|
}
|
||||||
|
@ -286,6 +415,19 @@ impl Copilot {
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
server
|
||||||
|
.request::<request::SetEditorInfo>(request::SetEditorInfoParams {
|
||||||
|
editor_info: request::EditorInfo {
|
||||||
|
name: "zed".into(),
|
||||||
|
version: env!("CARGO_PKG_VERSION").into(),
|
||||||
|
},
|
||||||
|
editor_plugin_info: request::EditorPluginInfo {
|
||||||
|
name: "zed-copilot".into(),
|
||||||
|
version: "0.0.1".into(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
anyhow::Ok((server, status))
|
anyhow::Ok((server, status))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -294,11 +436,11 @@ impl Copilot {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
match server {
|
match server {
|
||||||
Ok((server, status)) => {
|
Ok((server, status)) => {
|
||||||
this.server = CopilotServer::Started {
|
this.server = CopilotServer::Running(RunningCopilotServer {
|
||||||
server,
|
lsp: server,
|
||||||
status: SignInStatus::SignedOut,
|
sign_in_status: SignInStatus::SignedOut,
|
||||||
subscriptions_by_buffer_id: Default::default(),
|
registered_buffers: Default::default(),
|
||||||
};
|
});
|
||||||
this.update_sign_in_status(status, cx);
|
this.update_sign_in_status(status, cx);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
@ -311,8 +453,8 @@ impl Copilot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
if let CopilotServer::Started { server, status, .. } = &mut self.server {
|
if let CopilotServer::Running(server) = &mut self.server {
|
||||||
let task = match status {
|
let task = match &server.sign_in_status {
|
||||||
SignInStatus::Authorized { .. } | SignInStatus::Unauthorized { .. } => {
|
SignInStatus::Authorized { .. } | SignInStatus::Unauthorized { .. } => {
|
||||||
Task::ready(Ok(())).shared()
|
Task::ready(Ok(())).shared()
|
||||||
}
|
}
|
||||||
|
@ -321,11 +463,11 @@ impl Copilot {
|
||||||
task.clone()
|
task.clone()
|
||||||
}
|
}
|
||||||
SignInStatus::SignedOut => {
|
SignInStatus::SignedOut => {
|
||||||
let server = server.clone();
|
let lsp = server.lsp.clone();
|
||||||
let task = cx
|
let task = cx
|
||||||
.spawn(|this, mut cx| async move {
|
.spawn(|this, mut cx| async move {
|
||||||
let sign_in = async {
|
let sign_in = async {
|
||||||
let sign_in = server
|
let sign_in = lsp
|
||||||
.request::<request::SignInInitiate>(
|
.request::<request::SignInInitiate>(
|
||||||
request::SignInInitiateParams {},
|
request::SignInInitiateParams {},
|
||||||
)
|
)
|
||||||
|
@ -336,8 +478,10 @@ impl Copilot {
|
||||||
}
|
}
|
||||||
request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
|
request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let CopilotServer::Started { status, .. } =
|
if let CopilotServer::Running(RunningCopilotServer {
|
||||||
&mut this.server
|
sign_in_status: status,
|
||||||
|
..
|
||||||
|
}) = &mut this.server
|
||||||
{
|
{
|
||||||
if let SignInStatus::SigningIn {
|
if let SignInStatus::SigningIn {
|
||||||
prompt: prompt_flow,
|
prompt: prompt_flow,
|
||||||
|
@ -349,7 +493,7 @@ impl Copilot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let response = server
|
let response = lsp
|
||||||
.request::<request::SignInConfirm>(
|
.request::<request::SignInConfirm>(
|
||||||
request::SignInConfirmParams {
|
request::SignInConfirmParams {
|
||||||
user_code: flow.user_code,
|
user_code: flow.user_code,
|
||||||
|
@ -377,7 +521,7 @@ impl Copilot {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.shared();
|
.shared();
|
||||||
*status = SignInStatus::SigningIn {
|
server.sign_in_status = SignInStatus::SigningIn {
|
||||||
prompt: None,
|
prompt: None,
|
||||||
task: task.clone(),
|
task: task.clone(),
|
||||||
};
|
};
|
||||||
|
@ -396,10 +540,8 @@ impl Copilot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_out(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
fn sign_out(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
if let CopilotServer::Started { server, status, .. } = &mut self.server {
|
self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
|
||||||
*status = SignInStatus::SignedOut;
|
if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
|
||||||
cx.notify();
|
|
||||||
|
|
||||||
let server = server.clone();
|
let server = server.clone();
|
||||||
cx.background().spawn(async move {
|
cx.background().spawn(async move {
|
||||||
server
|
server
|
||||||
|
@ -433,6 +575,135 @@ impl Copilot {
|
||||||
cx.foreground().spawn(start_task)
|
cx.foreground().spawn(start_task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn register_buffer(&mut self, buffer: &ModelHandle<Buffer>, cx: &mut ModelContext<Self>) {
|
||||||
|
let buffer_id = buffer.id();
|
||||||
|
self.buffers.insert(buffer_id, buffer.downgrade());
|
||||||
|
|
||||||
|
if let CopilotServer::Running(RunningCopilotServer {
|
||||||
|
lsp: server,
|
||||||
|
sign_in_status: status,
|
||||||
|
registered_buffers,
|
||||||
|
..
|
||||||
|
}) = &mut self.server
|
||||||
|
{
|
||||||
|
if !matches!(status, SignInStatus::Authorized { .. }) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
registered_buffers.entry(buffer.id()).or_insert_with(|| {
|
||||||
|
let uri: lsp::Url = uri_for_buffer(buffer, cx);
|
||||||
|
let language_id = id_for_language(buffer.read(cx).language());
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
server
|
||||||
|
.notify::<lsp::notification::DidOpenTextDocument>(
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem {
|
||||||
|
uri: uri.clone(),
|
||||||
|
language_id: language_id.clone(),
|
||||||
|
version: 0,
|
||||||
|
text: snapshot.text(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
|
||||||
|
RegisteredBuffer {
|
||||||
|
id: buffer_id,
|
||||||
|
uri,
|
||||||
|
language_id,
|
||||||
|
snapshot,
|
||||||
|
snapshot_version: 0,
|
||||||
|
pending_buffer_change: Task::ready(Some(())),
|
||||||
|
_subscriptions: [
|
||||||
|
cx.subscribe(buffer, |this, buffer, event, cx| {
|
||||||
|
this.handle_buffer_event(buffer, event, cx).log_err();
|
||||||
|
}),
|
||||||
|
cx.observe_release(buffer, move |this, _buffer, _cx| {
|
||||||
|
this.buffers.remove(&buffer_id);
|
||||||
|
this.unregister_buffer(buffer_id);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_buffer_event(
|
||||||
|
&mut self,
|
||||||
|
buffer: ModelHandle<Buffer>,
|
||||||
|
event: &language::Event,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Ok(server) = self.server.as_running() {
|
||||||
|
if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.id()) {
|
||||||
|
match event {
|
||||||
|
language::Event::Edited => {
|
||||||
|
let _ = registered_buffer.report_changes(&buffer, cx);
|
||||||
|
}
|
||||||
|
language::Event::Saved => {
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidSaveTextDocument>(
|
||||||
|
lsp::DidSaveTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(
|
||||||
|
registered_buffer.uri.clone(),
|
||||||
|
),
|
||||||
|
text: None,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
language::Event::FileHandleChanged | language::Event::LanguageChanged => {
|
||||||
|
let new_language_id = id_for_language(buffer.read(cx).language());
|
||||||
|
let new_uri = uri_for_buffer(&buffer, cx);
|
||||||
|
if new_uri != registered_buffer.uri
|
||||||
|
|| new_language_id != registered_buffer.language_id
|
||||||
|
{
|
||||||
|
let old_uri = mem::replace(&mut registered_buffer.uri, new_uri);
|
||||||
|
registered_buffer.language_id = new_language_id;
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidCloseTextDocument>(
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(old_uri),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidOpenTextDocument>(
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
registered_buffer.uri.clone(),
|
||||||
|
registered_buffer.language_id.clone(),
|
||||||
|
registered_buffer.snapshot_version,
|
||||||
|
registered_buffer.snapshot.text(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unregister_buffer(&mut self, buffer_id: usize) {
|
||||||
|
if let Ok(server) = self.server.as_running() {
|
||||||
|
if let Some(buffer) = server.registered_buffers.remove(&buffer_id) {
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidCloseTextDocument>(
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer.uri),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn completions<T>(
|
pub fn completions<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &ModelHandle<Buffer>,
|
buffer: &ModelHandle<Buffer>,
|
||||||
|
@ -457,6 +728,51 @@ impl Copilot {
|
||||||
self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
|
self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn accept_completion(
|
||||||
|
&mut self,
|
||||||
|
completion: &Completion,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
let server = match self.server.as_authenticated() {
|
||||||
|
Ok(server) => server,
|
||||||
|
Err(error) => return Task::ready(Err(error)),
|
||||||
|
};
|
||||||
|
let request =
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
|
||||||
|
uuid: completion.uuid.clone(),
|
||||||
|
});
|
||||||
|
cx.background().spawn(async move {
|
||||||
|
request.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_completions(
|
||||||
|
&mut self,
|
||||||
|
completions: &[Completion],
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
let server = match self.server.as_authenticated() {
|
||||||
|
Ok(server) => server,
|
||||||
|
Err(error) => return Task::ready(Err(error)),
|
||||||
|
};
|
||||||
|
let request =
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.request::<request::NotifyRejected>(request::NotifyRejectedParams {
|
||||||
|
uuids: completions
|
||||||
|
.iter()
|
||||||
|
.map(|completion| completion.uuid.clone())
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
cx.background().spawn(async move {
|
||||||
|
request.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn request_completions<R, T>(
|
fn request_completions<R, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &ModelHandle<Buffer>,
|
buffer: &ModelHandle<Buffer>,
|
||||||
|
@ -464,116 +780,48 @@ impl Copilot {
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Vec<Completion>>>
|
) -> Task<Result<Vec<Completion>>>
|
||||||
where
|
where
|
||||||
R: lsp::request::Request<
|
R: 'static
|
||||||
|
+ lsp::request::Request<
|
||||||
Params = request::GetCompletionsParams,
|
Params = request::GetCompletionsParams,
|
||||||
Result = request::GetCompletionsResult,
|
Result = request::GetCompletionsResult,
|
||||||
>,
|
>,
|
||||||
T: ToPointUtf16,
|
T: ToPointUtf16,
|
||||||
{
|
{
|
||||||
let buffer_id = buffer.id();
|
self.register_buffer(buffer, cx);
|
||||||
let uri: lsp::Url = format!("buffer://{}", buffer_id).parse().unwrap();
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
|
||||||
let server = match &mut self.server {
|
|
||||||
CopilotServer::Starting { .. } => {
|
|
||||||
return Task::ready(Err(anyhow!("copilot is still starting")))
|
|
||||||
}
|
|
||||||
CopilotServer::Disabled => return Task::ready(Err(anyhow!("copilot is disabled"))),
|
|
||||||
CopilotServer::Error(error) => {
|
|
||||||
return Task::ready(Err(anyhow!(
|
|
||||||
"copilot was not started because of an error: {}",
|
|
||||||
error
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
CopilotServer::Started {
|
|
||||||
server,
|
|
||||||
status,
|
|
||||||
subscriptions_by_buffer_id,
|
|
||||||
} => {
|
|
||||||
if matches!(status, SignInStatus::Authorized { .. }) {
|
|
||||||
subscriptions_by_buffer_id
|
|
||||||
.entry(buffer_id)
|
|
||||||
.or_insert_with(|| {
|
|
||||||
server
|
|
||||||
.notify::<lsp::notification::DidOpenTextDocument>(
|
|
||||||
lsp::DidOpenTextDocumentParams {
|
|
||||||
text_document: lsp::TextDocumentItem {
|
|
||||||
uri: uri.clone(),
|
|
||||||
language_id: id_for_language(
|
|
||||||
buffer.read(cx).language(),
|
|
||||||
),
|
|
||||||
version: 0,
|
|
||||||
text: snapshot.text(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.log_err();
|
|
||||||
|
|
||||||
let uri = uri.clone();
|
let server = match self.server.as_authenticated() {
|
||||||
cx.observe_release(buffer, move |this, _, _| {
|
Ok(server) => server,
|
||||||
if let CopilotServer::Started {
|
Err(error) => return Task::ready(Err(error)),
|
||||||
server,
|
|
||||||
subscriptions_by_buffer_id,
|
|
||||||
..
|
|
||||||
} = &mut this.server
|
|
||||||
{
|
|
||||||
server
|
|
||||||
.notify::<lsp::notification::DidCloseTextDocument>(
|
|
||||||
lsp::DidCloseTextDocumentParams {
|
|
||||||
text_document: lsp::TextDocumentIdentifier::new(
|
|
||||||
uri.clone(),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.log_err();
|
|
||||||
subscriptions_by_buffer_id.remove(&buffer_id);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
server.clone()
|
|
||||||
} else {
|
|
||||||
return Task::ready(Err(anyhow!("must sign in before using copilot")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
let lsp = server.lsp.clone();
|
||||||
|
let registered_buffer = server.registered_buffers.get_mut(&buffer.id()).unwrap();
|
||||||
|
let snapshot = registered_buffer.report_changes(buffer, cx);
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
let uri = registered_buffer.uri.clone();
|
||||||
let settings = cx.global::<Settings>();
|
let settings = cx.global::<Settings>();
|
||||||
let position = position.to_point_utf16(&snapshot);
|
let position = position.to_point_utf16(buffer);
|
||||||
let language = snapshot.language_at(position);
|
let language = buffer.language_at(position);
|
||||||
let language_name = language.map(|language| language.name());
|
let language_name = language.map(|language| language.name());
|
||||||
let language_name = language_name.as_deref();
|
let language_name = language_name.as_deref();
|
||||||
let tab_size = settings.tab_size(language_name);
|
let tab_size = settings.tab_size(language_name);
|
||||||
let hard_tabs = settings.hard_tabs(language_name);
|
let hard_tabs = settings.hard_tabs(language_name);
|
||||||
let language_id = id_for_language(language);
|
let relative_path = buffer
|
||||||
|
.file()
|
||||||
|
.map(|file| file.path().to_path_buf())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let path;
|
cx.foreground().spawn(async move {
|
||||||
let relative_path;
|
let (version, snapshot) = snapshot.await?;
|
||||||
if let Some(file) = snapshot.file() {
|
let result = lsp
|
||||||
if let Some(file) = file.as_local() {
|
|
||||||
path = file.abs_path(cx);
|
|
||||||
} else {
|
|
||||||
path = file.full_path(cx);
|
|
||||||
}
|
|
||||||
relative_path = file.path().to_path_buf();
|
|
||||||
} else {
|
|
||||||
path = PathBuf::new();
|
|
||||||
relative_path = PathBuf::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.background().spawn(async move {
|
|
||||||
let result = server
|
|
||||||
.request::<R>(request::GetCompletionsParams {
|
.request::<R>(request::GetCompletionsParams {
|
||||||
doc: request::GetCompletionsDocument {
|
doc: request::GetCompletionsDocument {
|
||||||
source: snapshot.text(),
|
uri,
|
||||||
tab_size: tab_size.into(),
|
tab_size: tab_size.into(),
|
||||||
indent_size: 1,
|
indent_size: 1,
|
||||||
insert_spaces: !hard_tabs,
|
insert_spaces: !hard_tabs,
|
||||||
uri,
|
|
||||||
path: path.to_string_lossy().into(),
|
|
||||||
relative_path: relative_path.to_string_lossy().into(),
|
relative_path: relative_path.to_string_lossy().into(),
|
||||||
language_id,
|
|
||||||
position: point_to_lsp(position),
|
position: point_to_lsp(position),
|
||||||
version: 0,
|
version: version.try_into().unwrap(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -586,6 +834,7 @@ impl Copilot {
|
||||||
let end =
|
let end =
|
||||||
snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
|
snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
|
||||||
Completion {
|
Completion {
|
||||||
|
uuid: completion.uuid,
|
||||||
range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||||
text: completion.text,
|
text: completion.text,
|
||||||
}
|
}
|
||||||
|
@ -600,14 +849,16 @@ impl Copilot {
|
||||||
CopilotServer::Starting { task } => Status::Starting { task: task.clone() },
|
CopilotServer::Starting { task } => Status::Starting { task: task.clone() },
|
||||||
CopilotServer::Disabled => Status::Disabled,
|
CopilotServer::Disabled => Status::Disabled,
|
||||||
CopilotServer::Error(error) => Status::Error(error.clone()),
|
CopilotServer::Error(error) => Status::Error(error.clone()),
|
||||||
CopilotServer::Started { status, .. } => match status {
|
CopilotServer::Running(RunningCopilotServer { sign_in_status, .. }) => {
|
||||||
|
match sign_in_status {
|
||||||
SignInStatus::Authorized { .. } => Status::Authorized,
|
SignInStatus::Authorized { .. } => Status::Authorized,
|
||||||
SignInStatus::Unauthorized { .. } => Status::Unauthorized,
|
SignInStatus::Unauthorized { .. } => Status::Unauthorized,
|
||||||
SignInStatus::SigningIn { prompt, .. } => Status::SigningIn {
|
SignInStatus::SigningIn { prompt, .. } => Status::SigningIn {
|
||||||
prompt: prompt.clone(),
|
prompt: prompt.clone(),
|
||||||
},
|
},
|
||||||
SignInStatus::SignedOut => Status::SignedOut,
|
SignInStatus::SignedOut => Status::SignedOut,
|
||||||
},
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,14 +867,34 @@ impl Copilot {
|
||||||
lsp_status: request::SignInStatus,
|
lsp_status: request::SignInStatus,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let CopilotServer::Started { status, .. } = &mut self.server {
|
self.buffers.retain(|_, buffer| buffer.is_upgradable(cx));
|
||||||
*status = match lsp_status {
|
|
||||||
|
if let Ok(server) = self.server.as_running() {
|
||||||
|
match lsp_status {
|
||||||
request::SignInStatus::Ok { .. }
|
request::SignInStatus::Ok { .. }
|
||||||
| request::SignInStatus::MaybeOk { .. }
|
| request::SignInStatus::MaybeOk { .. }
|
||||||
| request::SignInStatus::AlreadySignedIn { .. } => SignInStatus::Authorized,
|
| request::SignInStatus::AlreadySignedIn { .. } => {
|
||||||
request::SignInStatus::NotAuthorized { .. } => SignInStatus::Unauthorized,
|
server.sign_in_status = SignInStatus::Authorized;
|
||||||
request::SignInStatus::NotSignedIn => SignInStatus::SignedOut,
|
for buffer in self.buffers.values().cloned().collect::<Vec<_>>() {
|
||||||
};
|
if let Some(buffer) = buffer.upgrade(cx) {
|
||||||
|
self.register_buffer(&buffer, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request::SignInStatus::NotAuthorized { .. } => {
|
||||||
|
server.sign_in_status = SignInStatus::Unauthorized;
|
||||||
|
for buffer_id in self.buffers.keys().copied().collect::<Vec<_>>() {
|
||||||
|
self.unregister_buffer(buffer_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request::SignInStatus::NotSignedIn => {
|
||||||
|
server.sign_in_status = SignInStatus::SignedOut;
|
||||||
|
for buffer_id in self.buffers.keys().copied().collect::<Vec<_>>() {
|
||||||
|
self.unregister_buffer(buffer_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -638,6 +909,14 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn uri_for_buffer(buffer: &ModelHandle<Buffer>, cx: &AppContext) -> lsp::Url {
|
||||||
|
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
|
||||||
|
lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
|
||||||
|
} else {
|
||||||
|
format!("buffer://{}", buffer.id()).parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn clear_copilot_dir() {
|
async fn clear_copilot_dir() {
|
||||||
remove_matching(&paths::COPILOT_DIR, |_| true).await
|
remove_matching(&paths::COPILOT_DIR, |_| true).await
|
||||||
}
|
}
|
||||||
|
@ -709,3 +988,226 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use gpui::{executor::Deterministic, TestAppContext};
|
||||||
|
|
||||||
|
#[gpui::test(iterations = 10)]
|
||||||
|
async fn test_buffer_management(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||||
|
deterministic.forbid_parking();
|
||||||
|
let (copilot, mut lsp) = Copilot::fake(cx);
|
||||||
|
|
||||||
|
let buffer_1 = cx.add_model(|cx| Buffer::new(0, "Hello", cx));
|
||||||
|
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.id()).parse().unwrap();
|
||||||
|
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_1_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
0,
|
||||||
|
"Hello".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let buffer_2 = cx.add_model(|cx| Buffer::new(0, "Goodbye", cx));
|
||||||
|
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.id()).parse().unwrap();
|
||||||
|
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_2_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
0,
|
||||||
|
"Goodbye".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidChangeTextDocumentParams {
|
||||||
|
text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
|
||||||
|
content_changes: vec![lsp::TextDocumentContentChangeEvent {
|
||||||
|
range: Some(lsp::Range::new(
|
||||||
|
lsp::Position::new(0, 5),
|
||||||
|
lsp::Position::new(0, 5)
|
||||||
|
)),
|
||||||
|
range_length: None,
|
||||||
|
text: " world".into(),
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure updates to the file are reflected in the LSP.
|
||||||
|
buffer_1
|
||||||
|
.update(cx, |buffer, cx| {
|
||||||
|
buffer.file_updated(
|
||||||
|
Arc::new(File {
|
||||||
|
abs_path: "/root/child/buffer-1".into(),
|
||||||
|
path: Path::new("child/buffer-1").into(),
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_1_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
1,
|
||||||
|
"Hello world".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure all previously-registered buffers are closed when signing out.
|
||||||
|
lsp.handle_request::<request::SignOut, _, _>(|_, _| async {
|
||||||
|
Ok(request::SignOutResult {})
|
||||||
|
});
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| copilot.sign_out(cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure all previously-registered buffers are re-opened when signing in.
|
||||||
|
lsp.handle_request::<request::SignInInitiate, _, _>(|_, _| async {
|
||||||
|
Ok(request::SignInInitiateResult::AlreadySignedIn {
|
||||||
|
user: "user-1".into(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_2_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
0,
|
||||||
|
"Goodbye".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_1_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
0,
|
||||||
|
"Hello world".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dropping a buffer causes it to be closed on the LSP side as well.
|
||||||
|
cx.update(|_| drop(buffer_2));
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
abs_path: PathBuf,
|
||||||
|
path: Arc<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl language::File for File {
|
||||||
|
fn as_local(&self) -> Option<&dyn language::LocalFile> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mtime(&self) -> std::time::SystemTime {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> &Arc<Path> {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_path(&self, _: &AppContext) -> PathBuf {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_deleted(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_proto(&self) -> rpc::proto::File {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl language::LocalFile for File {
|
||||||
|
fn abs_path(&self, _: &AppContext) -> PathBuf {
|
||||||
|
self.abs_path.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&self, _: &AppContext) -> Task<Result<String>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_reloaded(
|
||||||
|
&self,
|
||||||
|
_: u64,
|
||||||
|
_: &clock::Global,
|
||||||
|
_: language::RopeFingerprint,
|
||||||
|
_: ::fs::LineEnding,
|
||||||
|
_: std::time::SystemTime,
|
||||||
|
_: &mut AppContext,
|
||||||
|
) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -99,14 +99,11 @@ pub struct GetCompletionsParams {
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GetCompletionsDocument {
|
pub struct GetCompletionsDocument {
|
||||||
pub source: String,
|
|
||||||
pub tab_size: u32,
|
pub tab_size: u32,
|
||||||
pub indent_size: u32,
|
pub indent_size: u32,
|
||||||
pub insert_spaces: bool,
|
pub insert_spaces: bool,
|
||||||
pub uri: lsp::Url,
|
pub uri: lsp::Url,
|
||||||
pub path: String,
|
|
||||||
pub relative_path: String,
|
pub relative_path: String,
|
||||||
pub language_id: String,
|
|
||||||
pub position: lsp::Position,
|
pub position: lsp::Position,
|
||||||
pub version: usize,
|
pub version: usize,
|
||||||
}
|
}
|
||||||
|
@ -169,3 +166,60 @@ impl lsp::notification::Notification for StatusNotification {
|
||||||
type Params = StatusNotificationParams;
|
type Params = StatusNotificationParams;
|
||||||
const METHOD: &'static str = "statusNotification";
|
const METHOD: &'static str = "statusNotification";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum SetEditorInfo {}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SetEditorInfoParams {
|
||||||
|
pub editor_info: EditorInfo,
|
||||||
|
pub editor_plugin_info: EditorPluginInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lsp::request::Request for SetEditorInfo {
|
||||||
|
type Params = SetEditorInfoParams;
|
||||||
|
type Result = String;
|
||||||
|
const METHOD: &'static str = "setEditorInfo";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct EditorInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct EditorPluginInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum NotifyAccepted {}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct NotifyAcceptedParams {
|
||||||
|
pub uuid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lsp::request::Request for NotifyAccepted {
|
||||||
|
type Params = NotifyAcceptedParams;
|
||||||
|
type Result = String;
|
||||||
|
const METHOD: &'static str = "notifyAccepted";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum NotifyRejected {}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct NotifyRejectedParams {
|
||||||
|
pub uuids: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lsp::request::Request for NotifyRejected {
|
||||||
|
type Params = NotifyRejectedParams;
|
||||||
|
type Result = String;
|
||||||
|
const METHOD: &'static str = "notifyRejected";
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub use language::{char_kind, CharKind};
|
||||||
use language::{
|
use language::{
|
||||||
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
|
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
|
||||||
Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16,
|
Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16,
|
||||||
Point, Rope, Selection, SelectionGoal, TransactionId,
|
Point, Selection, SelectionGoal, TransactionId,
|
||||||
};
|
};
|
||||||
use link_go_to_definition::{
|
use link_go_to_definition::{
|
||||||
hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
|
hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
|
||||||
|
@ -1037,6 +1037,10 @@ impl Default for CopilotState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CopilotState {
|
impl CopilotState {
|
||||||
|
fn active_completion(&self) -> Option<&copilot::Completion> {
|
||||||
|
self.completions.get(self.active_completion_index)
|
||||||
|
}
|
||||||
|
|
||||||
fn text_for_active_completion(
|
fn text_for_active_completion(
|
||||||
&self,
|
&self,
|
||||||
cursor: Anchor,
|
cursor: Anchor,
|
||||||
|
@ -1044,7 +1048,7 @@ impl CopilotState {
|
||||||
) -> Option<&str> {
|
) -> Option<&str> {
|
||||||
use language::ToOffset as _;
|
use language::ToOffset as _;
|
||||||
|
|
||||||
let completion = self.completions.get(self.active_completion_index)?;
|
let completion = self.active_completion()?;
|
||||||
let excerpt_id = self.excerpt_id?;
|
let excerpt_id = self.excerpt_id?;
|
||||||
let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?;
|
let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?;
|
||||||
if excerpt_id != cursor.excerpt_id
|
if excerpt_id != cursor.excerpt_id
|
||||||
|
@ -1097,7 +1101,7 @@ impl CopilotState {
|
||||||
|
|
||||||
fn push_completion(&mut self, new_completion: copilot::Completion) {
|
fn push_completion(&mut self, new_completion: copilot::Completion) {
|
||||||
for completion in &self.completions {
|
for completion in &self.completions {
|
||||||
if *completion == new_completion {
|
if completion.text == new_completion.text && completion.range == new_completion.range {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1496,7 +1500,7 @@ impl Editor {
|
||||||
self.refresh_code_actions(cx);
|
self.refresh_code_actions(cx);
|
||||||
self.refresh_document_highlights(cx);
|
self.refresh_document_highlights(cx);
|
||||||
refresh_matching_bracket_highlights(self, cx);
|
refresh_matching_bracket_highlights(self, cx);
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.blink_manager.update(cx, BlinkManager::pause_blinking);
|
self.blink_manager.update(cx, BlinkManager::pause_blinking);
|
||||||
|
@ -1870,7 +1874,7 @@ impl Editor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.hide_copilot_suggestion(cx).is_some() {
|
if self.discard_copilot_suggestion(cx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2969,7 +2973,7 @@ impl Editor {
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cycle_suggestions(
|
fn cycle_copilot_suggestions(
|
||||||
&mut self,
|
&mut self,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
@ -3020,7 +3024,7 @@ impl Editor {
|
||||||
|
|
||||||
fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
|
fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
|
||||||
if self.has_active_copilot_suggestion(cx) {
|
if self.has_active_copilot_suggestion(cx) {
|
||||||
self.cycle_suggestions(Direction::Next, cx);
|
self.cycle_copilot_suggestions(Direction::Next, cx);
|
||||||
} else {
|
} else {
|
||||||
self.refresh_copilot_suggestions(false, cx);
|
self.refresh_copilot_suggestions(false, cx);
|
||||||
}
|
}
|
||||||
|
@ -3032,15 +3036,45 @@ impl Editor {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if self.has_active_copilot_suggestion(cx) {
|
if self.has_active_copilot_suggestion(cx) {
|
||||||
self.cycle_suggestions(Direction::Prev, cx);
|
self.cycle_copilot_suggestions(Direction::Prev, cx);
|
||||||
} else {
|
} else {
|
||||||
self.refresh_copilot_suggestions(false, cx);
|
self.refresh_copilot_suggestions(false, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
if let Some(text) = self.hide_copilot_suggestion(cx) {
|
if let Some(suggestion) = self
|
||||||
self.insert_with_autoindent_mode(&text.to_string(), None, cx);
|
.display_map
|
||||||
|
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx))
|
||||||
|
{
|
||||||
|
if let Some((copilot, completion)) =
|
||||||
|
Copilot::global(cx).zip(self.copilot_state.active_completion())
|
||||||
|
{
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx);
|
||||||
|
cx.notify();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
|
if self.has_active_copilot_suggestion(cx) {
|
||||||
|
if let Some(copilot) = Copilot::global(cx) {
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| {
|
||||||
|
copilot.discard_completions(&self.copilot_state.completions, cx)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.display_map
|
||||||
|
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
|
||||||
|
cx.notify();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -3051,18 +3085,6 @@ impl Editor {
|
||||||
self.display_map.read(cx).has_suggestion()
|
self.display_map.read(cx).has_suggestion()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<Rope> {
|
|
||||||
if self.has_active_copilot_suggestion(cx) {
|
|
||||||
let old_suggestion = self
|
|
||||||
.display_map
|
|
||||||
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
|
|
||||||
cx.notify();
|
|
||||||
old_suggestion.map(|suggestion| suggestion.text)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
|
fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
let selection = self.selections.newest_anchor();
|
let selection = self.selections.newest_anchor();
|
||||||
|
@ -3072,7 +3094,7 @@ impl Editor {
|
||||||
|| !self.completion_tasks.is_empty()
|
|| !self.completion_tasks.is_empty()
|
||||||
|| selection.start != selection.end
|
|| selection.start != selection.end
|
||||||
{
|
{
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
} else if let Some(text) = self
|
} else if let Some(text) = self
|
||||||
.copilot_state
|
.copilot_state
|
||||||
.text_for_active_completion(cursor, &snapshot)
|
.text_for_active_completion(cursor, &snapshot)
|
||||||
|
@ -3088,13 +3110,13 @@ impl Editor {
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
} else {
|
} else {
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) {
|
fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.copilot_state = Default::default();
|
self.copilot_state = Default::default();
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_code_actions_indicator(
|
pub fn render_code_actions_indicator(
|
||||||
|
@ -3212,7 +3234,7 @@ impl Editor {
|
||||||
self.completion_tasks.clear();
|
self.completion_tasks.clear();
|
||||||
}
|
}
|
||||||
self.context_menu = Some(menu);
|
self.context_menu = Some(menu);
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6643,6 +6665,7 @@ impl Editor {
|
||||||
multi_buffer::Event::DiagnosticsUpdated => {
|
multi_buffer::Event::DiagnosticsUpdated => {
|
||||||
self.refresh_active_diagnostics(cx);
|
self.refresh_active_diagnostics(cx);
|
||||||
}
|
}
|
||||||
|
multi_buffer::Event::LanguageChanged => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ pub enum Event {
|
||||||
},
|
},
|
||||||
Edited,
|
Edited,
|
||||||
Reloaded,
|
Reloaded,
|
||||||
|
LanguageChanged,
|
||||||
Reparsed,
|
Reparsed,
|
||||||
Saved,
|
Saved,
|
||||||
FileHandleChanged,
|
FileHandleChanged,
|
||||||
|
@ -1302,6 +1303,7 @@ impl MultiBuffer {
|
||||||
language::Event::Saved => Event::Saved,
|
language::Event::Saved => Event::Saved,
|
||||||
language::Event::FileHandleChanged => Event::FileHandleChanged,
|
language::Event::FileHandleChanged => Event::FileHandleChanged,
|
||||||
language::Event::Reloaded => Event::Reloaded,
|
language::Event::Reloaded => Event::Reloaded,
|
||||||
|
language::Event::LanguageChanged => Event::LanguageChanged,
|
||||||
language::Event::Reparsed => Event::Reparsed,
|
language::Event::Reparsed => Event::Reparsed,
|
||||||
language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
|
language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
|
||||||
language::Event::Closed => Event::Closed,
|
language::Event::Closed => Event::Closed,
|
||||||
|
|
|
@ -187,6 +187,7 @@ pub enum Event {
|
||||||
Saved,
|
Saved,
|
||||||
FileHandleChanged,
|
FileHandleChanged,
|
||||||
Reloaded,
|
Reloaded,
|
||||||
|
LanguageChanged,
|
||||||
Reparsed,
|
Reparsed,
|
||||||
DiagnosticsUpdated,
|
DiagnosticsUpdated,
|
||||||
Closed,
|
Closed,
|
||||||
|
@ -536,6 +537,7 @@ impl Buffer {
|
||||||
self.syntax_map.lock().clear();
|
self.syntax_map.lock().clear();
|
||||||
self.language = language;
|
self.language = language;
|
||||||
self.reparse(cx);
|
self.reparse(cx);
|
||||||
|
cx.emit(Event::LanguageChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_language_registry(&mut self, language_registry: Arc<LanguageRegistry>) {
|
pub fn set_language_registry(&mut self, language_registry: Arc<LanguageRegistry>) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ test-support = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
text = { path = "../text" }
|
text = { path = "../text" }
|
||||||
|
copilot = { path = "../copilot" }
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
|
|
|
@ -12,6 +12,7 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use client::{proto, Client, TypedEnvelope, UserStore};
|
use client::{proto, Client, TypedEnvelope, UserStore};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
||||||
|
use copilot::Copilot;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc::{self, UnboundedReceiver},
|
channel::mpsc::{self, UnboundedReceiver},
|
||||||
future::{try_join_all, Shared},
|
future::{try_join_all, Shared},
|
||||||
|
@ -129,6 +130,7 @@ pub struct Project {
|
||||||
_maintain_buffer_languages: Task<()>,
|
_maintain_buffer_languages: Task<()>,
|
||||||
_maintain_workspace_config: Task<()>,
|
_maintain_workspace_config: Task<()>,
|
||||||
terminals: Terminals,
|
terminals: Terminals,
|
||||||
|
copilot_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum BufferMessage {
|
enum BufferMessage {
|
||||||
|
@ -472,6 +474,7 @@ impl Project {
|
||||||
terminals: Terminals {
|
terminals: Terminals {
|
||||||
local_handles: Vec::new(),
|
local_handles: Vec::new(),
|
||||||
},
|
},
|
||||||
|
copilot_enabled: Copilot::global(cx).is_some(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -559,6 +562,7 @@ impl Project {
|
||||||
terminals: Terminals {
|
terminals: Terminals {
|
||||||
local_handles: Vec::new(),
|
local_handles: Vec::new(),
|
||||||
},
|
},
|
||||||
|
copilot_enabled: Copilot::global(cx).is_some(),
|
||||||
};
|
};
|
||||||
for worktree in worktrees {
|
for worktree in worktrees {
|
||||||
let _ = this.add_worktree(&worktree, cx);
|
let _ = this.add_worktree(&worktree, cx);
|
||||||
|
@ -664,6 +668,15 @@ impl Project {
|
||||||
self.start_language_server(worktree_id, worktree_path, language, cx);
|
self.start_language_server(worktree_id, worktree_path, language, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.copilot_enabled && Copilot::global(cx).is_some() {
|
||||||
|
self.copilot_enabled = true;
|
||||||
|
for buffer in self.opened_buffers.values() {
|
||||||
|
if let Some(buffer) = buffer.upgrade(cx) {
|
||||||
|
self.register_buffer_with_copilot(&buffer, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1616,6 +1629,7 @@ impl Project {
|
||||||
|
|
||||||
self.detect_language_for_buffer(buffer, cx);
|
self.detect_language_for_buffer(buffer, cx);
|
||||||
self.register_buffer_with_language_server(buffer, cx);
|
self.register_buffer_with_language_server(buffer, cx);
|
||||||
|
self.register_buffer_with_copilot(buffer, cx);
|
||||||
cx.observe_release(buffer, |this, buffer, cx| {
|
cx.observe_release(buffer, |this, buffer, cx| {
|
||||||
if let Some(file) = File::from_dyn(buffer.file()) {
|
if let Some(file) = File::from_dyn(buffer.file()) {
|
||||||
if file.is_local() {
|
if file.is_local() {
|
||||||
|
@ -1731,6 +1745,16 @@ impl Project {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn register_buffer_with_copilot(
|
||||||
|
&self,
|
||||||
|
buffer_handle: &ModelHandle<Buffer>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(copilot) = Copilot::global(cx) {
|
||||||
|
copilot.update(cx, |copilot, cx| copilot.register_buffer(buffer_handle, cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn send_buffer_messages(
|
async fn send_buffer_messages(
|
||||||
this: WeakModelHandle<Self>,
|
this: WeakModelHandle<Self>,
|
||||||
rx: UnboundedReceiver<BufferMessage>,
|
rx: UnboundedReceiver<BufferMessage>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue