Add copilot.disabled_globs setting

This commit is contained in:
Max Brunsfeld 2023-04-27 18:06:52 -07:00
parent dc999f719b
commit c485fc86a2
10 changed files with 176 additions and 34 deletions

2
Cargo.lock generated
View File

@ -1983,6 +1983,7 @@ dependencies = [
"futures 0.3.25",
"fuzzy",
"git",
"glob",
"gpui",
"indoc",
"itertools",
@ -5964,6 +5965,7 @@ dependencies = [
"collections",
"fs",
"futures 0.3.25",
"glob",
"gpui",
"json_comments",
"postage",

View File

@ -77,6 +77,7 @@ async-trait = { version = "0.1" }
ctor = { version = "0.1" }
env_logger = { version = "0.9" }
futures = { version = "0.3" }
glob = { version = "0.3.1" }
lazy_static = { version = "1.4.0" }
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
ordered-float = { version = "2.1.1" }

View File

@ -115,6 +115,13 @@
// "git_gutter": "hide"
"git_gutter": "tracked_files"
},
"copilot": {
// The set of glob patterns for which copilot should be disabled
// in any matching file.
"disabled_globs": [
".env"
]
},
// Settings specific to journaling
"journal": {
// The path of the directory where journal entries are stored

View File

@ -79,6 +79,7 @@ workspace = { path = "../workspace", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
glob.workspace = true
rand.workspace = true
unindent.workspace = true
tree-sitter = "0.20"

View File

@ -2925,11 +2925,7 @@ impl Editor {
let snapshot = self.buffer.read(cx).snapshot(cx);
let cursor = self.selections.newest_anchor().head();
let language_name = snapshot.language_at(cursor).map(|language| language.name());
if !cx
.global::<Settings>()
.show_copilot_suggestions(language_name.as_deref())
{
if !self.is_copilot_enabled_at(cursor, &snapshot, cx) {
self.clear_copilot_suggestions(cx);
return None;
}
@ -3080,6 +3076,37 @@ impl Editor {
}
}
fn is_copilot_enabled_at(
&self,
location: Anchor,
snapshot: &MultiBufferSnapshot,
cx: &mut ViewContext<Self>,
) -> bool {
let settings = cx.global::<Settings>();
let language_name = snapshot
.language_at(location)
.map(|language| language.name());
if !settings.show_copilot_suggestions(language_name.as_deref()) {
return false;
}
let file = snapshot.file_at(location);
if let Some(file) = file {
let path = file.path();
if settings
.copilot
.disabled_globs
.iter()
.any(|glob| glob.matches_path(path))
{
return false;
}
}
true
}
fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
self.display_map.read(cx).has_suggestion()
}

View File

@ -6387,6 +6387,97 @@ async fn test_copilot_multibuffer(
});
}
#[gpui::test]
async fn test_copilot_disabled_globs(
deterministic: Arc<Deterministic>,
cx: &mut gpui::TestAppContext,
) {
let (copilot, copilot_lsp) = Copilot::fake(cx);
cx.update(|cx| {
let mut settings = Settings::test(cx);
settings.copilot.disabled_globs = vec![glob::Pattern::new(".env*").unwrap()];
cx.set_global(settings);
cx.set_global(copilot)
});
let fs = FakeFs::new(cx.background());
fs.insert_tree(
"/test",
json!({
".env": "SECRET=something\n",
"README.md": "hello\n"
}),
)
.await;
let project = Project::test(fs, ["/test".as_ref()], cx).await;
let private_buffer = project
.update(cx, |project, cx| {
project.open_local_buffer("/test/.env", cx)
})
.await
.unwrap();
let public_buffer = project
.update(cx, |project, cx| {
project.open_local_buffer("/test/README.md", cx)
})
.await
.unwrap();
let multibuffer = cx.add_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
multibuffer.push_excerpts(
private_buffer.clone(),
[ExcerptRange {
context: Point::new(0, 0)..Point::new(1, 0),
primary: None,
}],
cx,
);
multibuffer.push_excerpts(
public_buffer.clone(),
[ExcerptRange {
context: Point::new(0, 0)..Point::new(1, 0),
primary: None,
}],
cx,
);
multibuffer
});
let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
let mut copilot_requests = copilot_lsp
.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
Ok(copilot::request::GetCompletionsResult {
completions: vec![copilot::request::Completion {
text: "next line".into(),
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
..Default::default()
}],
})
});
editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |selections| {
selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
});
editor.next_copilot_suggestion(&Default::default(), cx);
});
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
assert!(copilot_requests.try_next().is_err());
editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
});
editor.next_copilot_suggestion(&Default::default(), cx);
});
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
assert!(copilot_requests.try_next().is_ok());
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(row as u32, column as u32);
point..point

View File

@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
pub use language::Completion;
use language::{
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
DiagnosticEntry, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline,
OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _,
ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
};
use std::{
borrow::Cow,
@ -2754,6 +2754,11 @@ impl MultiBufferSnapshot {
self.trailing_excerpt_update_count
}
pub fn file_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc<dyn File>> {
self.point_to_buffer_offset(point)
.and_then(|(buffer, _)| buffer.file())
}
pub fn language_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc<Language>> {
self.point_to_buffer_offset(point)
.and_then(|(buffer, offset)| buffer.language_at(offset))

View File

@ -28,7 +28,6 @@ fs = { path = "../fs" }
fsevent = { path = "../fsevent" }
fuzzy = { path = "../fuzzy" }
git = { path = "../git" }
glob = { version = "0.3.1" }
gpui = { path = "../gpui" }
language = { path = "../language" }
lsp = { path = "../lsp" }
@ -43,6 +42,7 @@ anyhow.workspace = true
async-trait.workspace = true
backtrace = "0.3"
futures.workspace = true
glob.workspace = true
ignore = "0.4"
lazy_static.workspace = true
log.workspace = true

View File

@ -23,6 +23,7 @@ theme = { path = "../theme" }
staff_mode = { path = "../staff_mode" }
util = { path = "../util" }
glob.workspace = true
json_comments = "0.2"
postage.workspace = true
schemars = "0.8"

View File

@ -47,6 +47,7 @@ pub struct Settings {
pub editor_overrides: EditorSettings,
pub git: GitSettings,
pub git_overrides: GitSettings,
pub copilot: CopilotSettings,
pub journal_defaults: JournalSettings,
pub journal_overrides: JournalSettings,
pub terminal_defaults: TerminalSettings,
@ -61,29 +62,6 @@ pub struct Settings {
pub base_keymap: BaseKeymap,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum CopilotSettings {
#[default]
On,
Off,
}
impl From<CopilotSettings> for bool {
fn from(value: CopilotSettings) -> Self {
match value {
CopilotSettings::On => true,
CopilotSettings::Off => false,
}
}
}
impl CopilotSettings {
pub fn is_on(&self) -> bool {
<CopilotSettings as Into<bool>>::into(*self)
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
pub enum BaseKeymap {
#[default]
@ -150,6 +128,29 @@ impl TelemetrySettings {
}
}
#[derive(Clone, Debug, Default)]
pub struct CopilotSettings {
pub disabled_globs: Vec<glob::Pattern>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct CopilotSettingsContent {
#[serde(default)]
disabled_globs: Vec<String>,
}
impl From<CopilotSettingsContent> for CopilotSettings {
fn from(value: CopilotSettingsContent) -> Self {
Self {
disabled_globs: value
.disabled_globs
.into_iter()
.filter_map(|p| glob::Pattern::new(&p).ok())
.collect(),
}
}
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct GitSettings {
pub git_gutter: Option<GitGutter>,
@ -390,6 +391,8 @@ pub struct SettingsFileContent {
#[serde(default)]
pub buffer_font_features: Option<fonts::Features>,
#[serde(default)]
pub copilot: Option<CopilotSettingsContent>,
#[serde(default)]
pub active_pane_magnification: Option<f32>,
#[serde(default)]
pub cursor_blink: Option<bool>,
@ -438,8 +441,7 @@ pub struct LspSettings {
pub initialization_options: Option<Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Features {
pub copilot: bool,
}
@ -506,6 +508,7 @@ impl Settings {
show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions),
},
editor_overrides: Default::default(),
copilot: defaults.copilot.unwrap().into(),
git: defaults.git.unwrap(),
git_overrides: Default::default(),
journal_defaults: defaults.journal,
@ -576,6 +579,9 @@ impl Settings {
merge(&mut self.base_keymap, data.base_keymap);
merge(&mut self.features.copilot, data.features.copilot);
if let Some(copilot) = data.copilot.map(CopilotSettings::from) {
self.copilot = copilot;
}
self.editor_overrides = data.editor;
self.git_overrides = data.git.unwrap_or_default();
self.journal_overrides = data.journal;
@ -751,6 +757,7 @@ impl Settings {
show_copilot_suggestions: Some(true),
},
editor_overrides: Default::default(),
copilot: Default::default(),
journal_defaults: Default::default(),
journal_overrides: Default::default(),
terminal_defaults: Default::default(),