mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 02:17:35 +03:00
Start on adding support for editing via the assistant panel (#14795)
Note that this shouldn't have any visible user-facing behavior yet. The feature is incomplete but we wanna merge early to avoid a long-running branch. Release Notes: - N/A --------- Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
parent
87457f9ae8
commit
4d177918c1
24
Cargo.lock
generated
24
Cargo.lock
generated
@ -372,6 +372,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anthropic",
|
"anthropic",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"assets",
|
||||||
"assistant_slash_command",
|
"assistant_slash_command",
|
||||||
"async-watch",
|
"async-watch",
|
||||||
"breadcrumbs",
|
"breadcrumbs",
|
||||||
@ -408,6 +409,7 @@ dependencies = [
|
|||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"rope",
|
"rope",
|
||||||
|
"roxmltree 0.20.0",
|
||||||
"schemars",
|
"schemars",
|
||||||
"search",
|
"search",
|
||||||
"semantic_index",
|
"semantic_index",
|
||||||
@ -416,7 +418,6 @@ dependencies = [
|
|||||||
"settings",
|
"settings",
|
||||||
"similar",
|
"similar",
|
||||||
"smol",
|
"smol",
|
||||||
"strsim 0.11.1",
|
|
||||||
"strum",
|
"strum",
|
||||||
"telemetry_events",
|
"telemetry_events",
|
||||||
"terminal",
|
"terminal",
|
||||||
@ -2244,7 +2245,7 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"clap_lex 0.2.4",
|
"clap_lex 0.2.4",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"strsim 0.10.0",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
]
|
]
|
||||||
@ -2268,7 +2269,7 @@ dependencies = [
|
|||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex 0.5.1",
|
"clap_lex 0.5.1",
|
||||||
"strsim 0.10.0",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4272,7 +4273,7 @@ version = "0.5.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d"
|
checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"roxmltree",
|
"roxmltree 0.19.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5944,6 +5945,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"async-watch",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
@ -8901,6 +8903,12 @@ version = "0.19.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
|
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roxmltree"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rpc"
|
name = "rpc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -10359,12 +10367,6 @@ version = "0.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
@ -11899,7 +11901,7 @@ dependencies = [
|
|||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
"roxmltree",
|
"roxmltree 0.19.0",
|
||||||
"simplecss",
|
"simplecss",
|
||||||
"siphasher 1.0.1",
|
"siphasher 1.0.1",
|
||||||
"strict-num",
|
"strict-num",
|
||||||
|
241
assets/prompts/operations.md
Normal file
241
assets/prompts/operations.md
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
Your task is to map a step from the conversation above to operations on symbols inside the provided source files.
|
||||||
|
|
||||||
|
Guidelines:
|
||||||
|
- There's no need to describe *what* to do, just *where* to do it.
|
||||||
|
- If creating a file, assume any subsequent updates are included at the time of creation.
|
||||||
|
- Don't create and then update a file.
|
||||||
|
- We'll create it in one shot.
|
||||||
|
- Prefer updating symbols lower in the syntax tree if possible.
|
||||||
|
- Never include operations on a parent symbol and one of its children in the same <operations> block.
|
||||||
|
- Never nest an operation with another operation or include CDATA or other content. All operations are leaf nodes.
|
||||||
|
- Include a description attribute for each operation with a brief, one-line description of the change to perform.
|
||||||
|
- Descriptions are required for all operations except delete.
|
||||||
|
- When generating multiple operations, ensure the descriptions are specific to each individual operation.
|
||||||
|
- Avoid referring to the location in the description. Focus on the change to be made, not the location where it's made. That's implicit with the symbol you provide.
|
||||||
|
- Don't generate multiple operations at the same location. Instead, combine them together in a single operation with a succinct combined description.
|
||||||
|
|
||||||
|
The available operation types are:
|
||||||
|
|
||||||
|
1. <update>: Modify an existing symbol in a file.
|
||||||
|
2. <create_file>: Create a new file.
|
||||||
|
3. <insert_sibling_after>: Add a new symbol as sibling after an existing symbol in a file.
|
||||||
|
4. <append_child>: Add a new symbol as the last child of an existing symbol in a file.
|
||||||
|
5. <prepend_child>: Add a new symbol as the first child of an existing symbol in a file.
|
||||||
|
6. <delete>: Remove an existing symbol from a file. The `description` attribute is invalid for delete, but required for other ops.
|
||||||
|
|
||||||
|
All operations *require* a path.
|
||||||
|
Operations that *require* a symbol: <update>, <insert_sibling_after>, <delete>
|
||||||
|
Operations that don't allow a symbol: <create>
|
||||||
|
Operations that have an *optional* symbol: <prepend_child>, <append_child>
|
||||||
|
|
||||||
|
Example 1:
|
||||||
|
|
||||||
|
User:
|
||||||
|
```rs src/rectangle.rs
|
||||||
|
struct Rectangle {
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rectangle {
|
||||||
|
fn new(width: f64, height: f64) -> Self {
|
||||||
|
Rectangle { width, height }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Symbols for src/rectangle.rs:
|
||||||
|
- struct Rectangle
|
||||||
|
- impl Rectangle
|
||||||
|
- impl Rectangle fn new
|
||||||
|
|
||||||
|
<step>Add new methods 'calculate_area' and 'calculate_perimeter' to the Rectangle struct</step>
|
||||||
|
<step>Implement the 'Display' trait for the Rectangle struct</step>
|
||||||
|
|
||||||
|
What are the operations for the step: <step>Add a new method 'calculate_area' to the Rectangle struct</step>
|
||||||
|
|
||||||
|
Assistant (wrong):
|
||||||
|
<operations>
|
||||||
|
<append_child path="src/shapes.rs" symbol="impl Rectangle" description="Add calculate_area method" />
|
||||||
|
<append_child path="src/shapes.rs" symbol="impl Rectangle" description="Add calculate_perimeter method" />
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
This demonstrates what NOT to do. NEVER append multiple children at the same location.
|
||||||
|
|
||||||
|
Assistant (corrected):
|
||||||
|
<operations>
|
||||||
|
<append_child path="src/shapes.rs" symbol="impl Rectangle" description="Add calculate area and perimeter methods" />
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
User:
|
||||||
|
What are the operations for the step: <step>Implement the 'Display' trait for the Rectangle struct</step>
|
||||||
|
|
||||||
|
Assistant:
|
||||||
|
<operations>
|
||||||
|
<insert_sibling_after path="src/shapes.rs" symbol="impl Rectangle" description="Implement Display trait for Rectangle"/>
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
Example 2:
|
||||||
|
|
||||||
|
User:
|
||||||
|
```rs src/user.rs
|
||||||
|
struct User {
|
||||||
|
pub name: String,
|
||||||
|
age: u32,
|
||||||
|
email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
fn new(name: String, age: u32, email: String) -> Self {
|
||||||
|
User { name, age, email }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_info(&self) {
|
||||||
|
println!("Name: {}, Age: {}, Email: {}", self.name, self.age, self.email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Symbols for src/user.rs:
|
||||||
|
- struct User
|
||||||
|
- struct User pub name
|
||||||
|
- struct User age
|
||||||
|
- struct User email
|
||||||
|
- impl User
|
||||||
|
- impl User fn new
|
||||||
|
- impl User pub fn print_info
|
||||||
|
|
||||||
|
<step>Update the 'print_info' method to use formatted output</step>
|
||||||
|
<step>Remove the 'email' field from the User struct</step>
|
||||||
|
|
||||||
|
What are the operations for the step: <step>Update the 'print_info' method to use formatted output</step>
|
||||||
|
|
||||||
|
Assistant:
|
||||||
|
<operations>
|
||||||
|
<update path="src/user.rs" symbol="impl User fn print_info" description="Use formatted output" />
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
User:
|
||||||
|
What are the operations for the step: <step>Remove the 'email' field from the User struct</step>
|
||||||
|
|
||||||
|
Assistant:
|
||||||
|
<operations>
|
||||||
|
<delete path="src/user.rs" symbol="struct User email" description="Remove the email field" />
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
Example 3:
|
||||||
|
|
||||||
|
User:
|
||||||
|
```rs src/vehicle.rs
|
||||||
|
struct Vehicle {
|
||||||
|
make: String,
|
||||||
|
model: String,
|
||||||
|
year: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vehicle {
|
||||||
|
fn new(make: String, model: String, year: u32) -> Self {
|
||||||
|
Vehicle { make, model, year }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_year(&self) {
|
||||||
|
println!("Year: {}", self.year);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Symbols for src/vehicle.rs:
|
||||||
|
- struct Vehicle
|
||||||
|
- struct Vehicle make
|
||||||
|
- struct Vehicle model
|
||||||
|
- struct Vehicle year
|
||||||
|
- impl Vehicle
|
||||||
|
- impl Vehicle fn new
|
||||||
|
- impl Vehicle fn print_year
|
||||||
|
|
||||||
|
<step>Add a 'use std::fmt;' statement at the beginning of the file</step>
|
||||||
|
<step>Add a new method 'start_engine' in the Vehicle impl block</step>
|
||||||
|
|
||||||
|
What are the operations for the step: <step>Add a 'use std::fmt;' statement at the beginning of the file</step>
|
||||||
|
|
||||||
|
Assistant:
|
||||||
|
<operations>
|
||||||
|
<prepend_child path="src/vehicle.rs" description="Add 'use std::fmt' statement" />
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
User:
|
||||||
|
What are the operations for the step: <step>Add a new method 'start_engine' in the Vehicle impl block</step>
|
||||||
|
|
||||||
|
Assistant:
|
||||||
|
<operations>
|
||||||
|
<insert_sibling_after path="src/vehicle.rs" symbol="impl Vehicle fn new" description="Add start_engine method"/>
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
Example 4:
|
||||||
|
|
||||||
|
User:
|
||||||
|
```rs src/employee.rs
|
||||||
|
struct Employee {
|
||||||
|
name: String,
|
||||||
|
position: String,
|
||||||
|
salary: u32,
|
||||||
|
department: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Employee {
|
||||||
|
fn new(name: String, position: String, salary: u32, department: String) -> Self {
|
||||||
|
Employee { name, position, salary, department }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_details(&self) {
|
||||||
|
println!("Name: {}, Position: {}, Salary: {}, Department: {}",
|
||||||
|
self.name, self.position, self.salary, self.department);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn give_raise(&mut self, amount: u32) {
|
||||||
|
self.salary += amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Symbols for src/employee.rs:
|
||||||
|
- struct Employee
|
||||||
|
- struct Employee name
|
||||||
|
- struct Employee position
|
||||||
|
- struct Employee salary
|
||||||
|
- struct Employee department
|
||||||
|
- impl Employee
|
||||||
|
- impl Employee fn new
|
||||||
|
- impl Employee fn print_details
|
||||||
|
- impl Employee fn give_raise
|
||||||
|
|
||||||
|
<step>Make salary an f32</step>
|
||||||
|
|
||||||
|
What are the operations for the step: <step>Make salary an f32</step>
|
||||||
|
|
||||||
|
A (wrong):
|
||||||
|
<operations>
|
||||||
|
<update path="src/employee.rs" symbol="struct Employee" description="Change the type of salary to an f32" />
|
||||||
|
<update path="src/employee.rs" symbol="struct Employee salary" description="Change the type to an f32" />
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
This example demonstrates what not to do. `struct Employee salary` is a child of `struct Employee`.
|
||||||
|
|
||||||
|
A (corrected):
|
||||||
|
<operations>
|
||||||
|
<update path="src/employee.rs" symbol="struct Employee salary" description="Change the type to an f32" />
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
User:
|
||||||
|
What are the correct operations for the step: <step>Remove the 'department' field and update the 'print_details' method</step>
|
||||||
|
|
||||||
|
A:
|
||||||
|
<operations>
|
||||||
|
<delete path="src/employee.rs" symbol="struct Employee department" />
|
||||||
|
<update path="src/employee.rs" symbol="impl Employee fn print_details" description="Don't print the 'department' field" />
|
||||||
|
</operations>
|
||||||
|
|
||||||
|
Now generate the operations for the following step.
|
||||||
|
Output only valid XML containing valid operations with their required attributes.
|
||||||
|
NEVER output code or any other text inside <operation> tags. If you do, you will replaced with another model.
|
||||||
|
Your response *MUST* begin with <operations> and end with </operations>:
|
@ -107,6 +107,7 @@ impl ActivityIndicator {
|
|||||||
Editor::for_buffer(buffer, Some(project.clone()), cx)
|
Editor::for_buffer(buffer, Some(project.clone()), cx)
|
||||||
})),
|
})),
|
||||||
None,
|
None,
|
||||||
|
true,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})?;
|
})?;
|
||||||
|
@ -23,6 +23,7 @@ test-support = [
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anthropic = { workspace = true, features = ["schemars"] }
|
anthropic = { workspace = true, features = ["schemars"] }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
assets.workspace = true
|
||||||
assistant_slash_command.workspace = true
|
assistant_slash_command.workspace = true
|
||||||
async-watch.workspace = true
|
async-watch.workspace = true
|
||||||
breadcrumbs.workspace = true
|
breadcrumbs.workspace = true
|
||||||
@ -63,7 +64,6 @@ serde_json.workspace = true
|
|||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
similar.workspace = true
|
similar.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
strsim = "0.11"
|
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
telemetry_events.workspace = true
|
telemetry_events.workspace = true
|
||||||
terminal.workspace = true
|
terminal.workspace = true
|
||||||
@ -76,6 +76,7 @@ util.workspace = true
|
|||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
|
roxmltree = "0.20.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
|
@ -7,7 +7,6 @@ mod inline_assistant;
|
|||||||
mod model_selector;
|
mod model_selector;
|
||||||
mod prompt_library;
|
mod prompt_library;
|
||||||
mod prompts;
|
mod prompts;
|
||||||
mod search;
|
|
||||||
mod slash_command;
|
mod slash_command;
|
||||||
mod streaming_diff;
|
mod streaming_diff;
|
||||||
mod terminal_inline_assistant;
|
mod terminal_inline_assistant;
|
||||||
@ -53,9 +52,9 @@ actions!(
|
|||||||
InsertActivePrompt,
|
InsertActivePrompt,
|
||||||
DeployHistory,
|
DeployHistory,
|
||||||
DeployPromptLibrary,
|
DeployPromptLibrary,
|
||||||
ApplyEdit,
|
|
||||||
ConfirmCommand,
|
ConfirmCommand,
|
||||||
ToggleModelSelector
|
ToggleModelSelector,
|
||||||
|
DebugEditSteps
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
assistant_settings::{AssistantDockPosition, AssistantSettings},
|
assistant_settings::{AssistantDockPosition, AssistantSettings},
|
||||||
humanize_token_count, parse_next_edit_suggestion,
|
humanize_token_count,
|
||||||
prompt_library::open_prompt_library,
|
prompt_library::open_prompt_library,
|
||||||
search::*,
|
|
||||||
slash_command::{
|
slash_command::{
|
||||||
default_command::DefaultSlashCommand,
|
default_command::DefaultSlashCommand,
|
||||||
docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
|
docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
|
||||||
SlashCommandCompletionProvider, SlashCommandRegistry,
|
SlashCommandCompletionProvider, SlashCommandRegistry,
|
||||||
},
|
},
|
||||||
terminal_inline_assistant::TerminalInlineAssistant,
|
terminal_inline_assistant::TerminalInlineAssistant,
|
||||||
ApplyEdit, Assist, CompletionProvider, ConfirmCommand, Context, ContextEvent, ContextId,
|
Assist, CompletionProvider, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore,
|
||||||
ContextStore, CycleMessageRole, DeployHistory, DeployPromptLibrary, EditSuggestion,
|
CycleMessageRole, DebugEditSteps, DeployHistory, DeployPromptLibrary, EditStep,
|
||||||
InlineAssist, InlineAssistant, InsertIntoEditor, MessageStatus, ModelSelector,
|
EditStepOperations, EditSuggestionGroup, InlineAssist, InlineAssistId, InlineAssistant,
|
||||||
PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
|
InsertIntoEditor, MessageStatus, ModelSelector, PendingSlashCommand, PendingSlashCommandStatus,
|
||||||
ResetKey, Role, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector,
|
QuoteSelection, RemoteContextMetadata, ResetKey, Role, SavedContextMetadata, Split,
|
||||||
|
ToggleFocus, ToggleModelSelector,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
||||||
@ -25,29 +25,36 @@ use editor::{
|
|||||||
display_map::{
|
display_map::{
|
||||||
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, RenderBlock, ToDisplayPoint,
|
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, RenderBlock, ToDisplayPoint,
|
||||||
},
|
},
|
||||||
scroll::{Autoscroll, AutoscrollStrategy},
|
scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor},
|
||||||
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
|
Anchor, Editor, EditorEvent, ExcerptRange, MultiBuffer, RowExt, ToOffset as _, ToPoint,
|
||||||
};
|
};
|
||||||
use editor::{display_map::CreaseId, FoldPlaceholder};
|
use editor::{display_map::CreaseId, FoldPlaceholder};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, percentage, point, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
|
div, percentage, point, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
|
||||||
AsyncWindowContext, ClipboardItem, DismissEvent, Empty, EventEmitter, FocusHandle,
|
AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty, Entity, EventEmitter,
|
||||||
FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels, Render,
|
FocusHandle, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels,
|
||||||
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
|
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
|
||||||
UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext,
|
UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use indexed_docs::IndexedDocsStore;
|
use indexed_docs::IndexedDocsStore;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::SoftWrap, AutoindentMode, Buffer, LanguageRegistry, LspAdapterDelegate,
|
language_settings::SoftWrap, Buffer, Capability, LanguageRegistry, LspAdapterDelegate, Point,
|
||||||
OffsetRangeExt as _, Point, ToOffset,
|
ToOffset,
|
||||||
};
|
};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
|
use project::{Project, ProjectLspAdapterDelegate};
|
||||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{cmp, fmt::Write, ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
use std::{
|
||||||
|
cmp::{self, Ordering},
|
||||||
|
fmt::Write,
|
||||||
|
ops::Range,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
@ -60,7 +67,8 @@ use util::ResultExt;
|
|||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
item::{self, BreadcrumbText, FollowableItem, Item, ItemHandle},
|
item::{self, BreadcrumbText, FollowableItem, Item, ItemHandle},
|
||||||
pane,
|
notifications::NotifyTaskExt,
|
||||||
|
pane::{self, SaveIntent},
|
||||||
searchable::{SearchEvent, SearchableItem},
|
searchable::{SearchEvent, SearchableItem},
|
||||||
Pane, Save, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
Pane, Save, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||||
};
|
};
|
||||||
@ -591,6 +599,7 @@ impl AssistantPanel {
|
|||||||
make_lsp_adapter_delegate(workspace.project(), cx).log_err()
|
make_lsp_adapter_delegate(workspace.project(), cx).log_err()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let assistant_panel = cx.view().downgrade();
|
||||||
let editor = cx.new_view(|cx| {
|
let editor = cx.new_view(|cx| {
|
||||||
let mut editor = ContextEditor::for_context(
|
let mut editor = ContextEditor::for_context(
|
||||||
context,
|
context,
|
||||||
@ -598,6 +607,7 @@ impl AssistantPanel {
|
|||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
self.project.clone(),
|
self.project.clone(),
|
||||||
lsp_adapter_delegate,
|
lsp_adapter_delegate,
|
||||||
|
assistant_panel,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
editor.insert_default_prompt(cx);
|
editor.insert_default_prompt(cx);
|
||||||
@ -720,6 +730,7 @@ impl AssistantPanel {
|
|||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let context = context.await?;
|
let context = context.await?;
|
||||||
|
let assistant_panel = this.clone();
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
let workspace = workspace
|
let workspace = workspace
|
||||||
.upgrade()
|
.upgrade()
|
||||||
@ -731,6 +742,7 @@ impl AssistantPanel {
|
|||||||
workspace,
|
workspace,
|
||||||
project,
|
project,
|
||||||
lsp_adapter_delegate,
|
lsp_adapter_delegate,
|
||||||
|
assistant_panel,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -774,6 +786,7 @@ impl AssistantPanel {
|
|||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let context = context.await?;
|
let context = context.await?;
|
||||||
|
let assistant_panel = this.clone();
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
let workspace = workspace
|
let workspace = workspace
|
||||||
.upgrade()
|
.upgrade()
|
||||||
@ -785,6 +798,7 @@ impl AssistantPanel {
|
|||||||
workspace,
|
workspace,
|
||||||
this.project.clone(),
|
this.project.clone(),
|
||||||
lsp_adapter_delegate,
|
lsp_adapter_delegate,
|
||||||
|
assistant_panel,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -956,10 +970,18 @@ struct ScrollPosition {
|
|||||||
cursor: Anchor,
|
cursor: Anchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ActiveEditStep {
|
||||||
|
start: language::Anchor,
|
||||||
|
assist_ids: Vec<InlineAssistId>,
|
||||||
|
editor: Option<WeakView<Editor>>,
|
||||||
|
_open_editor: Task<Result<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ContextEditor {
|
pub struct ContextEditor {
|
||||||
context: Model<Context>,
|
context: Model<Context>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
|
project: Model<Project>,
|
||||||
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
blocks: HashSet<BlockId>,
|
blocks: HashSet<BlockId>,
|
||||||
@ -968,6 +990,8 @@ pub struct ContextEditor {
|
|||||||
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||||
pending_slash_command_blocks: HashMap<Range<language::Anchor>, BlockId>,
|
pending_slash_command_blocks: HashMap<Range<language::Anchor>, BlockId>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
|
active_edit_step: Option<ActiveEditStep>,
|
||||||
|
assistant_panel: WeakView<AssistantPanel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextEditor {
|
impl ContextEditor {
|
||||||
@ -979,6 +1003,7 @@ impl ContextEditor {
|
|||||||
workspace: View<Workspace>,
|
workspace: View<Workspace>,
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
|
assistant_panel: WeakView<AssistantPanel>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let completion_provider = SlashCommandCompletionProvider::new(
|
let completion_provider = SlashCommandCompletionProvider::new(
|
||||||
@ -996,7 +1021,7 @@ impl ContextEditor {
|
|||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
editor.set_completion_provider(Box::new(completion_provider));
|
editor.set_completion_provider(Box::new(completion_provider));
|
||||||
editor.set_collaboration_hub(Box::new(project));
|
editor.set_collaboration_hub(Box::new(project.clone()));
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1017,9 +1042,12 @@ impl ContextEditor {
|
|||||||
remote_id: None,
|
remote_id: None,
|
||||||
fs,
|
fs,
|
||||||
workspace: workspace.downgrade(),
|
workspace: workspace.downgrade(),
|
||||||
|
project,
|
||||||
pending_slash_command_creases: HashMap::default(),
|
pending_slash_command_creases: HashMap::default(),
|
||||||
pending_slash_command_blocks: HashMap::default(),
|
pending_slash_command_blocks: HashMap::default(),
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
|
active_edit_step: None,
|
||||||
|
assistant_panel,
|
||||||
};
|
};
|
||||||
this.update_message_headers(cx);
|
this.update_message_headers(cx);
|
||||||
this.insert_slash_command_output_sections(sections, cx);
|
this.insert_slash_command_output_sections(sections, cx);
|
||||||
@ -1052,31 +1080,37 @@ impl ContextEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
|
fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
|
||||||
let cursors = self.cursors(cx);
|
if !self.apply_edit_step(cx) {
|
||||||
|
self.send_to_model(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let user_messages = self.context.update(cx, |context, cx| {
|
fn apply_edit_step(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
let selected_messages = context
|
if let Some(step) = self.active_edit_step.as_ref() {
|
||||||
.messages_for_offsets(cursors, cx)
|
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||||
.into_iter()
|
for assist_id in &step.assist_ids {
|
||||||
.map(|message| message.id)
|
assistant.start_assist(*assist_id, cx);
|
||||||
.collect();
|
}
|
||||||
context.assist(selected_messages, cx)
|
!step.assist_ids.is_empty()
|
||||||
});
|
})
|
||||||
let new_selections = user_messages
|
} else {
|
||||||
.iter()
|
false
|
||||||
.map(|message| {
|
}
|
||||||
let cursor = message
|
}
|
||||||
|
|
||||||
|
fn send_to_model(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
|
||||||
|
let new_selection = {
|
||||||
|
let cursor = user_message
|
||||||
.start
|
.start
|
||||||
.to_offset(self.context.read(cx).buffer().read(cx));
|
.to_offset(self.context.read(cx).buffer().read(cx));
|
||||||
cursor..cursor
|
cursor..cursor
|
||||||
})
|
};
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if !new_selections.is_empty() {
|
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.change_selections(
|
editor.change_selections(
|
||||||
Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
|
Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
|
||||||
cx,
|
cx,
|
||||||
|selections| selections.select_ranges(new_selections),
|
|selections| selections.select_ranges([new_selection]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// Avoid scrolling to the new cursor position so the assistant's output is stable.
|
// Avoid scrolling to the new cursor position so the assistant's output is stable.
|
||||||
@ -1093,6 +1127,53 @@ impl ContextEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug_edit_steps(&mut self, _: &DebugEditSteps, cx: &mut ViewContext<Self>) {
|
||||||
|
let mut output = String::new();
|
||||||
|
for (i, step) in self.context.read(cx).edit_steps().iter().enumerate() {
|
||||||
|
output.push_str(&format!("Step {}:\n", i + 1));
|
||||||
|
output.push_str(&format!(
|
||||||
|
"Content: {}\n",
|
||||||
|
self.context
|
||||||
|
.read(cx)
|
||||||
|
.buffer()
|
||||||
|
.read(cx)
|
||||||
|
.text_for_range(step.source_range.clone())
|
||||||
|
.collect::<String>()
|
||||||
|
));
|
||||||
|
match &step.operations {
|
||||||
|
Some(EditStepOperations::Parsed {
|
||||||
|
operations,
|
||||||
|
raw_output,
|
||||||
|
}) => {
|
||||||
|
output.push_str(&format!("Raw Output:\n{raw_output}\n"));
|
||||||
|
output.push_str("Parsed Operations:\n");
|
||||||
|
for op in operations {
|
||||||
|
output.push_str(&format!(" {:?}\n", op));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(EditStepOperations::Pending(_)) => {
|
||||||
|
output.push_str("Operations: Pending\n");
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
output.push_str("Operations: None\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
let editor = self
|
||||||
|
.workspace
|
||||||
|
.update(cx, |workspace, cx| Editor::new_in_workspace(workspace, cx));
|
||||||
|
|
||||||
|
if let Ok(editor) = editor {
|
||||||
|
cx.spawn(|_, mut cx| async move {
|
||||||
|
let editor = editor.await?;
|
||||||
|
editor.update(&mut cx, |editor, cx| editor.set_text(output, cx))
|
||||||
|
})
|
||||||
|
.detach_and_notify_err(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
|
fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
|
||||||
let cursors = self.cursors(cx);
|
let cursors = self.cursors(cx);
|
||||||
self.context.update(cx, |context, cx| {
|
self.context.update(cx, |context, cx| {
|
||||||
@ -1222,39 +1303,8 @@ impl ContextEditor {
|
|||||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ContextEvent::EditSuggestionsChanged => {
|
ContextEvent::EditStepsChanged => {
|
||||||
self.editor.update(cx, |editor, cx| {
|
cx.notify();
|
||||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
|
||||||
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
|
||||||
let context = self.context.read(cx);
|
|
||||||
let highlighted_rows = context
|
|
||||||
.edit_suggestions()
|
|
||||||
.iter()
|
|
||||||
.map(|suggestion| {
|
|
||||||
let start = buffer
|
|
||||||
.anchor_in_excerpt(excerpt_id, suggestion.source_range.start)
|
|
||||||
.unwrap();
|
|
||||||
let end = buffer
|
|
||||||
.anchor_in_excerpt(excerpt_id, suggestion.source_range.end)
|
|
||||||
.unwrap();
|
|
||||||
start..=end
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
editor.clear_row_highlights::<EditSuggestion>();
|
|
||||||
for range in highlighted_rows {
|
|
||||||
editor.highlight_rows::<EditSuggestion>(
|
|
||||||
range,
|
|
||||||
Some(
|
|
||||||
cx.theme()
|
|
||||||
.colors()
|
|
||||||
.editor_document_highlight_read_background,
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
ContextEvent::SummaryChanged => {
|
ContextEvent::SummaryChanged => {
|
||||||
cx.emit(EditorEvent::TitleChanged);
|
cx.emit(EditorEvent::TitleChanged);
|
||||||
@ -1515,12 +1565,200 @@ impl ContextEditor {
|
|||||||
}
|
}
|
||||||
EditorEvent::SelectionsChanged { .. } => {
|
EditorEvent::SelectionsChanged { .. } => {
|
||||||
self.scroll_position = self.cursor_scroll_position(cx);
|
self.scroll_position = self.cursor_scroll_position(cx);
|
||||||
|
if self
|
||||||
|
.edit_step_for_cursor(cx)
|
||||||
|
.map(|step| step.source_range.start)
|
||||||
|
!= self.active_edit_step.as_ref().map(|step| step.start)
|
||||||
|
{
|
||||||
|
if let Some(old_active_edit_step) = self.active_edit_step.take() {
|
||||||
|
if let Some(editor) = old_active_edit_step
|
||||||
|
.editor
|
||||||
|
.and_then(|editor| editor.upgrade())
|
||||||
|
{
|
||||||
|
self.workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
if let Some(pane) = workspace.pane_for(&editor) {
|
||||||
|
pane.update(cx, |pane, cx| {
|
||||||
|
let item_id = editor.entity_id();
|
||||||
|
if pane.is_active_preview_item(item_id) {
|
||||||
|
pane.close_item_by_id(
|
||||||
|
item_id,
|
||||||
|
SaveIntent::Skip,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(new_active_step) = self.edit_step_for_cursor(cx) {
|
||||||
|
let suggestions = new_active_step.edit_suggestions(&self.project, cx);
|
||||||
|
self.active_edit_step = Some(ActiveEditStep {
|
||||||
|
start: new_active_step.source_range.start,
|
||||||
|
assist_ids: Vec::new(),
|
||||||
|
editor: None,
|
||||||
|
_open_editor: self.open_editor_for_edit_suggestions(suggestions, cx),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
cx.emit(event.clone());
|
cx.emit(event.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn open_editor_for_edit_suggestions(
|
||||||
|
&mut self,
|
||||||
|
edit_suggestions: Task<HashMap<Model<Buffer>, Vec<EditSuggestionGroup>>>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let project = self.project.clone();
|
||||||
|
let assistant_panel = self.assistant_panel.clone();
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let edit_suggestions = edit_suggestions.await;
|
||||||
|
|
||||||
|
let mut assist_ids = Vec::new();
|
||||||
|
let editor = if edit_suggestions.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
} else if edit_suggestions.len() == 1
|
||||||
|
&& edit_suggestions.values().next().unwrap().len() == 1
|
||||||
|
{
|
||||||
|
// If there's only one buffer and one suggestion group, open it directly
|
||||||
|
let (buffer, suggestion_groups) = edit_suggestions.into_iter().next().unwrap();
|
||||||
|
let suggestion_group = suggestion_groups.into_iter().next().unwrap();
|
||||||
|
let editor = workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
let active_pane = workspace.active_pane().clone();
|
||||||
|
workspace.open_project_item::<Editor>(active_pane, buffer, false, false, cx)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
for suggestion in suggestion_group.suggestions {
|
||||||
|
let description = suggestion.description.unwrap_or_else(|| "Delete".into());
|
||||||
|
let range = {
|
||||||
|
let buffer = editor.read(cx).buffer().read(cx).read(cx);
|
||||||
|
let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
|
||||||
|
buffer
|
||||||
|
.anchor_in_excerpt(excerpt_id, suggestion.range.start)
|
||||||
|
.unwrap()
|
||||||
|
..buffer
|
||||||
|
.anchor_in_excerpt(excerpt_id, suggestion.range.end)
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
let initial_text = suggestion.prepend_newline.then(|| "\n".into());
|
||||||
|
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||||
|
assist_ids.push(assistant.suggest_assist(
|
||||||
|
&editor,
|
||||||
|
range,
|
||||||
|
description,
|
||||||
|
initial_text,
|
||||||
|
Some(workspace.clone()),
|
||||||
|
assistant_panel.upgrade().as_ref(),
|
||||||
|
cx,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll the editor to the suggested assist
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
let anchor = {
|
||||||
|
let buffer = editor.buffer().read(cx).read(cx);
|
||||||
|
let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
|
||||||
|
buffer
|
||||||
|
.anchor_in_excerpt(excerpt_id, suggestion_group.context_range.start)
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.set_scroll_anchor(
|
||||||
|
ScrollAnchor {
|
||||||
|
offset: gpui::Point::default(),
|
||||||
|
anchor,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})?;
|
||||||
|
|
||||||
|
editor
|
||||||
|
} else {
|
||||||
|
// If there are multiple buffers or suggestion groups, create a multibuffer
|
||||||
|
let mut inline_assist_suggestions = Vec::new();
|
||||||
|
let multibuffer = cx.new_model(|cx| {
|
||||||
|
let replica_id = project.read(cx).replica_id();
|
||||||
|
let mut multibuffer = MultiBuffer::new(replica_id, Capability::ReadWrite);
|
||||||
|
for (buffer, suggestion_groups) in edit_suggestions {
|
||||||
|
let excerpt_ids = multibuffer.push_excerpts(
|
||||||
|
buffer,
|
||||||
|
suggestion_groups
|
||||||
|
.iter()
|
||||||
|
.map(|suggestion_group| ExcerptRange {
|
||||||
|
context: suggestion_group.context_range.clone(),
|
||||||
|
primary: None,
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (excerpt_id, suggestion_group) in
|
||||||
|
excerpt_ids.into_iter().zip(suggestion_groups)
|
||||||
|
{
|
||||||
|
for suggestion in suggestion_group.suggestions {
|
||||||
|
let description =
|
||||||
|
suggestion.description.unwrap_or_else(|| "Delete".into());
|
||||||
|
let range = {
|
||||||
|
let multibuffer = multibuffer.read(cx);
|
||||||
|
multibuffer
|
||||||
|
.anchor_in_excerpt(excerpt_id, suggestion.range.start)
|
||||||
|
.unwrap()
|
||||||
|
..multibuffer
|
||||||
|
.anchor_in_excerpt(excerpt_id, suggestion.range.end)
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
let initial_text =
|
||||||
|
suggestion.prepend_newline.then(|| "\n".to_string());
|
||||||
|
inline_assist_suggestions.push((range, description, initial_text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
multibuffer
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let editor = cx
|
||||||
|
.new_view(|cx| Editor::for_multibuffer(multibuffer, Some(project), true, cx))?;
|
||||||
|
cx.update(|cx| {
|
||||||
|
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||||
|
for (range, description, initial_text) in inline_assist_suggestions {
|
||||||
|
assist_ids.push(assistant.suggest_assist(
|
||||||
|
&editor,
|
||||||
|
range,
|
||||||
|
description,
|
||||||
|
initial_text,
|
||||||
|
Some(workspace.clone()),
|
||||||
|
assistant_panel.upgrade().as_ref(),
|
||||||
|
cx,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, cx)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
editor
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, _cx| {
|
||||||
|
if let Some(step) = this.active_edit_step.as_mut() {
|
||||||
|
step.assist_ids = assist_ids;
|
||||||
|
step.editor = Some(editor.downgrade());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_editor_search_event(
|
fn handle_editor_search_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: View<Editor>,
|
_: View<Editor>,
|
||||||
@ -1785,173 +2023,6 @@ impl ContextEditor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_edit(&mut self, _: &ApplyEdit, cx: &mut ViewContext<Self>) {
|
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let project = workspace.read(cx).project().clone();
|
|
||||||
|
|
||||||
struct Edit {
|
|
||||||
old_text: String,
|
|
||||||
new_text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
let context = self.context.read(cx);
|
|
||||||
let context_buffer = context.buffer().read(cx);
|
|
||||||
let context_buffer_snapshot = context_buffer.snapshot();
|
|
||||||
|
|
||||||
let selections = self.editor.read(cx).selections.disjoint_anchors();
|
|
||||||
let mut selections = selections.iter().peekable();
|
|
||||||
let selected_suggestions = context
|
|
||||||
.edit_suggestions()
|
|
||||||
.iter()
|
|
||||||
.filter(|suggestion| {
|
|
||||||
while let Some(selection) = selections.peek() {
|
|
||||||
if selection
|
|
||||||
.end
|
|
||||||
.text_anchor
|
|
||||||
.cmp(&suggestion.source_range.start, context_buffer)
|
|
||||||
.is_lt()
|
|
||||||
{
|
|
||||||
selections.next();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if selection
|
|
||||||
.start
|
|
||||||
.text_anchor
|
|
||||||
.cmp(&suggestion.source_range.end, context_buffer)
|
|
||||||
.is_gt()
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut opened_buffers: HashMap<PathBuf, Task<Result<Model<Buffer>>>> = HashMap::default();
|
|
||||||
project.update(cx, |project, cx| {
|
|
||||||
for suggestion in &selected_suggestions {
|
|
||||||
opened_buffers
|
|
||||||
.entry(suggestion.full_path.clone())
|
|
||||||
.or_insert_with(|| {
|
|
||||||
project.open_buffer_for_full_path(&suggestion.full_path, cx)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let mut buffers_by_full_path = HashMap::default();
|
|
||||||
for (full_path, buffer) in opened_buffers {
|
|
||||||
if let Some(buffer) = buffer.await.log_err() {
|
|
||||||
buffers_by_full_path.insert(full_path, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut suggestions_by_buffer = HashMap::default();
|
|
||||||
cx.update(|cx| {
|
|
||||||
for suggestion in selected_suggestions {
|
|
||||||
if let Some(buffer) = buffers_by_full_path.get(&suggestion.full_path) {
|
|
||||||
let (_, edits) = suggestions_by_buffer
|
|
||||||
.entry(buffer.clone())
|
|
||||||
.or_insert_with(|| (buffer.read(cx).snapshot(), Vec::new()));
|
|
||||||
|
|
||||||
let mut lines = context_buffer_snapshot
|
|
||||||
.as_rope()
|
|
||||||
.chunks_in_range(
|
|
||||||
suggestion.source_range.to_offset(&context_buffer_snapshot),
|
|
||||||
)
|
|
||||||
.lines();
|
|
||||||
if let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
|
|
||||||
let old_text = context_buffer_snapshot
|
|
||||||
.text_for_range(suggestion.old_text_range)
|
|
||||||
.collect();
|
|
||||||
let new_text = context_buffer_snapshot
|
|
||||||
.text_for_range(suggestion.new_text_range)
|
|
||||||
.collect();
|
|
||||||
edits.push(Edit { old_text, new_text });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let edits_by_buffer = cx
|
|
||||||
.background_executor()
|
|
||||||
.spawn(async move {
|
|
||||||
let mut result = HashMap::default();
|
|
||||||
for (buffer, (snapshot, suggestions)) in suggestions_by_buffer {
|
|
||||||
let edits =
|
|
||||||
result
|
|
||||||
.entry(buffer)
|
|
||||||
.or_insert(Vec::<(Range<language::Anchor>, _)>::new());
|
|
||||||
for suggestion in suggestions {
|
|
||||||
if let Some(range) =
|
|
||||||
fuzzy_search_lines(snapshot.as_rope(), &suggestion.old_text)
|
|
||||||
{
|
|
||||||
let edit_start = snapshot.anchor_after(range.start);
|
|
||||||
let edit_end = snapshot.anchor_before(range.end);
|
|
||||||
if let Err(ix) = edits.binary_search_by(|(range, _)| {
|
|
||||||
range.start.cmp(&edit_start, &snapshot)
|
|
||||||
}) {
|
|
||||||
edits.insert(
|
|
||||||
ix,
|
|
||||||
(edit_start..edit_end, suggestion.new_text.clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::info!(
|
|
||||||
"assistant edit did not match any text in buffer {:?}",
|
|
||||||
&suggestion.old_text
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut project_transaction = ProjectTransaction::default();
|
|
||||||
let (editor, workspace, title) = this.update(&mut cx, |this, cx| {
|
|
||||||
for (buffer_handle, edits) in edits_by_buffer {
|
|
||||||
buffer_handle.update(cx, |buffer, cx| {
|
|
||||||
buffer.start_transaction();
|
|
||||||
buffer.edit(
|
|
||||||
edits,
|
|
||||||
Some(AutoindentMode::Block {
|
|
||||||
original_indent_columns: Vec::new(),
|
|
||||||
}),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
buffer.end_transaction(cx);
|
|
||||||
if let Some(transaction) = buffer.finalize_last_transaction() {
|
|
||||||
project_transaction
|
|
||||||
.0
|
|
||||||
.insert(buffer_handle.clone(), transaction.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
this.editor.downgrade(),
|
|
||||||
this.workspace.clone(),
|
|
||||||
this.title(cx),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Editor::open_project_transaction(
|
|
||||||
&editor,
|
|
||||||
workspace,
|
|
||||||
project_transaction,
|
|
||||||
format!("Edits from {}", title),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
|
fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
|
||||||
self.context
|
self.context
|
||||||
.update(cx, |context, cx| context.save(None, self.fs.clone(), cx));
|
.update(cx, |context, cx| context.save(None, self.fs.clone(), cx));
|
||||||
@ -1967,6 +2038,14 @@ impl ContextEditor {
|
|||||||
|
|
||||||
fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let focus_handle = self.focus_handle(cx).clone();
|
let focus_handle = self.focus_handle(cx).clone();
|
||||||
|
let button_text = match self.edit_step_for_cursor(cx) {
|
||||||
|
Some(edit_step) => match &edit_step.operations {
|
||||||
|
Some(EditStepOperations::Pending(_)) => "Computing Changes...",
|
||||||
|
Some(EditStepOperations::Parsed { .. }) => "Apply Changes",
|
||||||
|
None => "Send",
|
||||||
|
},
|
||||||
|
None => "Send",
|
||||||
|
};
|
||||||
ButtonLike::new("send_button")
|
ButtonLike::new("send_button")
|
||||||
.style(ButtonStyle::Filled)
|
.style(ButtonStyle::Filled)
|
||||||
.layer(ElevationIndex::ModalSurface)
|
.layer(ElevationIndex::ModalSurface)
|
||||||
@ -1974,11 +2053,38 @@ impl ContextEditor {
|
|||||||
KeyBinding::for_action_in(&Assist, &focus_handle, cx)
|
KeyBinding::for_action_in(&Assist, &focus_handle, cx)
|
||||||
.map(|binding| binding.into_any_element()),
|
.map(|binding| binding.into_any_element()),
|
||||||
)
|
)
|
||||||
.child(Label::new("Send"))
|
.child(Label::new(button_text))
|
||||||
.on_click(move |_event, cx| {
|
.on_click(move |_event, cx| {
|
||||||
focus_handle.dispatch_action(&Assist, cx);
|
focus_handle.dispatch_action(&Assist, cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn edit_step_for_cursor<'a>(&'a self, cx: &'a AppContext) -> Option<&'a EditStep> {
|
||||||
|
let newest_cursor = self
|
||||||
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.selections
|
||||||
|
.newest_anchor()
|
||||||
|
.head()
|
||||||
|
.text_anchor;
|
||||||
|
let context = self.context.read(cx);
|
||||||
|
let buffer = context.buffer().read(cx);
|
||||||
|
|
||||||
|
let edit_steps = context.edit_steps();
|
||||||
|
edit_steps
|
||||||
|
.binary_search_by(|step| {
|
||||||
|
let step_range = step.source_range.clone();
|
||||||
|
if newest_cursor.cmp(&step_range.start, buffer).is_lt() {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if newest_cursor.cmp(&step_range.end, buffer).is_gt() {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.map(|index| &edit_steps[index])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<EditorEvent> for ContextEditor {}
|
impl EventEmitter<EditorEvent> for ContextEditor {}
|
||||||
@ -1995,7 +2101,7 @@ impl Render for ContextEditor {
|
|||||||
.capture_action(cx.listener(ContextEditor::confirm_command))
|
.capture_action(cx.listener(ContextEditor::confirm_command))
|
||||||
.on_action(cx.listener(ContextEditor::assist))
|
.on_action(cx.listener(ContextEditor::assist))
|
||||||
.on_action(cx.listener(ContextEditor::split))
|
.on_action(cx.listener(ContextEditor::split))
|
||||||
.on_action(cx.listener(ContextEditor::apply_edit))
|
.on_action(cx.listener(ContextEditor::debug_edit_steps))
|
||||||
.size_full()
|
.size_full()
|
||||||
.v_flex()
|
.v_flex()
|
||||||
.child(
|
.child(
|
||||||
|
@ -20,11 +20,10 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use futures::{future::BoxFuture, stream::BoxStream};
|
use futures::{future::BoxFuture, stream::BoxStream, StreamExt};
|
||||||
use gpui::{AnyView, AppContext, BorrowAppContext, Task, WindowContext};
|
use gpui::{AnyView, AppContext, BorrowAppContext, Task, WindowContext};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use std::time::Duration;
|
use std::{any::Any, pin::Pin, sync::Arc, task::Poll, time::Duration};
|
||||||
use std::{any::Any, sync::Arc};
|
|
||||||
|
|
||||||
/// Choose which model to use for openai provider.
|
/// Choose which model to use for openai provider.
|
||||||
/// If the model is not available, try to use the first available model, or fallback to the original model.
|
/// If the model is not available, try to use the first available model, or fallback to the original model.
|
||||||
@ -55,10 +54,21 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct CompletionResponse {
|
pub struct CompletionResponse {
|
||||||
pub inner: BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>,
|
inner: BoxStream<'static, Result<String>>,
|
||||||
_lock: SemaphoreGuardArc,
|
_lock: SemaphoreGuardArc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl futures::Stream for CompletionResponse {
|
||||||
|
type Item = Result<String>;
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Option<Self::Item>> {
|
||||||
|
Pin::new(&mut self.inner).poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait LanguageModelCompletionProvider: Send + Sync {
|
pub trait LanguageModelCompletionProvider: Send + Sync {
|
||||||
fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel>;
|
fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel>;
|
||||||
fn settings_version(&self) -> usize;
|
fn settings_version(&self) -> usize;
|
||||||
@ -72,7 +82,7 @@ pub trait LanguageModelCompletionProvider: Send + Sync {
|
|||||||
request: LanguageModelRequest,
|
request: LanguageModelRequest,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> BoxFuture<'static, Result<usize>>;
|
) -> BoxFuture<'static, Result<usize>>;
|
||||||
fn complete(
|
fn stream_completion(
|
||||||
&self,
|
&self,
|
||||||
request: LanguageModelRequest,
|
request: LanguageModelRequest,
|
||||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>;
|
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>;
|
||||||
@ -136,20 +146,34 @@ impl CompletionProvider {
|
|||||||
self.provider.read().count_tokens(request, cx)
|
self.provider.read().count_tokens(request, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete(
|
pub fn stream_completion(
|
||||||
&self,
|
&self,
|
||||||
request: LanguageModelRequest,
|
request: LanguageModelRequest,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Task<CompletionResponse> {
|
) -> Task<Result<CompletionResponse>> {
|
||||||
let rate_limiter = self.request_limiter.clone();
|
let rate_limiter = self.request_limiter.clone();
|
||||||
let provider = self.provider.clone();
|
let provider = self.provider.clone();
|
||||||
cx.background_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let lock = rate_limiter.acquire_arc().await;
|
let lock = rate_limiter.acquire_arc().await;
|
||||||
let response = provider.read().complete(request);
|
let response = provider.read().stream_completion(request);
|
||||||
CompletionResponse {
|
let response = response.await?;
|
||||||
|
Ok(CompletionResponse {
|
||||||
inner: response,
|
inner: response,
|
||||||
_lock: lock,
|
_lock: lock,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn complete(&self, request: LanguageModelRequest, cx: &AppContext) -> Task<Result<String>> {
|
||||||
|
let response = self.stream_completion(request, cx);
|
||||||
|
cx.foreground_executor().spawn(async move {
|
||||||
|
let mut chunks = response.await?;
|
||||||
|
let mut completion = String::new();
|
||||||
|
while let Some(chunk) = chunks.next().await {
|
||||||
|
let chunk = chunk?;
|
||||||
|
completion.push_str(&chunk);
|
||||||
|
}
|
||||||
|
Ok(completion)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,7 +324,7 @@ mod tests {
|
|||||||
|
|
||||||
// Enqueue some requests
|
// Enqueue some requests
|
||||||
for i in 0..MAX_CONCURRENT_COMPLETION_REQUESTS * 2 {
|
for i in 0..MAX_CONCURRENT_COMPLETION_REQUESTS * 2 {
|
||||||
let response = provider.complete(
|
let response = provider.stream_completion(
|
||||||
LanguageModelRequest {
|
LanguageModelRequest {
|
||||||
temperature: i as f32 / 10.0,
|
temperature: i as f32 / 10.0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -309,8 +333,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
cx.background_executor()
|
cx.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let response = response.await;
|
let mut stream = response.await.unwrap();
|
||||||
let mut stream = response.inner.await.unwrap();
|
|
||||||
while let Some(message) = stream.next().await {
|
while let Some(message) = stream.next().await {
|
||||||
message.unwrap();
|
message.unwrap();
|
||||||
}
|
}
|
||||||
@ -326,7 +349,7 @@ mod tests {
|
|||||||
|
|
||||||
// Get the first completion request that is in flight and mark it as completed.
|
// Get the first completion request that is in flight and mark it as completed.
|
||||||
let completion = fake_provider
|
let completion = fake_provider
|
||||||
.running_completions()
|
.pending_completions()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -347,7 +370,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Mark all completion requests as finished that are in flight.
|
// Mark all completion requests as finished that are in flight.
|
||||||
for request in fake_provider.running_completions() {
|
for request in fake_provider.pending_completions() {
|
||||||
fake_provider.finish_completion(&request);
|
fake_provider.finish_completion(&request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,7 +385,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Finish all remaining completion requests.
|
// Finish all remaining completion requests.
|
||||||
for request in fake_provider.running_completions() {
|
for request in fake_provider.pending_completions() {
|
||||||
fake_provider.finish_completion(&request);
|
fake_provider.finish_completion(&request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ impl LanguageModelCompletionProvider for AnthropicCompletionProvider {
|
|||||||
count_open_ai_tokens(request, cx.background_executor())
|
count_open_ai_tokens(request, cx.background_executor())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(
|
fn stream_completion(
|
||||||
&self,
|
&self,
|
||||||
request: LanguageModelRequest,
|
request: LanguageModelRequest,
|
||||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
||||||
|
@ -135,7 +135,7 @@ impl LanguageModelCompletionProvider for CloudCompletionProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(
|
fn stream_completion(
|
||||||
&self,
|
&self,
|
||||||
mut request: LanguageModelRequest,
|
mut request: LanguageModelRequest,
|
||||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
||||||
|
@ -23,7 +23,7 @@ impl FakeCompletionProvider {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn running_completions(&self) -> Vec<LanguageModelRequest> {
|
pub fn pending_completions(&self) -> Vec<LanguageModelRequest> {
|
||||||
self.current_completion_txs
|
self.current_completion_txs
|
||||||
.lock()
|
.lock()
|
||||||
.keys()
|
.keys()
|
||||||
@ -35,7 +35,7 @@ impl FakeCompletionProvider {
|
|||||||
self.current_completion_txs.lock().len()
|
self.current_completion_txs.lock().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_completion(&self, request: &LanguageModelRequest, chunk: String) {
|
pub fn send_completion_chunk(&self, request: &LanguageModelRequest, chunk: String) {
|
||||||
let json = serde_json::to_string(request).unwrap();
|
let json = serde_json::to_string(request).unwrap();
|
||||||
self.current_completion_txs
|
self.current_completion_txs
|
||||||
.lock()
|
.lock()
|
||||||
@ -45,10 +45,19 @@ impl FakeCompletionProvider {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_last_completion_chunk(&self, chunk: String) {
|
||||||
|
self.send_completion_chunk(self.pending_completions().last().unwrap(), chunk);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn finish_completion(&self, request: &LanguageModelRequest) {
|
pub fn finish_completion(&self, request: &LanguageModelRequest) {
|
||||||
self.current_completion_txs
|
self.current_completion_txs
|
||||||
.lock()
|
.lock()
|
||||||
.remove(&serde_json::to_string(request).unwrap());
|
.remove(&serde_json::to_string(request).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish_last_completion(&self) {
|
||||||
|
self.finish_completion(self.pending_completions().last().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +98,7 @@ impl LanguageModelCompletionProvider for FakeCompletionProvider {
|
|||||||
futures::future::ready(Ok(0)).boxed()
|
futures::future::ready(Ok(0)).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(
|
fn stream_completion(
|
||||||
&self,
|
&self,
|
||||||
_request: LanguageModelRequest,
|
_request: LanguageModelRequest,
|
||||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
||||||
|
@ -91,7 +91,7 @@ impl LanguageModelCompletionProvider for OllamaCompletionProvider {
|
|||||||
async move { Ok(token_count) }.boxed()
|
async move { Ok(token_count) }.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(
|
fn stream_completion(
|
||||||
&self,
|
&self,
|
||||||
request: LanguageModelRequest,
|
request: LanguageModelRequest,
|
||||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
||||||
|
@ -179,7 +179,7 @@ impl LanguageModelCompletionProvider for OpenAiCompletionProvider {
|
|||||||
count_open_ai_tokens(request, cx.background_executor())
|
count_open_ai_tokens(request, cx.background_executor())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(
|
fn stream_completion(
|
||||||
&self,
|
&self,
|
||||||
request: LanguageModelRequest,
|
request: LanguageModelRequest,
|
||||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,12 @@ use editor::{
|
|||||||
ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
|
ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
|
||||||
};
|
};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
|
use futures::{
|
||||||
|
channel::mpsc,
|
||||||
|
future::LocalBoxFuture,
|
||||||
|
stream::{self, BoxStream},
|
||||||
|
SinkExt, Stream, StreamExt,
|
||||||
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, Global, HighlightStyle,
|
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, Global, HighlightStyle,
|
||||||
Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView,
|
Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView,
|
||||||
@ -28,8 +33,11 @@ use parking_lot::Mutex;
|
|||||||
use rope::Rope;
|
use rope::Rope;
|
||||||
use settings::{update_settings_file, Settings};
|
use settings::{update_settings_file, Settings};
|
||||||
use similar::TextDiff;
|
use similar::TextDiff;
|
||||||
|
use smol::future::FutureExt;
|
||||||
use std::{
|
use std::{
|
||||||
cmp, mem,
|
cmp,
|
||||||
|
future::Future,
|
||||||
|
mem,
|
||||||
ops::{Range, RangeInclusive},
|
ops::{Range, RangeInclusive},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@ -134,7 +142,6 @@ impl InlineAssistant {
|
|||||||
let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx));
|
let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx));
|
||||||
|
|
||||||
let mut assists = Vec::new();
|
let mut assists = Vec::new();
|
||||||
let mut assist_blocks = Vec::new();
|
|
||||||
let mut assist_to_focus = None;
|
let mut assist_to_focus = None;
|
||||||
for range in codegen_ranges {
|
for range in codegen_ranges {
|
||||||
let assist_id = self.next_assist_id.post_inc();
|
let assist_id = self.next_assist_id.post_inc();
|
||||||
@ -142,6 +149,7 @@ impl InlineAssistant {
|
|||||||
Codegen::new(
|
Codegen::new(
|
||||||
editor.read(cx).buffer().clone(),
|
editor.read(cx).buffer().clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
|
None,
|
||||||
self.telemetry.clone(),
|
self.telemetry.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@ -174,42 +182,18 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assist_blocks.push(BlockProperties {
|
let [prompt_block_id, end_block_id] =
|
||||||
style: BlockStyle::Sticky,
|
self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
|
||||||
position: range.start,
|
|
||||||
height: prompt_editor.read(cx).height_in_lines,
|
|
||||||
render: build_assist_editor_renderer(&prompt_editor),
|
|
||||||
disposition: BlockDisposition::Above,
|
|
||||||
});
|
|
||||||
assist_blocks.push(BlockProperties {
|
|
||||||
style: BlockStyle::Sticky,
|
|
||||||
position: range.end,
|
|
||||||
height: 1,
|
|
||||||
render: Box::new(|cx| {
|
|
||||||
v_flex()
|
|
||||||
.h_full()
|
|
||||||
.w_full()
|
|
||||||
.border_t_1()
|
|
||||||
.border_color(cx.theme().status().info_border)
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
disposition: BlockDisposition::Below,
|
|
||||||
});
|
|
||||||
assists.push((assist_id, prompt_editor));
|
|
||||||
}
|
|
||||||
|
|
||||||
let assist_block_ids = editor.update(cx, |editor, cx| {
|
assists.push((assist_id, prompt_editor, prompt_block_id, end_block_id));
|
||||||
editor.insert_blocks(assist_blocks, None, cx)
|
}
|
||||||
});
|
|
||||||
|
|
||||||
let editor_assists = self
|
let editor_assists = self
|
||||||
.assists_by_editor
|
.assists_by_editor
|
||||||
.entry(editor.downgrade())
|
.entry(editor.downgrade())
|
||||||
.or_insert_with(|| EditorInlineAssists::new(&editor, cx));
|
.or_insert_with(|| EditorInlineAssists::new(&editor, cx));
|
||||||
let mut assist_group = InlineAssistGroup::new();
|
let mut assist_group = InlineAssistGroup::new();
|
||||||
for ((assist_id, prompt_editor), block_ids) in
|
for (assist_id, prompt_editor, prompt_block_id, end_block_id) in assists {
|
||||||
assists.into_iter().zip(assist_block_ids.chunks_exact(2))
|
|
||||||
{
|
|
||||||
self.assists.insert(
|
self.assists.insert(
|
||||||
assist_id,
|
assist_id,
|
||||||
InlineAssist::new(
|
InlineAssist::new(
|
||||||
@ -218,8 +202,8 @@ impl InlineAssistant {
|
|||||||
assistant_panel.is_some(),
|
assistant_panel.is_some(),
|
||||||
editor,
|
editor,
|
||||||
&prompt_editor,
|
&prompt_editor,
|
||||||
block_ids[0],
|
prompt_block_id,
|
||||||
block_ids[1],
|
end_block_id,
|
||||||
prompt_editor.read(cx).codegen.clone(),
|
prompt_editor.read(cx).codegen.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
cx,
|
cx,
|
||||||
@ -235,6 +219,128 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn suggest_assist(
|
||||||
|
&mut self,
|
||||||
|
editor: &View<Editor>,
|
||||||
|
mut range: Range<Anchor>,
|
||||||
|
initial_prompt: String,
|
||||||
|
initial_insertion: Option<String>,
|
||||||
|
workspace: Option<WeakView<Workspace>>,
|
||||||
|
assistant_panel: Option<&View<AssistantPanel>>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> InlineAssistId {
|
||||||
|
let assist_group_id = self.next_assist_group_id.post_inc();
|
||||||
|
let prompt_buffer = cx.new_model(|cx| Buffer::local(&initial_prompt, cx));
|
||||||
|
let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx));
|
||||||
|
|
||||||
|
let assist_id = self.next_assist_id.post_inc();
|
||||||
|
|
||||||
|
let buffer = editor.read(cx).buffer().clone();
|
||||||
|
let prepend_transaction_id = initial_insertion.and_then(|initial_insertion| {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.start_transaction(cx);
|
||||||
|
buffer.edit([(range.start..range.start, initial_insertion)], None, cx);
|
||||||
|
buffer.end_transaction(cx)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
range.start = range.start.bias_left(&buffer.read(cx).read(cx));
|
||||||
|
range.end = range.end.bias_right(&buffer.read(cx).read(cx));
|
||||||
|
|
||||||
|
let codegen = cx.new_model(|cx| {
|
||||||
|
Codegen::new(
|
||||||
|
editor.read(cx).buffer().clone(),
|
||||||
|
range.clone(),
|
||||||
|
prepend_transaction_id,
|
||||||
|
self.telemetry.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
|
||||||
|
let prompt_editor = cx.new_view(|cx| {
|
||||||
|
PromptEditor::new(
|
||||||
|
assist_id,
|
||||||
|
gutter_dimensions.clone(),
|
||||||
|
self.prompt_history.clone(),
|
||||||
|
prompt_buffer.clone(),
|
||||||
|
codegen.clone(),
|
||||||
|
editor,
|
||||||
|
assistant_panel,
|
||||||
|
workspace.clone(),
|
||||||
|
self.fs.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let [prompt_block_id, end_block_id] =
|
||||||
|
self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
|
||||||
|
|
||||||
|
let editor_assists = self
|
||||||
|
.assists_by_editor
|
||||||
|
.entry(editor.downgrade())
|
||||||
|
.or_insert_with(|| EditorInlineAssists::new(&editor, cx));
|
||||||
|
|
||||||
|
let mut assist_group = InlineAssistGroup::new();
|
||||||
|
self.assists.insert(
|
||||||
|
assist_id,
|
||||||
|
InlineAssist::new(
|
||||||
|
assist_id,
|
||||||
|
assist_group_id,
|
||||||
|
assistant_panel.is_some(),
|
||||||
|
editor,
|
||||||
|
&prompt_editor,
|
||||||
|
prompt_block_id,
|
||||||
|
end_block_id,
|
||||||
|
prompt_editor.read(cx).codegen.clone(),
|
||||||
|
workspace.clone(),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
assist_group.assist_ids.push(assist_id);
|
||||||
|
editor_assists.assist_ids.push(assist_id);
|
||||||
|
self.assist_groups.insert(assist_group_id, assist_group);
|
||||||
|
assist_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_assist_blocks(
|
||||||
|
&self,
|
||||||
|
editor: &View<Editor>,
|
||||||
|
range: &Range<Anchor>,
|
||||||
|
prompt_editor: &View<PromptEditor>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> [BlockId; 2] {
|
||||||
|
let assist_blocks = vec![
|
||||||
|
BlockProperties {
|
||||||
|
style: BlockStyle::Sticky,
|
||||||
|
position: range.start,
|
||||||
|
height: prompt_editor.read(cx).height_in_lines,
|
||||||
|
render: build_assist_editor_renderer(prompt_editor),
|
||||||
|
disposition: BlockDisposition::Above,
|
||||||
|
},
|
||||||
|
BlockProperties {
|
||||||
|
style: BlockStyle::Sticky,
|
||||||
|
position: range.end,
|
||||||
|
height: 1,
|
||||||
|
render: Box::new(|cx| {
|
||||||
|
v_flex()
|
||||||
|
.h_full()
|
||||||
|
.w_full()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(cx.theme().status().info_border)
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
|
disposition: BlockDisposition::Below,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
let block_ids = editor.insert_blocks(assist_blocks, None, cx);
|
||||||
|
[block_ids[0], block_ids[1]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_prompt_editor_focus_in(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
|
fn handle_prompt_editor_focus_in(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
|
||||||
let assist = &self.assists[&assist_id];
|
let assist = &self.assists[&assist_id];
|
||||||
let Some(decorations) = assist.decorations.as_ref() else {
|
let Some(decorations) = assist.decorations.as_ref() else {
|
||||||
@ -379,6 +485,14 @@ impl InlineAssistant {
|
|||||||
cx.propagate();
|
cx.propagate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_editor_release(&mut self, editor: WeakView<Editor>, cx: &mut WindowContext) {
|
||||||
|
if let Some(editor_assists) = self.assists_by_editor.get_mut(&editor) {
|
||||||
|
for assist_id in editor_assists.assist_ids.clone() {
|
||||||
|
self.finish_assist(assist_id, true, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_editor_change(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
|
fn handle_editor_change(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
|
||||||
let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else {
|
let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else {
|
||||||
return;
|
return;
|
||||||
@ -698,7 +812,7 @@ impl InlineAssistant {
|
|||||||
assist_group.assist_ids.clone()
|
assist_group.assist_ids.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
|
pub fn start_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
|
||||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||||
assist
|
assist
|
||||||
} else {
|
} else {
|
||||||
@ -727,16 +841,26 @@ impl InlineAssistant {
|
|||||||
self.prompt_history.pop_front();
|
self.prompt_history.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
assist.codegen.update(cx, |codegen, cx| codegen.undo(cx));
|
|
||||||
let codegen = assist.codegen.clone();
|
let codegen = assist.codegen.clone();
|
||||||
|
let telemetry_id = CompletionProvider::global(cx).model().telemetry_id();
|
||||||
|
let chunks: LocalBoxFuture<Result<BoxStream<Result<String>>>> =
|
||||||
|
if user_prompt.trim().to_lowercase() == "delete" {
|
||||||
|
async { Ok(stream::empty().boxed()) }.boxed_local()
|
||||||
|
} else {
|
||||||
let request = self.request_for_inline_assist(assist_id, cx);
|
let request = self.request_for_inline_assist(assist_id, cx);
|
||||||
|
let mut cx = cx.to_async();
|
||||||
cx.spawn(|mut cx| async move {
|
async move {
|
||||||
let request = request.await?;
|
let request = request.await?;
|
||||||
codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx))?;
|
let chunks = cx
|
||||||
anyhow::Ok(())
|
.update(|cx| CompletionProvider::global(cx).stream_completion(request, cx))?
|
||||||
})
|
.await?;
|
||||||
.detach_and_log_err(cx);
|
Ok(chunks.boxed())
|
||||||
|
}
|
||||||
|
.boxed_local()
|
||||||
|
};
|
||||||
|
codegen.update(cx, |codegen, cx| {
|
||||||
|
codegen.start(telemetry_id, chunks, cx);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_for_inline_assist(
|
fn request_for_inline_assist(
|
||||||
@ -855,7 +979,7 @@ impl InlineAssistant {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
|
pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
|
||||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||||
assist
|
assist
|
||||||
} else {
|
} else {
|
||||||
@ -1074,6 +1198,14 @@ impl EditorInlineAssists {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
_subscriptions: vec![
|
_subscriptions: vec![
|
||||||
|
cx.observe_release(editor, {
|
||||||
|
let editor = editor.downgrade();
|
||||||
|
|_, cx| {
|
||||||
|
InlineAssistant::update_global(cx, |this, cx| {
|
||||||
|
this.handle_editor_release(editor, cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
cx.observe(editor, move |editor, cx| {
|
cx.observe(editor, move |editor, cx| {
|
||||||
InlineAssistant::update_global(cx, |this, cx| {
|
InlineAssistant::update_global(cx, |this, cx| {
|
||||||
this.handle_editor_change(editor, cx)
|
this.handle_editor_change(editor, cx)
|
||||||
@ -1138,7 +1270,7 @@ fn build_assist_editor_renderer(editor: &View<PromptEditor>) -> RenderBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
|
||||||
struct InlineAssistId(usize);
|
pub struct InlineAssistId(usize);
|
||||||
|
|
||||||
impl InlineAssistId {
|
impl InlineAssistId {
|
||||||
fn post_inc(&mut self) -> InlineAssistId {
|
fn post_inc(&mut self) -> InlineAssistId {
|
||||||
@ -1882,7 +2014,8 @@ pub struct Codegen {
|
|||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
edit_position: Anchor,
|
edit_position: Anchor,
|
||||||
last_equal_ranges: Vec<Range<Anchor>>,
|
last_equal_ranges: Vec<Range<Anchor>>,
|
||||||
transaction_id: Option<TransactionId>,
|
prepend_transaction_id: Option<TransactionId>,
|
||||||
|
generation_transaction_id: Option<TransactionId>,
|
||||||
status: CodegenStatus,
|
status: CodegenStatus,
|
||||||
generation: Task<()>,
|
generation: Task<()>,
|
||||||
diff: Diff,
|
diff: Diff,
|
||||||
@ -1911,6 +2044,7 @@ impl Codegen {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
buffer: Model<MultiBuffer>,
|
buffer: Model<MultiBuffer>,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
|
prepend_transaction_id: Option<TransactionId>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -1943,7 +2077,8 @@ impl Codegen {
|
|||||||
range,
|
range,
|
||||||
snapshot,
|
snapshot,
|
||||||
last_equal_ranges: Default::default(),
|
last_equal_ranges: Default::default(),
|
||||||
transaction_id: Default::default(),
|
prepend_transaction_id,
|
||||||
|
generation_transaction_id: None,
|
||||||
status: CodegenStatus::Idle,
|
status: CodegenStatus::Idle,
|
||||||
generation: Task::ready(()),
|
generation: Task::ready(()),
|
||||||
diff: Diff::default(),
|
diff: Diff::default(),
|
||||||
@ -1959,8 +2094,13 @@ impl Codegen {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
|
if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
|
||||||
if self.transaction_id == Some(*transaction_id) {
|
if self.generation_transaction_id == Some(*transaction_id) {
|
||||||
self.transaction_id = None;
|
self.generation_transaction_id = None;
|
||||||
|
self.generation = Task::ready(());
|
||||||
|
cx.emit(CodegenEvent::Undone);
|
||||||
|
} else if self.prepend_transaction_id == Some(*transaction_id) {
|
||||||
|
self.prepend_transaction_id = None;
|
||||||
|
self.generation_transaction_id = None;
|
||||||
self.generation = Task::ready(());
|
self.generation = Task::ready(());
|
||||||
cx.emit(CodegenEvent::Undone);
|
cx.emit(CodegenEvent::Undone);
|
||||||
}
|
}
|
||||||
@ -1971,7 +2111,12 @@ impl Codegen {
|
|||||||
&self.last_equal_ranges
|
&self.last_equal_ranges
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
|
pub fn start(
|
||||||
|
&mut self,
|
||||||
|
telemetry_id: String,
|
||||||
|
stream: impl 'static + Future<Output = Result<BoxStream<'static, Result<String>>>>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
let range = self.range.clone();
|
let range = self.range.clone();
|
||||||
let snapshot = self.snapshot.clone();
|
let snapshot = self.snapshot.clone();
|
||||||
let selected_text = snapshot
|
let selected_text = snapshot
|
||||||
@ -1985,15 +2130,17 @@ impl Codegen {
|
|||||||
.next()
|
.next()
|
||||||
.unwrap_or_else(|| snapshot.indent_size_for_line(MultiBufferRow(selection_start.row)));
|
.unwrap_or_else(|| snapshot.indent_size_for_line(MultiBufferRow(selection_start.row)));
|
||||||
|
|
||||||
let model_telemetry_id = prompt.model.telemetry_id();
|
|
||||||
let response = CompletionProvider::global(cx).complete(prompt, cx);
|
|
||||||
let telemetry = self.telemetry.clone();
|
let telemetry = self.telemetry.clone();
|
||||||
self.edit_position = range.start;
|
self.edit_position = range.start;
|
||||||
self.diff = Diff::default();
|
self.diff = Diff::default();
|
||||||
self.status = CodegenStatus::Pending;
|
self.status = CodegenStatus::Pending;
|
||||||
|
if let Some(transaction_id) = self.generation_transaction_id.take() {
|
||||||
|
self.buffer
|
||||||
|
.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
|
||||||
|
}
|
||||||
self.generation = cx.spawn(|this, mut cx| {
|
self.generation = cx.spawn(|this, mut cx| {
|
||||||
async move {
|
async move {
|
||||||
let response = response.await;
|
let chunks = stream.await;
|
||||||
let generate = async {
|
let generate = async {
|
||||||
let mut edit_start = range.start.to_offset(&snapshot);
|
let mut edit_start = range.start.to_offset(&snapshot);
|
||||||
|
|
||||||
@ -2003,7 +2150,7 @@ impl Codegen {
|
|||||||
let mut response_latency = None;
|
let mut response_latency = None;
|
||||||
let request_start = Instant::now();
|
let request_start = Instant::now();
|
||||||
let diff = async {
|
let diff = async {
|
||||||
let chunks = StripInvalidSpans::new(response.inner.await?);
|
let chunks = StripInvalidSpans::new(chunks?);
|
||||||
futures::pin_mut!(chunks);
|
futures::pin_mut!(chunks);
|
||||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||||
|
|
||||||
@ -2086,7 +2233,7 @@ impl Codegen {
|
|||||||
telemetry.report_assistant_event(
|
telemetry.report_assistant_event(
|
||||||
None,
|
None,
|
||||||
telemetry_events::AssistantKind::Inline,
|
telemetry_events::AssistantKind::Inline,
|
||||||
model_telemetry_id,
|
telemetry_id,
|
||||||
response_latency,
|
response_latency,
|
||||||
error_message,
|
error_message,
|
||||||
);
|
);
|
||||||
@ -2136,7 +2283,7 @@ impl Codegen {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if let Some(transaction) = transaction {
|
if let Some(transaction) = transaction {
|
||||||
if let Some(first_transaction) = this.transaction_id {
|
if let Some(first_transaction) = this.generation_transaction_id {
|
||||||
// Group all assistant edits into the first transaction.
|
// Group all assistant edits into the first transaction.
|
||||||
this.buffer.update(cx, |buffer, cx| {
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.merge_transactions(
|
buffer.merge_transactions(
|
||||||
@ -2146,7 +2293,7 @@ impl Codegen {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.transaction_id = Some(transaction);
|
this.generation_transaction_id = Some(transaction);
|
||||||
this.buffer.update(cx, |buffer, cx| {
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.finalize_last_transaction(cx)
|
buffer.finalize_last_transaction(cx)
|
||||||
});
|
});
|
||||||
@ -2189,7 +2336,12 @@ impl Codegen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
|
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
if let Some(transaction_id) = self.transaction_id.take() {
|
if let Some(transaction_id) = self.prepend_transaction_id.take() {
|
||||||
|
self.buffer
|
||||||
|
.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(transaction_id) = self.generation_transaction_id.take() {
|
||||||
self.buffer
|
self.buffer
|
||||||
.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
|
.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
|
||||||
}
|
}
|
||||||
@ -2451,11 +2603,8 @@ fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::FakeCompletionProvider;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::FakeCompletionProvider;
|
||||||
use futures::stream::{self};
|
use futures::stream::{self};
|
||||||
use gpui::{Context, TestAppContext};
|
use gpui::{Context, TestAppContext};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
@ -2466,6 +2615,7 @@ mod tests {
|
|||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
|
use std::{future, sync::Arc};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct DummyCompletionRequest {
|
pub struct DummyCompletionRequest {
|
||||||
@ -2475,7 +2625,7 @@ mod tests {
|
|||||||
#[gpui::test(iterations = 10)]
|
#[gpui::test(iterations = 10)]
|
||||||
async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
|
async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||||
cx.set_global(cx.update(SettingsStore::test));
|
cx.set_global(cx.update(SettingsStore::test));
|
||||||
let provider = cx.update(|cx| FakeCompletionProvider::setup_test(cx));
|
cx.update(|cx| FakeCompletionProvider::setup_test(cx));
|
||||||
cx.update(language_settings::init);
|
cx.update(language_settings::init);
|
||||||
|
|
||||||
let text = indoc! {"
|
let text = indoc! {"
|
||||||
@ -2493,14 +2643,17 @@ mod tests {
|
|||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
|
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
|
||||||
});
|
});
|
||||||
let codegen = cx.new_model(|cx| Codegen::new(buffer.clone(), range, None, cx));
|
let codegen = cx.new_model(|cx| Codegen::new(buffer.clone(), range, None, None, cx));
|
||||||
|
|
||||||
|
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
||||||
codegen.update(cx, |codegen, cx| {
|
codegen.update(cx, |codegen, cx| {
|
||||||
codegen.start(LanguageModelRequest::default(), cx)
|
codegen.start(
|
||||||
|
String::new(),
|
||||||
|
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.background_executor.run_until_parked();
|
|
||||||
|
|
||||||
let mut new_text = concat!(
|
let mut new_text = concat!(
|
||||||
" let mut x = 0;\n",
|
" let mut x = 0;\n",
|
||||||
" while x < 10 {\n",
|
" while x < 10 {\n",
|
||||||
@ -2511,11 +2664,11 @@ mod tests {
|
|||||||
let max_len = cmp::min(new_text.len(), 10);
|
let max_len = cmp::min(new_text.len(), 10);
|
||||||
let len = rng.gen_range(1..=max_len);
|
let len = rng.gen_range(1..=max_len);
|
||||||
let (chunk, suffix) = new_text.split_at(len);
|
let (chunk, suffix) = new_text.split_at(len);
|
||||||
provider.send_completion(&LanguageModelRequest::default(), chunk.into());
|
chunks_tx.unbounded_send(chunk.to_string()).unwrap();
|
||||||
new_text = suffix;
|
new_text = suffix;
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
}
|
}
|
||||||
provider.finish_completion(&LanguageModelRequest::default());
|
drop(chunks_tx);
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -2536,7 +2689,6 @@ mod tests {
|
|||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
mut rng: StdRng,
|
mut rng: StdRng,
|
||||||
) {
|
) {
|
||||||
let provider = cx.update(|cx| FakeCompletionProvider::setup_test(cx));
|
|
||||||
cx.set_global(cx.update(SettingsStore::test));
|
cx.set_global(cx.update(SettingsStore::test));
|
||||||
cx.update(language_settings::init);
|
cx.update(language_settings::init);
|
||||||
|
|
||||||
@ -2552,10 +2704,16 @@ mod tests {
|
|||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
|
snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
|
||||||
});
|
});
|
||||||
let codegen = cx.new_model(|cx| Codegen::new(buffer.clone(), range, None, cx));
|
let codegen = cx.new_model(|cx| Codegen::new(buffer.clone(), range, None, None, cx));
|
||||||
|
|
||||||
let request = LanguageModelRequest::default();
|
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
||||||
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
|
codegen.update(cx, |codegen, cx| {
|
||||||
|
codegen.start(
|
||||||
|
String::new(),
|
||||||
|
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
@ -2569,11 +2727,11 @@ mod tests {
|
|||||||
let max_len = cmp::min(new_text.len(), 10);
|
let max_len = cmp::min(new_text.len(), 10);
|
||||||
let len = rng.gen_range(1..=max_len);
|
let len = rng.gen_range(1..=max_len);
|
||||||
let (chunk, suffix) = new_text.split_at(len);
|
let (chunk, suffix) = new_text.split_at(len);
|
||||||
provider.send_completion(&LanguageModelRequest::default(), chunk.into());
|
chunks_tx.unbounded_send(chunk.to_string()).unwrap();
|
||||||
new_text = suffix;
|
new_text = suffix;
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
}
|
}
|
||||||
provider.finish_completion(&LanguageModelRequest::default());
|
drop(chunks_tx);
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -2594,7 +2752,7 @@ mod tests {
|
|||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
mut rng: StdRng,
|
mut rng: StdRng,
|
||||||
) {
|
) {
|
||||||
let provider = cx.update(|cx| FakeCompletionProvider::setup_test(cx));
|
cx.update(|cx| FakeCompletionProvider::setup_test(cx));
|
||||||
cx.set_global(cx.update(SettingsStore::test));
|
cx.set_global(cx.update(SettingsStore::test));
|
||||||
cx.update(language_settings::init);
|
cx.update(language_settings::init);
|
||||||
|
|
||||||
@ -2610,10 +2768,16 @@ mod tests {
|
|||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
|
snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
|
||||||
});
|
});
|
||||||
let codegen = cx.new_model(|cx| Codegen::new(buffer.clone(), range, None, cx));
|
let codegen = cx.new_model(|cx| Codegen::new(buffer.clone(), range, None, None, cx));
|
||||||
|
|
||||||
let request = LanguageModelRequest::default();
|
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
||||||
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
|
codegen.update(cx, |codegen, cx| {
|
||||||
|
codegen.start(
|
||||||
|
String::new(),
|
||||||
|
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
@ -2627,11 +2791,11 @@ mod tests {
|
|||||||
let max_len = cmp::min(new_text.len(), 10);
|
let max_len = cmp::min(new_text.len(), 10);
|
||||||
let len = rng.gen_range(1..=max_len);
|
let len = rng.gen_range(1..=max_len);
|
||||||
let (chunk, suffix) = new_text.split_at(len);
|
let (chunk, suffix) = new_text.split_at(len);
|
||||||
provider.send_completion(&LanguageModelRequest::default(), chunk.into());
|
chunks_tx.unbounded_send(chunk.to_string()).unwrap();
|
||||||
new_text = suffix;
|
new_text = suffix;
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
}
|
}
|
||||||
provider.finish_completion(&LanguageModelRequest::default());
|
drop(chunks_tx);
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -3,6 +3,7 @@ use crate::{
|
|||||||
InlineAssist, InlineAssistant, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
InlineAssist, InlineAssistant, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use assets::Assets;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
|
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
|
||||||
@ -12,8 +13,8 @@ use futures::{
|
|||||||
};
|
};
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, size, transparent_black, AppContext, BackgroundExecutor, Bounds, EventEmitter,
|
actions, point, size, transparent_black, AppContext, AssetSource, BackgroundExecutor, Bounds,
|
||||||
Global, HighlightStyle, PromptLevel, ReadGlobal, Subscription, Task, TextStyle,
|
EventEmitter, Global, HighlightStyle, PromptLevel, ReadGlobal, Subscription, Task, TextStyle,
|
||||||
TitlebarOptions, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
|
TitlebarOptions, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
|
||||||
};
|
};
|
||||||
use heed::{types::SerdeBincode, Database, RoTxn};
|
use heed::{types::SerdeBincode, Database, RoTxn};
|
||||||
@ -1296,6 +1297,17 @@ impl PromptStore {
|
|||||||
fn first(&self) -> Option<PromptMetadata> {
|
fn first(&self) -> Option<PromptMetadata> {
|
||||||
self.metadata_cache.read().metadata.first().cloned()
|
self.metadata_cache.read().metadata.first().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn operations_prompt(&self) -> String {
|
||||||
|
String::from_utf8(
|
||||||
|
Assets
|
||||||
|
.load("prompts/operations.md")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.to_vec(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps a shared future to a prompt store so it can be assigned as a context global.
|
/// Wraps a shared future to a prompt store so it can be assigned as a context global.
|
||||||
|
@ -1,171 +0,0 @@
|
|||||||
use language::Rope;
|
|
||||||
use std::ops::Range;
|
|
||||||
|
|
||||||
/// Search the given buffer for the given substring, ignoring any differences
|
|
||||||
/// in line indentation between the query and the buffer.
|
|
||||||
///
|
|
||||||
/// Returns a vector of ranges of byte offsets in the buffer corresponding
|
|
||||||
/// to the entire lines of the buffer.
|
|
||||||
pub fn fuzzy_search_lines(haystack: &Rope, needle: &str) -> Option<Range<usize>> {
|
|
||||||
const SIMILARITY_THRESHOLD: f64 = 0.8;
|
|
||||||
|
|
||||||
let mut best_match: Option<(Range<usize>, f64)> = None; // (range, score)
|
|
||||||
let mut haystack_lines = haystack.chunks().lines();
|
|
||||||
let mut haystack_line_start = 0;
|
|
||||||
while let Some(mut haystack_line) = haystack_lines.next() {
|
|
||||||
let next_haystack_line_start = haystack_line_start + haystack_line.len() + 1;
|
|
||||||
let mut advanced_to_next_haystack_line = false;
|
|
||||||
|
|
||||||
let mut matched = true;
|
|
||||||
let match_start = haystack_line_start;
|
|
||||||
let mut match_end = next_haystack_line_start;
|
|
||||||
let mut match_score = 0.0;
|
|
||||||
let mut needle_lines = needle.lines().peekable();
|
|
||||||
while let Some(needle_line) = needle_lines.next() {
|
|
||||||
let similarity = line_similarity(haystack_line, needle_line);
|
|
||||||
if similarity >= SIMILARITY_THRESHOLD {
|
|
||||||
match_end = haystack_lines.offset();
|
|
||||||
match_score += similarity;
|
|
||||||
|
|
||||||
if needle_lines.peek().is_some() {
|
|
||||||
if let Some(next_haystack_line) = haystack_lines.next() {
|
|
||||||
advanced_to_next_haystack_line = true;
|
|
||||||
haystack_line = next_haystack_line;
|
|
||||||
} else {
|
|
||||||
matched = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
matched = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matched
|
|
||||||
&& best_match
|
|
||||||
.as_ref()
|
|
||||||
.map(|(_, best_score)| match_score > *best_score)
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
best_match = Some((match_start..match_end, match_score));
|
|
||||||
}
|
|
||||||
|
|
||||||
if advanced_to_next_haystack_line {
|
|
||||||
haystack_lines.seek(next_haystack_line_start);
|
|
||||||
}
|
|
||||||
haystack_line_start = next_haystack_line_start;
|
|
||||||
}
|
|
||||||
|
|
||||||
best_match.map(|(range, _)| range)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the similarity between two lines, ignoring leading and trailing whitespace,
|
|
||||||
/// using the Jaro-Winkler distance.
|
|
||||||
///
|
|
||||||
/// Returns a value between 0.0 and 1.0, where 1.0 indicates an exact match.
|
|
||||||
fn line_similarity(line1: &str, line2: &str) -> f64 {
|
|
||||||
strsim::jaro_winkler(line1.trim(), line2.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use gpui::{AppContext, Context as _};
|
|
||||||
use language::Buffer;
|
|
||||||
use unindent::Unindent as _;
|
|
||||||
use util::test::marked_text_ranges;
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_fuzzy_search_lines(cx: &mut AppContext) {
|
|
||||||
let (text, expected_ranges) = marked_text_ranges(
|
|
||||||
&r#"
|
|
||||||
fn main() {
|
|
||||||
if a() {
|
|
||||||
assert_eq!(
|
|
||||||
1 + 2,
|
|
||||||
does_not_match,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("hi");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
1 + 2,
|
|
||||||
3,
|
|
||||||
); // this last line does not match
|
|
||||||
|
|
||||||
« assert_eq!(
|
|
||||||
1 + 2,
|
|
||||||
3,
|
|
||||||
);
|
|
||||||
»
|
|
||||||
|
|
||||||
« assert_eq!(
|
|
||||||
"something",
|
|
||||||
"else",
|
|
||||||
);
|
|
||||||
»
|
|
||||||
}
|
|
||||||
"#
|
|
||||||
.unindent(),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let buffer = cx.new_model(|cx| Buffer::local(&text, cx));
|
|
||||||
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
|
||||||
|
|
||||||
let actual_range = fuzzy_search_lines(
|
|
||||||
snapshot.as_rope(),
|
|
||||||
&"
|
|
||||||
assert_eq!(
|
|
||||||
1 + 2,
|
|
||||||
3,
|
|
||||||
);
|
|
||||||
"
|
|
||||||
.unindent(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(actual_range, expected_ranges[0]);
|
|
||||||
|
|
||||||
let actual_range = fuzzy_search_lines(
|
|
||||||
snapshot.as_rope(),
|
|
||||||
&"
|
|
||||||
assert_eq!(
|
|
||||||
1 + 2,
|
|
||||||
3,
|
|
||||||
);
|
|
||||||
"
|
|
||||||
.unindent(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(actual_range, expected_ranges[0]);
|
|
||||||
|
|
||||||
let actual_range = fuzzy_search_lines(
|
|
||||||
snapshot.as_rope(),
|
|
||||||
&"
|
|
||||||
asst_eq!(
|
|
||||||
\"something\",
|
|
||||||
\"els\"
|
|
||||||
)
|
|
||||||
"
|
|
||||||
.unindent(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(actual_range, expected_ranges[1]);
|
|
||||||
|
|
||||||
let actual_range = fuzzy_search_lines(
|
|
||||||
snapshot.as_rope(),
|
|
||||||
&"
|
|
||||||
assert_eq!(
|
|
||||||
2 + 1,
|
|
||||||
3,
|
|
||||||
);
|
|
||||||
"
|
|
||||||
.unindent(),
|
|
||||||
);
|
|
||||||
assert_eq!(actual_range, None);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1026,7 +1026,7 @@ impl Codegen {
|
|||||||
|
|
||||||
let telemetry = self.telemetry.clone();
|
let telemetry = self.telemetry.clone();
|
||||||
let model_telemetry_id = prompt.model.telemetry_id();
|
let model_telemetry_id = prompt.model.telemetry_id();
|
||||||
let response = CompletionProvider::global(cx).complete(prompt, cx);
|
let response = CompletionProvider::global(cx).stream_completion(prompt, cx);
|
||||||
|
|
||||||
self.generation = cx.spawn(|this, mut cx| async move {
|
self.generation = cx.spawn(|this, mut cx| async move {
|
||||||
let response = response.await;
|
let response = response.await;
|
||||||
@ -1037,8 +1037,8 @@ impl Codegen {
|
|||||||
let mut response_latency = None;
|
let mut response_latency = None;
|
||||||
let request_start = Instant::now();
|
let request_start = Instant::now();
|
||||||
let task = async {
|
let task = async {
|
||||||
let mut response = response.inner.await?;
|
let mut chunks = response?;
|
||||||
while let Some(chunk) = response.next().await {
|
while let Some(chunk) = chunks.next().await {
|
||||||
if response_latency.is_none() {
|
if response_latency.is_none() {
|
||||||
response_latency = Some(request_start.elapsed());
|
response_latency = Some(request_start.elapsed());
|
||||||
}
|
}
|
||||||
|
@ -288,7 +288,12 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
|
|||||||
Some(tab_description),
|
Some(tab_description),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
workspace.add_item_to_active_pane(Box::new(view.clone()), None, cx);
|
workspace.add_item_to_active_pane(
|
||||||
|
Box::new(view.clone()),
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
@ -266,7 +266,7 @@ async fn test_basic_following(
|
|||||||
|
|
||||||
// When client A activates a different editor, client B does so as well.
|
// When client A activates a different editor, client B does so as well.
|
||||||
workspace_a.update(cx_a, |workspace, cx| {
|
workspace_a.update(cx_a, |workspace, cx| {
|
||||||
workspace.activate_item(&editor_a1, cx)
|
workspace.activate_item(&editor_a1, true, true, cx)
|
||||||
});
|
});
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
workspace_b.update(cx_b, |workspace, cx| {
|
workspace_b.update(cx_b, |workspace, cx| {
|
||||||
@ -311,7 +311,7 @@ async fn test_basic_following(
|
|||||||
let editor = cx.new_view(|cx| {
|
let editor = cx.new_view(|cx| {
|
||||||
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, cx)
|
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, cx)
|
||||||
});
|
});
|
||||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
@ -401,7 +401,7 @@ async fn test_basic_following(
|
|||||||
workspace.unfollow(peer_id_a, cx).unwrap()
|
workspace.unfollow(peer_id_a, cx).unwrap()
|
||||||
});
|
});
|
||||||
workspace_a.update(cx_a, |workspace, cx| {
|
workspace_a.update(cx_a, |workspace, cx| {
|
||||||
workspace.activate_item(&editor_a2, cx)
|
workspace.activate_item(&editor_a2, true, true, cx)
|
||||||
});
|
});
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -466,7 +466,7 @@ async fn test_basic_following(
|
|||||||
|
|
||||||
// Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
|
// Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
|
||||||
workspace_b.update(cx_b, |workspace, cx| {
|
workspace_b.update(cx_b, |workspace, cx| {
|
||||||
workspace.activate_item(&multibuffer_editor_b, cx)
|
workspace.activate_item(&multibuffer_editor_b, true, true, cx)
|
||||||
});
|
});
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
workspace_a.update(cx_a, |workspace, cx| {
|
workspace_a.update(cx_a, |workspace, cx| {
|
||||||
|
@ -477,7 +477,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
|
||||||
editor.update(cx, |editor, cx| editor.focus(cx))
|
editor.update(cx, |editor, cx| editor.focus(cx))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -237,13 +237,13 @@ impl ProjectDiagnosticsEditor {
|
|||||||
|
|
||||||
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
||||||
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||||
workspace.activate_item(&existing, cx);
|
workspace.activate_item(&existing, true, true, cx);
|
||||||
} else {
|
} else {
|
||||||
let workspace_handle = cx.view().downgrade();
|
let workspace_handle = cx.view().downgrade();
|
||||||
let diagnostics = cx.new_view(|cx| {
|
let diagnostics = cx.new_view(|cx| {
|
||||||
ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
|
ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
|
||||||
});
|
});
|
||||||
workspace.add_item_to_active_pane(Box::new(diagnostics), None, cx);
|
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,13 +250,13 @@ impl GroupedDiagnosticsEditor {
|
|||||||
|
|
||||||
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
||||||
if let Some(existing) = workspace.item_of_type::<GroupedDiagnosticsEditor>(cx) {
|
if let Some(existing) = workspace.item_of_type::<GroupedDiagnosticsEditor>(cx) {
|
||||||
workspace.activate_item(&existing, cx);
|
workspace.activate_item(&existing, true, true, cx);
|
||||||
} else {
|
} else {
|
||||||
let workspace_handle = cx.view().downgrade();
|
let workspace_handle = cx.view().downgrade();
|
||||||
let diagnostics = cx.new_view(|cx| {
|
let diagnostics = cx.new_view(|cx| {
|
||||||
GroupedDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
|
GroupedDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
|
||||||
});
|
});
|
||||||
workspace.add_item_to_active_pane(Box::new(diagnostics), None, cx);
|
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1992,28 +1992,35 @@ impl Editor {
|
|||||||
_: &workspace::NewFile,
|
_: &workspace::NewFile,
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) {
|
) {
|
||||||
|
Self::new_in_workspace(workspace, cx).detach_and_prompt_err(
|
||||||
|
"Failed to create buffer",
|
||||||
|
cx,
|
||||||
|
|e, _| match e.error_code() {
|
||||||
|
ErrorCode::RemoteUpgradeRequired => Some(format!(
|
||||||
|
"The remote instance of Zed does not support this yet. It must be upgraded to {}",
|
||||||
|
e.error_tag("required").unwrap_or("the latest version")
|
||||||
|
)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_in_workspace(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) -> Task<Result<View<Editor>>> {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
let create = project.update(cx, |project, cx| project.create_buffer(cx));
|
let create = project.update(cx, |project, cx| project.create_buffer(cx));
|
||||||
|
|
||||||
cx.spawn(|workspace, mut cx| async move {
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
let buffer = create.await?;
|
let buffer = create.await?;
|
||||||
workspace.update(&mut cx, |workspace, cx| {
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(
|
let editor =
|
||||||
Box::new(
|
cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx));
|
||||||
cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
|
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
|
||||||
),
|
editor
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
|
|
||||||
ErrorCode::RemoteUpgradeRequired => Some(format!(
|
|
||||||
"The remote instance of Zed does not support this yet. It must be upgraded to {}",
|
|
||||||
e.error_tag("required").unwrap_or("the latest version")
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_file_in_direction(
|
pub fn new_file_in_direction(
|
||||||
@ -4658,7 +4665,7 @@ impl Editor {
|
|||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
let editor =
|
let editor =
|
||||||
cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), true, cx));
|
cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), true, cx));
|
||||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.highlight_background::<Self>(
|
editor.highlight_background::<Self>(
|
||||||
&ranges_to_highlight,
|
&ranges_to_highlight,
|
||||||
@ -9093,7 +9100,13 @@ impl Editor {
|
|||||||
workspace.active_pane().clone()
|
workspace.active_pane().clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
workspace.open_project_item(pane, target.buffer.clone(), cx)
|
workspace.open_project_item(
|
||||||
|
pane,
|
||||||
|
target.buffer.clone(),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
target_editor.update(cx, |target_editor, cx| {
|
target_editor.update(cx, |target_editor, cx| {
|
||||||
// When selecting a definition in a different buffer, disable the nav history
|
// When selecting a definition in a different buffer, disable the nav history
|
||||||
@ -9391,7 +9404,7 @@ impl Editor {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
workspace.add_item_to_active_pane(item.clone(), destination_index, cx);
|
workspace.add_item_to_active_pane(item.clone(), destination_index, true, cx);
|
||||||
}
|
}
|
||||||
workspace.active_pane().update(cx, |pane, cx| {
|
workspace.active_pane().update(cx, |pane, cx| {
|
||||||
pane.set_preview_item_id(Some(item_id), cx);
|
pane.set_preview_item_id(Some(item_id), cx);
|
||||||
@ -11342,7 +11355,8 @@ impl Editor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (buffer, ranges) in new_selections_by_buffer {
|
for (buffer, ranges) in new_selections_by_buffer {
|
||||||
let editor = workspace.open_project_item::<Self>(pane.clone(), buffer, cx);
|
let editor =
|
||||||
|
workspace.open_project_item::<Self>(pane.clone(), buffer, true, true, cx);
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
|
||||||
s.select_ranges(ranges);
|
s.select_ranges(ranges);
|
||||||
|
@ -10445,7 +10445,12 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
|
|||||||
workspace.active_item(cx).is_none(),
|
workspace.active_item(cx).is_none(),
|
||||||
"active item should be None before the first item is added"
|
"active item should be None before the first item is added"
|
||||||
);
|
);
|
||||||
workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
|
workspace.add_item_to_active_pane(
|
||||||
|
Box::new(multi_buffer_editor.clone()),
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
let active_item = workspace
|
let active_item = workspace
|
||||||
.active_item(cx)
|
.active_item(cx)
|
||||||
.expect("should have an active item after adding the multi buffer");
|
.expect("should have an active item after adding the multi buffer");
|
||||||
|
@ -113,6 +113,7 @@ pub fn expand_macro_recursively(
|
|||||||
cx.new_view(|cx| Editor::for_multibuffer(multibuffer, Some(project), true, cx)),
|
cx.new_view(|cx| Editor::for_multibuffer(multibuffer, Some(project), true, cx)),
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
|
true,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@ -48,10 +48,10 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
.find_map(|item| item.downcast::<ExtensionsPage>());
|
.find_map(|item| item.downcast::<ExtensionsPage>());
|
||||||
|
|
||||||
if let Some(existing) = existing {
|
if let Some(existing) = existing {
|
||||||
workspace.activate_item(&existing, cx);
|
workspace.activate_item(&existing, true, true, cx);
|
||||||
} else {
|
} else {
|
||||||
let extensions_page = ExtensionsPage::new(workspace, cx);
|
let extensions_page = ExtensionsPage::new(workspace, cx);
|
||||||
workspace.add_item_to_active_pane(Box::new(extensions_page), None, cx)
|
workspace.add_item_to_active_pane(Box::new(extensions_page), None, true, cx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register_action(move |workspace, _: &InstallDevExtension, cx| {
|
.register_action(move |workspace, _: &InstallDevExtension, cx| {
|
||||||
|
@ -1161,6 +1161,29 @@ impl<'a> WindowContext<'a> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a callback to be invoked when the given Model or View is released.
|
||||||
|
pub fn observe_release<E, T>(
|
||||||
|
&mut self,
|
||||||
|
entity: &E,
|
||||||
|
mut on_release: impl FnOnce(&mut T, &mut WindowContext) + 'static,
|
||||||
|
) -> Subscription
|
||||||
|
where
|
||||||
|
E: Entity<T>,
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
let entity_id = entity.entity_id();
|
||||||
|
let window_handle = self.window.handle;
|
||||||
|
let (subscription, activate) = self.app.release_listeners.insert(
|
||||||
|
entity_id,
|
||||||
|
Box::new(move |entity, cx| {
|
||||||
|
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||||
|
let _ = window_handle.update(cx, |_, cx| on_release(entity, cx));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
activate();
|
||||||
|
subscription
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates an [`AsyncWindowContext`], which has a static lifetime and can be held across
|
/// Creates an [`AsyncWindowContext`], which has a static lifetime and can be held across
|
||||||
/// await points in async code.
|
/// await points in async code.
|
||||||
pub fn to_async(&self) -> AsyncWindowContext {
|
pub fn to_async(&self) -> AsyncWindowContext {
|
||||||
|
@ -27,6 +27,7 @@ test-support = [
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
async-watch.workspace = true
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
@ -17,6 +17,7 @@ use crate::{
|
|||||||
LanguageScope, Outline, RunnableCapture, RunnableTag,
|
LanguageScope, Outline, RunnableCapture, RunnableTag,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use async_watch as watch;
|
||||||
pub use clock::ReplicaId;
|
pub use clock::ReplicaId;
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@ -32,7 +33,7 @@ use smol::future::yield_now;
|
|||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering, Reverse},
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
fmt,
|
fmt,
|
||||||
@ -104,6 +105,7 @@ pub struct Buffer {
|
|||||||
sync_parse_timeout: Duration,
|
sync_parse_timeout: Duration,
|
||||||
syntax_map: Mutex<SyntaxMap>,
|
syntax_map: Mutex<SyntaxMap>,
|
||||||
parsing_in_background: bool,
|
parsing_in_background: bool,
|
||||||
|
parse_status: (watch::Sender<ParseStatus>, watch::Receiver<ParseStatus>),
|
||||||
non_text_state_update_count: usize,
|
non_text_state_update_count: usize,
|
||||||
diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>,
|
diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>,
|
||||||
remote_selections: TreeMap<ReplicaId, SelectionSet>,
|
remote_selections: TreeMap<ReplicaId, SelectionSet>,
|
||||||
@ -119,6 +121,12 @@ pub struct Buffer {
|
|||||||
has_unsaved_edits: Cell<(clock::Global, bool)>,
|
has_unsaved_edits: Cell<(clock::Global, bool)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ParseStatus {
|
||||||
|
Idle,
|
||||||
|
Parsing,
|
||||||
|
}
|
||||||
|
|
||||||
/// An immutable, cheaply cloneable representation of a fixed
|
/// An immutable, cheaply cloneable representation of a fixed
|
||||||
/// state of a buffer.
|
/// state of a buffer.
|
||||||
pub struct BufferSnapshot {
|
pub struct BufferSnapshot {
|
||||||
@ -710,6 +718,7 @@ impl Buffer {
|
|||||||
parsing_in_background: false,
|
parsing_in_background: false,
|
||||||
non_text_state_update_count: 0,
|
non_text_state_update_count: 0,
|
||||||
sync_parse_timeout: Duration::from_millis(1),
|
sync_parse_timeout: Duration::from_millis(1),
|
||||||
|
parse_status: async_watch::channel(ParseStatus::Idle),
|
||||||
autoindent_requests: Default::default(),
|
autoindent_requests: Default::default(),
|
||||||
pending_autoindent: Default::default(),
|
pending_autoindent: Default::default(),
|
||||||
language: None,
|
language: None,
|
||||||
@ -1059,6 +1068,7 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.parse_status.0.send(ParseStatus::Parsing).unwrap();
|
||||||
match cx
|
match cx
|
||||||
.background_executor()
|
.background_executor()
|
||||||
.block_with_timeout(self.sync_parse_timeout, parse_task)
|
.block_with_timeout(self.sync_parse_timeout, parse_task)
|
||||||
@ -1101,10 +1111,15 @@ impl Buffer {
|
|||||||
self.non_text_state_update_count += 1;
|
self.non_text_state_update_count += 1;
|
||||||
self.syntax_map.lock().did_parse(syntax_snapshot);
|
self.syntax_map.lock().did_parse(syntax_snapshot);
|
||||||
self.request_autoindent(cx);
|
self.request_autoindent(cx);
|
||||||
|
self.parse_status.0.send(ParseStatus::Idle).unwrap();
|
||||||
cx.emit(Event::Reparsed);
|
cx.emit(Event::Reparsed);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_status(&self) -> watch::Receiver<ParseStatus> {
|
||||||
|
self.parse_status.1.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Assign to the buffer a set of diagnostics created by a given language server.
|
/// Assign to the buffer a set of diagnostics created by a given language server.
|
||||||
pub fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -2749,7 +2764,6 @@ impl BufferSnapshot {
|
|||||||
.map(|g| g.outline_config.as_ref().unwrap())
|
.map(|g| g.outline_config.as_ref().unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut stack = Vec::<Range<usize>>::new();
|
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
while let Some(mat) = matches.peek() {
|
while let Some(mat) = matches.peek() {
|
||||||
let config = &configs[mat.grammar_index];
|
let config = &configs[mat.grammar_index];
|
||||||
@ -2767,6 +2781,9 @@ impl BufferSnapshot {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut open_index = None;
|
||||||
|
let mut close_index = None;
|
||||||
|
|
||||||
let mut buffer_ranges = Vec::new();
|
let mut buffer_ranges = Vec::new();
|
||||||
for capture in mat.captures {
|
for capture in mat.captures {
|
||||||
let node_is_name;
|
let node_is_name;
|
||||||
@ -2778,6 +2795,12 @@ impl BufferSnapshot {
|
|||||||
{
|
{
|
||||||
node_is_name = false;
|
node_is_name = false;
|
||||||
} else {
|
} else {
|
||||||
|
if Some(capture.index) == config.open_capture_ix {
|
||||||
|
open_index = Some(capture.node.end_byte());
|
||||||
|
} else if Some(capture.index) == config.close_capture_ix {
|
||||||
|
close_index = Some(capture.node.start_byte());
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2850,22 +2873,45 @@ impl BufferSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
matches.advance();
|
matches.advance();
|
||||||
while stack.last().map_or(false, |prev_range| {
|
|
||||||
prev_range.start > item_range.start || prev_range.end < item_range.end
|
|
||||||
}) {
|
|
||||||
stack.pop();
|
|
||||||
}
|
|
||||||
stack.push(item_range.clone());
|
|
||||||
|
|
||||||
items.push(OutlineItem {
|
items.push(OutlineItem {
|
||||||
depth: stack.len() - 1,
|
depth: 0, // We'll calculate the depth later
|
||||||
range: self.anchor_after(item_range.start)..self.anchor_before(item_range.end),
|
range: item_range,
|
||||||
text,
|
text,
|
||||||
highlight_ranges,
|
highlight_ranges,
|
||||||
name_ranges,
|
name_ranges,
|
||||||
})
|
body_range: open_index.zip(close_index).map(|(start, end)| start..end),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Some(items)
|
|
||||||
|
items.sort_by_key(|item| (item.range.start, Reverse(item.range.end)));
|
||||||
|
|
||||||
|
// Assign depths based on containment relationships and convert to anchors.
|
||||||
|
let mut item_ends_stack = Vec::<usize>::new();
|
||||||
|
let mut anchor_items = Vec::new();
|
||||||
|
for item in items {
|
||||||
|
while let Some(last_end) = item_ends_stack.last().copied() {
|
||||||
|
if last_end < item.range.end {
|
||||||
|
item_ends_stack.pop();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anchor_items.push(OutlineItem {
|
||||||
|
depth: item_ends_stack.len(),
|
||||||
|
range: self.anchor_after(item.range.start)..self.anchor_before(item.range.end),
|
||||||
|
text: item.text,
|
||||||
|
highlight_ranges: item.highlight_ranges,
|
||||||
|
name_ranges: item.name_ranges,
|
||||||
|
body_range: item.body_range.map(|body_range| {
|
||||||
|
self.anchor_after(body_range.start)..self.anchor_before(body_range.end)
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
item_ends_stack.push(item.range.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(anchor_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each grammar in the language, runs the provided
|
/// For each grammar in the language, runs the provided
|
||||||
|
@ -2615,7 +2615,8 @@ fn rust_lang() -> Language {
|
|||||||
"impl" @context
|
"impl" @context
|
||||||
trait: (_)? @name
|
trait: (_)? @name
|
||||||
"for"? @context
|
"for"? @context
|
||||||
type: (_) @name) @item
|
type: (_) @name
|
||||||
|
body: (_ "{" (_)* "}")) @item
|
||||||
(function_item
|
(function_item
|
||||||
"fn" @context
|
"fn" @context
|
||||||
name: (_) @name) @item
|
name: (_) @name) @item
|
||||||
|
@ -867,6 +867,8 @@ pub struct OutlineConfig {
|
|||||||
pub name_capture_ix: u32,
|
pub name_capture_ix: u32,
|
||||||
pub context_capture_ix: Option<u32>,
|
pub context_capture_ix: Option<u32>,
|
||||||
pub extra_context_capture_ix: Option<u32>,
|
pub extra_context_capture_ix: Option<u32>,
|
||||||
|
pub open_capture_ix: Option<u32>,
|
||||||
|
pub close_capture_ix: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -1050,6 +1052,8 @@ impl Language {
|
|||||||
let mut name_capture_ix = None;
|
let mut name_capture_ix = None;
|
||||||
let mut context_capture_ix = None;
|
let mut context_capture_ix = None;
|
||||||
let mut extra_context_capture_ix = None;
|
let mut extra_context_capture_ix = None;
|
||||||
|
let mut open_capture_ix = None;
|
||||||
|
let mut close_capture_ix = None;
|
||||||
get_capture_indices(
|
get_capture_indices(
|
||||||
&query,
|
&query,
|
||||||
&mut [
|
&mut [
|
||||||
@ -1057,6 +1061,8 @@ impl Language {
|
|||||||
("name", &mut name_capture_ix),
|
("name", &mut name_capture_ix),
|
||||||
("context", &mut context_capture_ix),
|
("context", &mut context_capture_ix),
|
||||||
("context.extra", &mut extra_context_capture_ix),
|
("context.extra", &mut extra_context_capture_ix),
|
||||||
|
("open", &mut open_capture_ix),
|
||||||
|
("close", &mut close_capture_ix),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) {
|
if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) {
|
||||||
@ -1066,6 +1072,8 @@ impl Language {
|
|||||||
name_capture_ix,
|
name_capture_ix,
|
||||||
context_capture_ix,
|
context_capture_ix,
|
||||||
extra_context_capture_ix,
|
extra_context_capture_ix,
|
||||||
|
open_capture_ix,
|
||||||
|
close_capture_ix,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(self)
|
Ok(self)
|
||||||
|
@ -23,6 +23,7 @@ pub struct OutlineItem<T> {
|
|||||||
pub text: String,
|
pub text: String,
|
||||||
pub highlight_ranges: Vec<(Range<usize>, HighlightStyle)>,
|
pub highlight_ranges: Vec<(Range<usize>, HighlightStyle)>,
|
||||||
pub name_ranges: Vec<Range<usize>>,
|
pub name_ranges: Vec<Range<usize>>,
|
||||||
|
pub body_range: Option<Range<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Outline<T> {
|
impl<T> Outline<T> {
|
||||||
|
@ -115,6 +115,7 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
LspLogView::new(workspace.project().clone(), log_store.clone(), cx)
|
LspLogView::new(workspace.project().clone(), log_store.clone(), cx)
|
||||||
})),
|
})),
|
||||||
None,
|
None,
|
||||||
|
true,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
"impl" @context
|
"impl" @context
|
||||||
trait: (_)? @name
|
trait: (_)? @name
|
||||||
"for"? @context
|
"for"? @context
|
||||||
type: (_) @name) @item
|
type: (_) @name
|
||||||
|
body: (_ "{" @open (_)* "}" @close)) @item
|
||||||
|
|
||||||
(trait_item
|
(trait_item
|
||||||
(visibility_modifier)? @context
|
(visibility_modifier)? @context
|
||||||
|
@ -3639,6 +3639,12 @@ impl MultiBufferSnapshot {
|
|||||||
text: item.text,
|
text: item.text,
|
||||||
highlight_ranges: item.highlight_ranges,
|
highlight_ranges: item.highlight_ranges,
|
||||||
name_ranges: item.name_ranges,
|
name_ranges: item.name_ranges,
|
||||||
|
body_range: item.body_range.and_then(|body_range| {
|
||||||
|
Some(
|
||||||
|
self.anchor_in_excerpt(*excerpt_id, body_range.start)?
|
||||||
|
..self.anchor_in_excerpt(*excerpt_id, body_range.end)?,
|
||||||
|
)
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
@ -3668,6 +3674,12 @@ impl MultiBufferSnapshot {
|
|||||||
text: item.text,
|
text: item.text,
|
||||||
highlight_ranges: item.highlight_ranges,
|
highlight_ranges: item.highlight_ranges,
|
||||||
name_ranges: item.name_ranges,
|
name_ranges: item.name_ranges,
|
||||||
|
body_range: item.body_range.and_then(|body_range| {
|
||||||
|
Some(
|
||||||
|
self.anchor_in_excerpt(excerpt_id, body_range.start)?
|
||||||
|
..self.anchor_in_excerpt(excerpt_id, body_range.end)?,
|
||||||
|
)
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -8403,6 +8403,37 @@ impl Project {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to find a `ProjectPath` corresponding to the given full path.
|
||||||
|
///
|
||||||
|
/// This method iterates through all worktrees in the project, trying to match
|
||||||
|
/// the given full path against each worktree's root name. If a match is found,
|
||||||
|
/// it returns a `ProjectPath` containing the worktree ID and the relative path
|
||||||
|
/// within that worktree.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `full_path` - A reference to a `Path` representing the full path to resolve.
|
||||||
|
/// * `cx` - A reference to the `AppContext`.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns `Some(ProjectPath)` if a matching worktree is found, otherwise `None`.
|
||||||
|
pub fn project_path_for_full_path(
|
||||||
|
&self,
|
||||||
|
full_path: &Path,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> Option<ProjectPath> {
|
||||||
|
self.worktrees.iter().find_map(|worktree| {
|
||||||
|
let worktree = worktree.upgrade()?;
|
||||||
|
let worktree_root_name = worktree.read(cx).root_name();
|
||||||
|
let relative_path = full_path.strip_prefix(worktree_root_name).ok()?;
|
||||||
|
Some(ProjectPath {
|
||||||
|
worktree_id: worktree.read(cx).id(),
|
||||||
|
path: relative_path.into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_workspace_root(
|
pub fn get_workspace_root(
|
||||||
&self,
|
&self,
|
||||||
project_path: &ProjectPath,
|
project_path: &ProjectPath,
|
||||||
|
@ -131,7 +131,8 @@ impl PickerDelegate for ProjectSymbolsDelegate {
|
|||||||
workspace.active_pane().clone()
|
workspace.active_pane().clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let editor = workspace.open_project_item::<Editor>(pane, buffer, cx);
|
let editor =
|
||||||
|
workspace.open_project_item::<Editor>(pane, buffer, true, true, cx);
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.change_selections(Some(Autoscroll::center()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::center()), cx, |s| {
|
||||||
|
@ -716,7 +716,7 @@ impl ProjectSearchView {
|
|||||||
|
|
||||||
let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
||||||
let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, None));
|
let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, None));
|
||||||
workspace.add_item_to_active_pane(Box::new(search.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(search.clone()), None, true, cx);
|
||||||
search.update(cx, |search, cx| {
|
search.update(cx, |search, cx| {
|
||||||
search
|
search
|
||||||
.included_files_editor
|
.included_files_editor
|
||||||
@ -768,6 +768,7 @@ impl ProjectSearchView {
|
|||||||
workspace.add_item_to_active_pane(
|
workspace.add_item_to_active_pane(
|
||||||
Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))),
|
Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))),
|
||||||
None,
|
None,
|
||||||
|
true,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -800,7 +801,7 @@ impl ProjectSearchView {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let search = if let Some(existing) = existing {
|
let search = if let Some(existing) = existing {
|
||||||
workspace.activate_item(&existing, cx);
|
workspace.activate_item(&existing, true, true, cx);
|
||||||
existing
|
existing
|
||||||
} else {
|
} else {
|
||||||
let settings = cx
|
let settings = cx
|
||||||
@ -817,7 +818,7 @@ impl ProjectSearchView {
|
|||||||
let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
||||||
let view = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings));
|
let view = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings));
|
||||||
|
|
||||||
workspace.add_item_to_active_pane(Box::new(view.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(view.clone()), None, true, cx);
|
||||||
view
|
view
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -340,7 +340,7 @@ mod tests {
|
|||||||
workspace
|
workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
// Now, let's switch the active item to .ts file.
|
// Now, let's switch the active item to .ts file.
|
||||||
workspace.activate_item(&editor1, cx);
|
workspace.activate_item(&editor1, true, true, cx);
|
||||||
task_context(workspace, cx)
|
task_context(workspace, cx)
|
||||||
})
|
})
|
||||||
.await,
|
.await,
|
||||||
|
@ -150,7 +150,7 @@ impl TerminalView {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
workspace.add_item_to_active_pane(Box::new(view), None, cx)
|
workspace.add_item_to_active_pane(Box::new(view), None, true, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +136,7 @@ where
|
|||||||
|
|
||||||
pub trait AnchorRangeExt {
|
pub trait AnchorRangeExt {
|
||||||
fn cmp(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> Ordering;
|
fn cmp(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> Ordering;
|
||||||
|
fn intersects(&self, other: &Range<Anchor>, buffer: &BufferSnapshot) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnchorRangeExt for Range<Anchor> {
|
impl AnchorRangeExt for Range<Anchor> {
|
||||||
@ -145,4 +146,8 @@ impl AnchorRangeExt for Range<Anchor> {
|
|||||||
ord => ord,
|
ord => ord,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn intersects(&self, other: &Range<Anchor>, buffer: &BufferSnapshot) -> bool {
|
||||||
|
self.start.cmp(&other.end, buffer).is_lt() && other.start.cmp(&self.end, buffer).is_lt()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||||
workspace.register_action(|workspace, _: &Welcome, cx| {
|
workspace.register_action(|workspace, _: &Welcome, cx| {
|
||||||
let welcome_page = WelcomePage::new(workspace, cx);
|
let welcome_page = WelcomePage::new(workspace, cx);
|
||||||
workspace.add_item_to_active_pane(Box::new(welcome_page), None, cx)
|
workspace.add_item_to_active_pane(Box::new(welcome_page), None, true, cx)
|
||||||
});
|
});
|
||||||
workspace
|
workspace
|
||||||
.register_action(|_workspace, _: &ResetHints, cx| MultibufferHint::set_count(0, cx));
|
.register_action(|_workspace, _: &ResetHints, cx| MultibufferHint::set_count(0, cx));
|
||||||
|
@ -2380,9 +2380,17 @@ impl Workspace {
|
|||||||
&mut self,
|
&mut self,
|
||||||
item: Box<dyn ItemHandle>,
|
item: Box<dyn ItemHandle>,
|
||||||
destination_index: Option<usize>,
|
destination_index: Option<usize>,
|
||||||
|
focus_item: bool,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
self.add_item(self.active_pane.clone(), item, destination_index, cx)
|
self.add_item(
|
||||||
|
self.active_pane.clone(),
|
||||||
|
item,
|
||||||
|
destination_index,
|
||||||
|
false,
|
||||||
|
focus_item,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_item(
|
pub fn add_item(
|
||||||
@ -2390,6 +2398,8 @@ impl Workspace {
|
|||||||
pane: View<Pane>,
|
pane: View<Pane>,
|
||||||
item: Box<dyn ItemHandle>,
|
item: Box<dyn ItemHandle>,
|
||||||
destination_index: Option<usize>,
|
destination_index: Option<usize>,
|
||||||
|
activate_pane: bool,
|
||||||
|
focus_item: bool,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
if let Some(text) = item.telemetry_event_text(cx) {
|
if let Some(text) = item.telemetry_event_text(cx) {
|
||||||
@ -2399,7 +2409,7 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
pane.add_item(item, true, true, destination_index, cx)
|
pane.add_item(item, activate_pane, focus_item, destination_index, cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2410,7 +2420,7 @@ impl Workspace {
|
|||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
|
let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
|
||||||
self.add_item(new_pane, item, None, cx);
|
self.add_item(new_pane, item, None, true, true, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_abs_path(
|
pub fn open_abs_path(
|
||||||
@ -2565,6 +2575,8 @@ impl Workspace {
|
|||||||
&mut self,
|
&mut self,
|
||||||
pane: View<Pane>,
|
pane: View<Pane>,
|
||||||
project_item: Model<T::Item>,
|
project_item: Model<T::Item>,
|
||||||
|
activate_pane: bool,
|
||||||
|
focus_item: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> View<T>
|
) -> View<T>
|
||||||
where
|
where
|
||||||
@ -2577,7 +2589,7 @@ impl Workspace {
|
|||||||
.and_then(|entry_id| pane.read(cx).item_for_entry(entry_id, cx))
|
.and_then(|entry_id| pane.read(cx).item_for_entry(entry_id, cx))
|
||||||
.and_then(|item| item.downcast())
|
.and_then(|item| item.downcast())
|
||||||
{
|
{
|
||||||
self.activate_item(&item, cx);
|
self.activate_item(&item, activate_pane, focus_item, cx);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2596,7 +2608,14 @@ impl Workspace {
|
|||||||
pane.set_preview_item_id(Some(item.item_id()), cx)
|
pane.set_preview_item_id(Some(item.item_id()), cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
self.add_item(pane, Box::new(item.clone()), destination_index, cx);
|
self.add_item(
|
||||||
|
pane,
|
||||||
|
Box::new(item.clone()),
|
||||||
|
destination_index,
|
||||||
|
activate_pane,
|
||||||
|
focus_item,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2608,14 +2627,22 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
|
pub fn activate_item(
|
||||||
|
&mut self,
|
||||||
|
item: &dyn ItemHandle,
|
||||||
|
activate_pane: bool,
|
||||||
|
focus_item: bool,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> bool {
|
||||||
let result = self.panes.iter().find_map(|pane| {
|
let result = self.panes.iter().find_map(|pane| {
|
||||||
pane.read(cx)
|
pane.read(cx)
|
||||||
.index_for_item(item)
|
.index_for_item(item)
|
||||||
.map(|ix| (pane.clone(), ix))
|
.map(|ix| (pane.clone(), ix))
|
||||||
});
|
});
|
||||||
if let Some((pane, ix)) = result {
|
if let Some((pane, ix)) = result {
|
||||||
pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
|
pane.update(cx, |pane, cx| {
|
||||||
|
pane.activate_item(ix, activate_pane, focus_item, cx)
|
||||||
|
});
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -5568,7 +5595,7 @@ mod tests {
|
|||||||
item
|
item
|
||||||
});
|
});
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
|
||||||
});
|
});
|
||||||
item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
|
item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
|
||||||
|
|
||||||
@ -5580,7 +5607,7 @@ mod tests {
|
|||||||
item
|
item
|
||||||
});
|
});
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
|
||||||
});
|
});
|
||||||
item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
|
item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
|
||||||
item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
|
item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
|
||||||
@ -5594,7 +5621,7 @@ mod tests {
|
|||||||
item
|
item
|
||||||
});
|
});
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
|
||||||
});
|
});
|
||||||
item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
|
item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
|
||||||
item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
|
item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
|
||||||
@ -5638,7 +5665,7 @@ mod tests {
|
|||||||
|
|
||||||
// Add an item to an empty pane
|
// Add an item to an empty pane
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(item1), None, cx)
|
workspace.add_item_to_active_pane(Box::new(item1), None, true, cx)
|
||||||
});
|
});
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -5652,7 +5679,7 @@ mod tests {
|
|||||||
|
|
||||||
// Add a second item to a non-empty pane
|
// Add a second item to a non-empty pane
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(item2), None, cx)
|
workspace.add_item_to_active_pane(Box::new(item2), None, true, cx)
|
||||||
});
|
});
|
||||||
assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
|
assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
@ -5707,7 +5734,7 @@ mod tests {
|
|||||||
// When there are no dirty items, there's nothing to do.
|
// When there are no dirty items, there's nothing to do.
|
||||||
let item1 = cx.new_view(|cx| TestItem::new(cx));
|
let item1 = cx.new_view(|cx| TestItem::new(cx));
|
||||||
workspace.update(cx, |w, cx| {
|
workspace.update(cx, |w, cx| {
|
||||||
w.add_item_to_active_pane(Box::new(item1.clone()), None, cx)
|
w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx)
|
||||||
});
|
});
|
||||||
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
|
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
|
||||||
assert!(task.await.unwrap());
|
assert!(task.await.unwrap());
|
||||||
@ -5721,8 +5748,8 @@ mod tests {
|
|||||||
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
||||||
});
|
});
|
||||||
workspace.update(cx, |w, cx| {
|
workspace.update(cx, |w, cx| {
|
||||||
w.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
|
w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
|
||||||
w.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
|
w.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
|
||||||
});
|
});
|
||||||
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
|
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
@ -5762,8 +5789,8 @@ mod tests {
|
|||||||
.with_serialize(|| Some(Task::ready(Ok(()))))
|
.with_serialize(|| Some(Task::ready(Ok(()))))
|
||||||
});
|
});
|
||||||
workspace.update(cx, |w, cx| {
|
workspace.update(cx, |w, cx| {
|
||||||
w.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
|
w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
|
||||||
w.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
|
w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
|
||||||
});
|
});
|
||||||
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
|
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
|
||||||
assert!(task.await.unwrap());
|
assert!(task.await.unwrap());
|
||||||
@ -5801,10 +5828,10 @@ mod tests {
|
|||||||
.with_project_items(&[TestProjectItem::new_untitled(cx)])
|
.with_project_items(&[TestProjectItem::new_untitled(cx)])
|
||||||
});
|
});
|
||||||
let pane = workspace.update(cx, |workspace, cx| {
|
let pane = workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
|
||||||
workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
|
||||||
workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
|
||||||
workspace.add_item_to_active_pane(Box::new(item4.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, cx);
|
||||||
workspace.active_pane().clone()
|
workspace.active_pane().clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -5926,9 +5953,9 @@ mod tests {
|
|||||||
// multi-entry items: (3, 4)
|
// multi-entry items: (3, 4)
|
||||||
let left_pane = workspace.update(cx, |workspace, cx| {
|
let left_pane = workspace.update(cx, |workspace, cx| {
|
||||||
let left_pane = workspace.active_pane().clone();
|
let left_pane = workspace.active_pane().clone();
|
||||||
workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, cx);
|
||||||
for item in single_entry_items {
|
for item in single_entry_items {
|
||||||
workspace.add_item_to_active_pane(Box::new(item), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item), None, true, cx);
|
||||||
}
|
}
|
||||||
left_pane.update(cx, |pane, cx| {
|
left_pane.update(cx, |pane, cx| {
|
||||||
pane.activate_item(2, true, true, cx);
|
pane.activate_item(2, true, true, cx);
|
||||||
@ -5999,7 +6026,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
let item_id = item.entity_id();
|
let item_id = item.entity_id();
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Autosave on window change.
|
// Autosave on window change.
|
||||||
@ -6084,7 +6111,7 @@ mod tests {
|
|||||||
|
|
||||||
// Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
|
// Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
|
||||||
});
|
});
|
||||||
item.update(cx, |item, cx| {
|
item.update(cx, |item, cx| {
|
||||||
item.project_items[0].update(cx, |item, _| {
|
item.project_items[0].update(cx, |item, _| {
|
||||||
@ -6122,7 +6149,7 @@ mod tests {
|
|||||||
let toolbar_notify_count = Rc::new(RefCell::new(0));
|
let toolbar_notify_count = Rc::new(RefCell::new(0));
|
||||||
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
|
workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
|
||||||
let toolbar_notification_count = toolbar_notify_count.clone();
|
let toolbar_notification_count = toolbar_notify_count.clone();
|
||||||
cx.observe(&toolbar, move |_, _, _| {
|
cx.observe(&toolbar, move |_, _, _| {
|
||||||
*toolbar_notification_count.borrow_mut() += 1
|
*toolbar_notification_count.borrow_mut() += 1
|
||||||
|
@ -670,7 +670,7 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
workspace.add_item_to_active_pane(Box::new(editor), None, cx);
|
workspace.add_item_to_active_pane(Box::new(editor), None, true, cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
})
|
})
|
||||||
@ -889,7 +889,9 @@ fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Works
|
|||||||
});
|
});
|
||||||
workspace.add_item_to_active_pane(
|
workspace.add_item_to_active_pane(
|
||||||
Box::new(cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), true, cx))),
|
Box::new(cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), true, cx))),
|
||||||
None,cx,
|
None,
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
);
|
);
|
||||||
}).log_err()?;
|
}).log_err()?;
|
||||||
|
|
||||||
@ -924,6 +926,7 @@ fn open_bundled_file(
|
|||||||
Editor::for_multibuffer(buffer, Some(project.clone()), true, cx)
|
Editor::for_multibuffer(buffer, Some(project.clone()), true, cx)
|
||||||
})),
|
})),
|
||||||
None,
|
None,
|
||||||
|
true,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user