From 12cd712b53a3cfd825d826c8962382c5c7ff3f9b Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 6 Jan 2023 22:46:32 -0500 Subject: [PATCH 1/7] Require start autocomplete query byte to match a completion word start byte --- crates/editor/src/editor.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d8ee49866b..354a2be97d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -827,6 +827,40 @@ impl CompletionsMenu { }) .collect() }; + + //Remove all candidates where the query's start does not match the start of any word in the candidate + if let Some(query) = query { + if let Some(&start) = query.as_bytes().get(0) { + let start = start.to_ascii_lowercase(); + matches.retain(|m| { + let bytes = m.string.as_bytes(); + let mut index = 0; + + std::iter::from_fn(move || { + let start_index = index; + while index < bytes.len() { + let current_upper = bytes[index].is_ascii_uppercase(); + let has_more = index + 1 < bytes.len(); + let next_upper = has_more && bytes[index + 1].is_ascii_uppercase(); + + index += 1; + if !current_upper && next_upper { + return Some(&m.string[start_index..index]); + } + } + + index = bytes.len(); + if start_index < bytes.len() { + return Some(&m.string[start_index..]); + } + None + }) + .flat_map(|w| w.split_inclusive('_')) + .any(|w| w.as_bytes().first().map(|&b| b.to_ascii_lowercase()) == Some(start)) + }); + } + } + matches.sort_unstable_by_key(|mat| { let completion = &self.completions[mat.candidate_id]; ( From a46ca323567aada6960467c447d789a606db3d6a Mon Sep 17 00:00:00 2001 From: Julia Date: Sat, 7 Jan 2023 15:34:28 -0500 Subject: [PATCH 2/7] Completion word start filtering which is codepoint aware --- crates/editor/src/editor.rs | 38 ++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 354a2be97d..4405fc0b33 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -830,33 +830,41 @@ impl CompletionsMenu { //Remove all candidates where the query's start does not match the start of any word in the candidate if let Some(query) = query { - if let Some(&start) = query.as_bytes().get(0) { - let start = start.to_ascii_lowercase(); - matches.retain(|m| { - let bytes = m.string.as_bytes(); + if let Some(query_start) = query.chars().next() { + matches.retain(|string_match| { + let text = &string_match.string; + let mut index = 0; + let mut codepoints = text.char_indices().peekable(); std::iter::from_fn(move || { let start_index = index; - while index < bytes.len() { - let current_upper = bytes[index].is_ascii_uppercase(); - let has_more = index + 1 < bytes.len(); - let next_upper = has_more && bytes[index + 1].is_ascii_uppercase(); + while let Some((new_index, codepoint)) = codepoints.next() { + index = new_index + codepoint.len_utf8(); + let current_upper = codepoint.is_uppercase(); + let next_upper = codepoints + .peek() + .map(|(_, c)| c.is_uppercase()) + .unwrap_or(false); - index += 1; if !current_upper && next_upper { - return Some(&m.string[start_index..index]); + return Some(&text[start_index..index]); } } - index = bytes.len(); - if start_index < bytes.len() { - return Some(&m.string[start_index..]); + index = text.len(); + if start_index < text.len() { + return Some(&text[start_index..]); } None }) - .flat_map(|w| w.split_inclusive('_')) - .any(|w| w.as_bytes().first().map(|&b| b.to_ascii_lowercase()) == Some(start)) + .flat_map(|word| word.split_inclusive('_')) + .any(|word| { + word.chars() + .flat_map(|codepoint| codepoint.to_lowercase()) + .zip(query_start.to_lowercase()) + .all(|(word_cp, query_cp)| word_cp == query_cp) + }) }); } } From 529ccbda3a9e784538e8aef5474236de2d848c21 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 7 Jan 2023 16:25:00 -0700 Subject: [PATCH 3/7] Introduce git index mutations to randomized collaboration test The test now fails at the following seed: ```bash SEED=850 ITERATIONS=1 OPERATIONS=131 cargo test --package=collab random ``` --- .../src/tests/randomized_integration_tests.rs | 68 +++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index a42d4f7d32..b067cac5ff 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -14,8 +14,11 @@ use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf1 use lsp::FakeLanguageServer; use parking_lot::Mutex; use project::{search::SearchQuery, Project}; -use rand::prelude::*; -use std::{env, path::PathBuf, sync::Arc}; +use rand::{ + distributions::{Alphanumeric, DistString}, + prelude::*, +}; +use std::{env, ffi::OsStr, path::PathBuf, sync::Arc}; #[gpui::test(iterations = 100)] async fn test_random_collaboration( @@ -382,9 +385,15 @@ async fn test_random_collaboration( ); } (None, None) => {} - (None, _) => panic!("host's file is None, guest's isn't "), - (_, None) => panic!("guest's file is None, hosts's isn't "), + (None, _) => panic!("host's file is None, guest's isn't"), + (_, None) => panic!("guest's file is None, hosts's isn't"), } + + let host_diff_base = + host_buffer.read_with(host_cx, |b, _| b.diff_base().map(ToString::to_string)); + let guest_diff_base = guest_buffer + .read_with(client_cx, |b, _| b.diff_base().map(ToString::to_string)); + assert_eq!(guest_diff_base, host_diff_base); } } } @@ -543,9 +552,10 @@ async fn randomly_mutate_client( 50..=59 if !client.local_projects.is_empty() || !client.remote_projects.is_empty() => { randomly_mutate_worktrees(client, &rng, cx).await?; } - 60..=84 if !client.local_projects.is_empty() || !client.remote_projects.is_empty() => { + 60..=74 if !client.local_projects.is_empty() || !client.remote_projects.is_empty() => { randomly_query_and_mutate_buffers(client, &rng, cx).await?; } + 75..=84 => randomly_mutate_git(client, &rng).await, _ => randomly_mutate_fs(client, &rng).await, } @@ -605,6 +615,54 @@ async fn randomly_mutate_active_call( Ok(()) } +async fn randomly_mutate_git(client: &mut TestClient, rng: &Mutex) { + let directories = client.fs.directories().await; + let mut dir_path = directories.choose(&mut *rng.lock()).unwrap().clone(); + if dir_path.file_name() == Some(OsStr::new(".git")) { + dir_path.pop(); + } + let mut git_dir_path = dir_path.clone(); + git_dir_path.push(".git"); + + if !directories.contains(&git_dir_path) { + log::info!( + "{}: creating git directory at {:?}", + client.username, + git_dir_path + ); + client.fs.create_dir(&git_dir_path).await.unwrap(); + } + + let mut child_paths = client.fs.read_dir(&dir_path).await.unwrap(); + let mut child_file_paths = Vec::new(); + while let Some(child_path) = child_paths.next().await { + let child_path = child_path.unwrap(); + if client.fs.is_file(&child_path).await { + child_file_paths.push(child_path); + } + } + let count = rng.lock().gen_range(0..=child_file_paths.len()); + child_file_paths.shuffle(&mut *rng.lock()); + child_file_paths.truncate(count); + + let mut new_index = Vec::new(); + for abs_child_file_path in &child_file_paths { + let child_file_path = abs_child_file_path.strip_prefix(&dir_path).unwrap(); + let new_base = Alphanumeric.sample_string(&mut *rng.lock(), 16); + new_index.push((child_file_path, new_base)); + } + log::info!( + "{}: updating Git index at {:?}: {:#?}", + client.username, + git_dir_path, + new_index + ); + client + .fs + .set_index_for_repo(&git_dir_path, &new_index) + .await; +} + async fn randomly_mutate_fs(client: &mut TestClient, rng: &Mutex) { let is_dir = rng.lock().gen::(); let mut new_path = client From 95098e4f29ae235d096a6453a72d59fc052305e6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sun, 8 Jan 2023 09:10:57 -0700 Subject: [PATCH 4/7] Update git diff base when synchronizing a guest's buffers --- crates/project/src/project.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 271d0f242b..608abc5815 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5189,20 +5189,27 @@ impl Project { let operations = buffer.serialize_ops(Some(remote_version), cx); let client = this.client.clone(); - let file = buffer.file().cloned(); + if let Some(file) = buffer.file() { + client + .send(proto::UpdateBufferFile { + project_id, + buffer_id: buffer_id as u64, + file: Some(file.to_proto()), + }) + .log_err(); + } + + client + .send(proto::UpdateDiffBase { + project_id, + buffer_id: buffer_id as u64, + diff_base: buffer.diff_base().map(Into::into), + }) + .log_err(); + cx.background() .spawn( async move { - if let Some(file) = file { - client - .send(proto::UpdateBufferFile { - project_id, - buffer_id: buffer_id as u64, - file: Some(file.to_proto()), - }) - .log_err(); - } - let operations = operations.await; for chunk in split_operations(operations) { client From ad7eaca443f7fd5c81c3823238b902666f2624ef Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sun, 8 Jan 2023 09:36:58 -0700 Subject: [PATCH 5/7] Make `Buffer::diff_base` available outside of tests --- crates/language/src/buffer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 66f8651e9d..9ceb6b32a9 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -682,7 +682,6 @@ impl Buffer { task } - #[cfg(any(test, feature = "test-support"))] pub fn diff_base(&self) -> Option<&str> { self.diff_base.as_deref() } From 97ed89a797fb11c9bfa3a6900dc62f817b628d1d Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 9 Jan 2023 13:02:44 -0500 Subject: [PATCH 6/7] Test that completion word splitting does reasonable things --- crates/editor/src/editor.rs | 59 ++++++++++++++++--------------- crates/editor/src/editor_tests.rs | 14 ++++++++ 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4405fc0b33..356fd0ca4f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -832,34 +832,9 @@ impl CompletionsMenu { if let Some(query) = query { if let Some(query_start) = query.chars().next() { matches.retain(|string_match| { - let text = &string_match.string; - - let mut index = 0; - let mut codepoints = text.char_indices().peekable(); - - std::iter::from_fn(move || { - let start_index = index; - while let Some((new_index, codepoint)) = codepoints.next() { - index = new_index + codepoint.len_utf8(); - let current_upper = codepoint.is_uppercase(); - let next_upper = codepoints - .peek() - .map(|(_, c)| c.is_uppercase()) - .unwrap_or(false); - - if !current_upper && next_upper { - return Some(&text[start_index..index]); - } - } - - index = text.len(); - if start_index < text.len() { - return Some(&text[start_index..]); - } - None - }) - .flat_map(|word| word.split_inclusive('_')) - .any(|word| { + split_words(&string_match.string).any(|word| { + //Check that the first codepoint of the word as lowercase matches the first + //codepoint of the query as lowercase word.chars() .flat_map(|codepoint| codepoint.to_lowercase()) .zip(query_start.to_lowercase()) @@ -6841,6 +6816,34 @@ pub fn styled_runs_for_code_label<'a>( }) } +pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator + 'a { + let mut index = 0; + let mut codepoints = text.char_indices().peekable(); + + std::iter::from_fn(move || { + let start_index = index; + while let Some((new_index, codepoint)) = codepoints.next() { + index = new_index + codepoint.len_utf8(); + let current_upper = codepoint.is_uppercase(); + let next_upper = codepoints + .peek() + .map(|(_, c)| c.is_uppercase()) + .unwrap_or(false); + + if !current_upper && next_upper { + return Some(&text[start_index..index]); + } + } + + index = text.len(); + if start_index < text.len() { + return Some(&text[start_index..]); + } + None + }) + .flat_map(|word| word.split_inclusive('_')) +} + trait RangeExt { fn sorted(&self) -> Range; fn to_inclusive(&self) -> RangeInclusive; diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 2fcc5f0014..798636c0a1 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5439,6 +5439,20 @@ async fn go_to_hunk(deterministic: Arc, cx: &mut gpui::TestAppCon ); } +#[test] +fn test_split_words() { + fn split<'a>(text: &'a str) -> Vec<&'a str> { + split_words(text).collect() + } + + assert_eq!(split("HelloWorld"), &["Hello", "World"]); + assert_eq!(split("hello_world"), &["hello_", "world"]); + assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]); + assert_eq!(split("Hello_World"), &["Hello_", "World"]); + assert_eq!(split("helloWOrld"), &["hello", "WOrld"]); + assert_eq!(split("helloworld"), &["helloworld"]); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point From 69e28d04b0b459edb66971941fbf2da69ba1a624 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 9 Jan 2023 10:19:11 -0800 Subject: [PATCH 7/7] Added open screenshare when following into non-followable buffer --- crates/workspace/src/workspace.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 06e19bbf88..6f37565fa4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2142,7 +2142,6 @@ impl Workspace { let call = self.active_call()?; let room = call.read(cx).room()?.read(cx); let participant = room.remote_participant_for_peer_id(leader_id)?; - let mut items_to_add = Vec::new(); match participant.location { call::ParticipantLocation::SharedProject { project_id } => { @@ -2153,6 +2152,12 @@ impl Workspace { .and_then(|id| state.items_by_leader_view_id.get(&id)) { items_to_add.push((pane.clone(), item.boxed_clone())); + } else { + if let Some(shared_screen) = + self.shared_screen_for_peer(leader_id, pane, cx) + { + items_to_add.push((pane.clone(), Box::new(shared_screen))); + } } } }