From 9db716123d23ffabb8b2a7ce95d583545ed1bcaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Eriksson?= Date: Fri, 23 Jan 2026 15:57:38 +0100 Subject: [PATCH] copilot: Add the option to disable Next Edit Suggestions (#47438) Adds a new setting to GitHub Copilot to toggle the Next Edit Suggestions feature, it is enabled by default. ## Motivations Due to some current usability issues with this feature, see #46880, and some personal anecdotes of using it, it is currently rough to utilize, so this gives the option to disable it. ## Related - #47071 - #30124 - #44486 ## Release Notes - Adds the ability to disable GitHub Copilot's Next Edit Suggestions feature. ## User Interface ![image](https://github.com/user-attachments/assets/5a3d7166-68dd-4f5b-a220-0a9bd9282cd5) ## Text Example The text example will be adding a `z` variable to a `Point3D` class in TypeScript. ### With Next Edit Suggestions In this example I am able to just press auto-complete (press TAB) 3x. ```ts class Point3D { x: number; y: number; z: number; // <-- Cursor before z: suggested constructor(x: number, y: number , z: number // <-- Next Suggestion ) { this.x = x; this.y = y; this.z = z; // <-- Last Suggestion } } ``` ### Without Next Edit Suggestions ```ts class Point3D { x: number; y: number; z: number; // <-- Cursor before z: the only suggestion constructor(x: number, y: number) { this.x = x; this.y = y; } } ``` --- assets/settings/default.json | 2 + crates/copilot/src/copilot.rs | 149 ++++++++++-------- .../src/edit_prediction_button.rs | 26 +++ crates/language/src/language_settings.rs | 3 + crates/settings_content/src/language.rs | 4 + 5 files changed, 122 insertions(+), 62 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 5e6f65e8b7ab1e172f9d5662e2110b0b9df42c88..7af7100648b7e17d938d7c1eb171d8644e1f8a19 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1528,11 +1528,13 @@ // "enterprise_uri": "", // "proxy": "", // "proxy_no_verify": false + // "enabled_next_edit_suggestions": true // }, "copilot": { "enterprise_uri": null, "proxy": null, "proxy_no_verify": null, + "enabled_next_edit_suggestions": true, }, "codestral": { "api_url": "https://codestral.mistral.ai", diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 767f4dbab3066fe130d483092641e42b2eba9b08..37c77395090b8910ede421e698c0a7346eee672d 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -9,12 +9,13 @@ use ::fs::Fs; use anyhow::{Context as _, Result, anyhow}; use collections::{HashMap, HashSet}; use command_palette_hooks::CommandPaletteFilter; +use futures::future; use futures::{Future, FutureExt, TryFutureExt, channel::oneshot, future::Shared, select_biased}; use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Task, WeakEntity, actions, }; -use language::language_settings::CopilotSettings; +use language::language_settings::{AllLanguageSettings, CopilotSettings}; use language::{ Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, ToPointUtf16, language_settings::{EditPredictionProvider, all_language_settings}, @@ -928,21 +929,63 @@ impl Copilot { let hard_tabs = settings.hard_tabs; drop(settings); + let nes_enabled = AllLanguageSettings::get_global(cx) + .edit_predictions + .copilot + .enabled_next_edit_suggestions + .unwrap_or(true); + cx.background_spawn(async move { let (version, snapshot) = pending_snapshot.await?; let lsp_position = point_to_lsp(position); - let nes_request = lsp - .request::(request::NextEditSuggestionsParams { + let nes_fut = if nes_enabled { + lsp.request::(request::NextEditSuggestionsParams { text_document: lsp::VersionedTextDocumentIdentifier { uri: uri.clone(), version, }, position: lsp_position, }) - .fuse(); + .map(|resp| { + resp.into_response() + .ok() + .map(|result| { + result + .edits + .into_iter() + .map(|completion| { + let start = snapshot.clip_point_utf16( + point_from_lsp(completion.range.start), + Bias::Left, + ); + let end = snapshot.clip_point_utf16( + point_from_lsp(completion.range.end), + Bias::Left, + ); + CopilotEditPrediction { + buffer: buffer_entity.clone(), + range: snapshot.anchor_before(start) + ..snapshot.anchor_after(end), + text: completion.text, + command: completion.command, + snapshot: snapshot.clone(), + source: CompletionSource::NextEditSuggestion, + } + }) + .collect::>() + }) + .unwrap_or_default() + }) + .left_future() + .fuse() + } else { + future::ready(Vec::::new()) + .right_future() + .fuse() + }; - let inline_request = lsp + let inline_fut = lsp .request::(request::InlineCompletionsParams { text_document: lsp::VersionedTextDocumentIdentifier { uri: uri.clone(), @@ -957,74 +1000,56 @@ impl Copilot { insert_spaces: !hard_tabs, }), }) - .fuse(); - - futures::pin_mut!(nes_request, inline_request); - - let convert_nes = - |result: request::NextEditSuggestionsResult| -> Vec { - result - .edits - .into_iter() - .map(|completion| { - let start = snapshot.clip_point_utf16( - point_from_lsp(completion.range.start), - Bias::Left, - ); - let end = snapshot - .clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left); - CopilotEditPrediction { - buffer: buffer_entity.clone(), - range: snapshot.anchor_before(start)..snapshot.anchor_after(end), - text: completion.text, - command: completion.command, - snapshot: snapshot.clone(), - source: CompletionSource::NextEditSuggestion, - } + .map(|resp| { + resp.into_response() + .ok() + .map(|result| { + result + .items + .into_iter() + .map(|item| { + let start = snapshot.clip_point_utf16( + point_from_lsp(item.range.start), + Bias::Left, + ); + let end = snapshot.clip_point_utf16( + point_from_lsp(item.range.end), + Bias::Left, + ); + CopilotEditPrediction { + buffer: buffer_entity.clone(), + range: snapshot.anchor_before(start) + ..snapshot.anchor_after(end), + text: item.insert_text, + command: item.command, + snapshot: snapshot.clone(), + source: CompletionSource::InlineCompletion, + } + }) + .collect::>() }) - .collect() - }; + .unwrap_or_default() + }) + .fuse(); - let convert_inline = - |result: request::InlineCompletionsResult| -> Vec { - result - .items - .into_iter() - .map(|item| { - let start = snapshot - .clip_point_utf16(point_from_lsp(item.range.start), Bias::Left); - let end = snapshot - .clip_point_utf16(point_from_lsp(item.range.end), Bias::Left); - CopilotEditPrediction { - buffer: buffer_entity.clone(), - range: snapshot.anchor_before(start)..snapshot.anchor_after(end), - text: item.insert_text, - command: item.command, - snapshot: snapshot.clone(), - source: CompletionSource::InlineCompletion, - } - }) - .collect() - }; + futures::pin_mut!(nes_fut, inline_fut); let mut nes_result: Option> = None; let mut inline_result: Option> = None; loop { select_biased! { - nes = nes_request => { - let completions = nes.into_response().ok().map(convert_nes).unwrap_or_default(); - if !completions.is_empty() { - return Ok(completions); + nes = nes_fut => { + if !nes.is_empty() { + return Ok(nes); } - nes_result = Some(completions); + nes_result = Some(nes); } - inline = inline_request => { - let completions = inline.into_response().ok().map(convert_inline).unwrap_or_default(); - if !completions.is_empty() { - return Ok(completions); + inline = inline_fut => { + if !inline.is_empty() { + return Ok(inline); } - inline_result = Some(completions); + inline_result = Some(inline); } complete => break, } diff --git a/crates/edit_prediction_ui/src/edit_prediction_button.rs b/crates/edit_prediction_ui/src/edit_prediction_button.rs index 45f844bc09d42ebced2730c4bc3a3dc17af94df0..67773c67c40f837dc308aeb3263c1cf6cfb79233 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_button.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_button.rs @@ -942,6 +942,11 @@ impl EditPredictionButton { cx: &mut Context, ) -> Entity { let all_language_settings = all_language_settings(None, cx); + let next_edit_suggestions = all_language_settings + .edit_predictions + .copilot + .enabled_next_edit_suggestions + .unwrap_or(true); let copilot_config = copilot_chat::CopilotChatConfiguration { enterprise_uri: all_language_settings .edit_predictions @@ -957,6 +962,27 @@ impl EditPredictionButton { self.add_provider_switching_section(menu, EditPredictionProvider::Copilot, cx); menu.separator() + .item( + ContextMenuEntry::new("Copilot: Next Edit Suggestions") + .toggleable(IconPosition::Start, next_edit_suggestions) + .handler({ + let fs = self.fs.clone(); + move |_, cx| { + update_settings_file(fs.clone(), cx, move |settings, _| { + settings + .project + .all_languages + .edit_predictions + .get_or_insert_default() + .copilot + .get_or_insert_default() + .enabled_next_edit_suggestions = + Some(!next_edit_suggestions); + }); + } + }), + ) + .separator() .link( "Go to Copilot Settings", OpenBrowser { url: settings_url }.boxed_clone(), diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index bb2f922ed9c29097cc55ebf602a97ed7f340d0ea..0c1c9efc3a27462d69737b3ef83b3484f40d68b6 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -483,6 +483,8 @@ pub struct CopilotSettings { pub proxy_no_verify: Option, /// Enterprise URI for Copilot. pub enterprise_uri: Option, + /// Whether the Copilot Next Edit Suggestions feature is enabled. + pub enabled_next_edit_suggestions: Option, } #[derive(Clone, Debug, Default)] @@ -741,6 +743,7 @@ impl settings::Settings for AllLanguageSettings { proxy: copilot.proxy, proxy_no_verify: copilot.proxy_no_verify, enterprise_uri: copilot.enterprise_uri, + enabled_next_edit_suggestions: copilot.enabled_next_edit_suggestions, }; let codestral = edit_predictions.codestral.unwrap(); diff --git a/crates/settings_content/src/language.rs b/crates/settings_content/src/language.rs index d6d2b051e7fdfd74509bfb0219c3a57976154494..d3135396ebacd7c857ec5c5564f5bec6b08f4579 100644 --- a/crates/settings_content/src/language.rs +++ b/crates/settings_content/src/language.rs @@ -210,6 +210,10 @@ pub struct CopilotSettingsContent { /// /// Default: none pub enterprise_uri: Option, + /// Whether the Copilot Next Edit Suggestions feature is enabled. + /// + /// Default: true + pub enabled_next_edit_suggestions: Option, } #[with_fallible_options]