Compare commits
24 Commits
v0.148.0
...
docs_highl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e3e1647e1 | ||
|
|
a39f1f5133 | ||
|
|
fbc629df7d | ||
|
|
8ba5207c6c | ||
|
|
c3cfaade7d | ||
|
|
8681eeb0e2 | ||
|
|
8f9bcbe739 | ||
|
|
9a211b239c | ||
|
|
a71bfd41cc | ||
|
|
73fb8277fc | ||
|
|
514b79e461 | ||
|
|
793cd88792 | ||
|
|
92496f33e7 | ||
|
|
b9159d98ea | ||
|
|
f3abb7e724 | ||
|
|
389cb86e43 | ||
|
|
fea8f16df0 | ||
|
|
76d58ac295 | ||
|
|
e69b0833aa | ||
|
|
da8d1306af | ||
|
|
a5961c8d45 | ||
|
|
e9ddca1075 | ||
|
|
6f6eeb6595 | ||
|
|
22162e884b |
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@@ -1,15 +1,13 @@
|
||||
Closes #ISSUE
|
||||
|
||||
|
||||
Release Notes:
|
||||
|
||||
- Added/Fixed/Improved ...
|
||||
- Added/Fixed/Improved ... ([#NNNNN](https://github.com/zed-industries/zed/issues/NNNNN)).
|
||||
|
||||
Optionally, include screenshots / media showcasing your addition that can be included in the release notes.
|
||||
|
||||
### Or...
|
||||
|
||||
Closes #ISSUE
|
||||
|
||||
Release Notes:
|
||||
|
||||
- N/A
|
||||
|
||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -231,20 +231,20 @@ jobs:
|
||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- name: Upload app bundle (universal) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
|
||||
path: target/release/Zed.dmg
|
||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
|
||||
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
@@ -319,7 +319,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
@@ -403,7 +403,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
|
||||
31
Cargo.lock
generated
31
Cargo.lock
generated
@@ -405,7 +405,7 @@ dependencies = [
|
||||
"multi_buffer",
|
||||
"ollama",
|
||||
"open_ai",
|
||||
"ordered-float 2.10.0",
|
||||
"ordered-float 2.10.1",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"picker",
|
||||
@@ -2504,6 +2504,7 @@ dependencies = [
|
||||
"settings",
|
||||
"sha2",
|
||||
"sqlx",
|
||||
"strum",
|
||||
"subtle",
|
||||
"supermaven_api",
|
||||
"telemetry_events",
|
||||
@@ -3557,7 +3558,7 @@ dependencies = [
|
||||
"lsp",
|
||||
"markdown",
|
||||
"multi_buffer",
|
||||
"ordered-float 2.10.0",
|
||||
"ordered-float 2.10.1",
|
||||
"parking_lot",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
@@ -5320,9 +5321,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.27"
|
||||
version = "0.14.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
|
||||
checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
|
||||
dependencies = [
|
||||
"bytes 1.5.0",
|
||||
"futures-channel",
|
||||
@@ -5335,7 +5336,7 @@ dependencies = [
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.4.9",
|
||||
"socket2 0.5.7",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -7321,9 +7322,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.10.0"
|
||||
version = "2.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
|
||||
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
@@ -7381,7 +7382,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"language",
|
||||
"menu",
|
||||
"ordered-float 2.10.0",
|
||||
"ordered-float 2.10.1",
|
||||
"picker",
|
||||
"project",
|
||||
"rope",
|
||||
@@ -7498,9 +7499,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
@@ -8058,9 +8059,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -8183,7 +8184,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"language",
|
||||
"lsp",
|
||||
"ordered-float 2.10.0",
|
||||
"ordered-float 2.10.1",
|
||||
"picker",
|
||||
"project",
|
||||
"release_channel",
|
||||
@@ -8592,7 +8593,7 @@ dependencies = [
|
||||
"log",
|
||||
"markdown",
|
||||
"menu",
|
||||
"ordered-float 2.10.0",
|
||||
"ordered-float 2.10.1",
|
||||
"picker",
|
||||
"project",
|
||||
"release_channel",
|
||||
@@ -13775,7 +13776,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.148.0"
|
||||
version = "0.149.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
1
assets/icons/undo.svg
Normal file
1
assets/icons/undo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/></svg>
|
||||
|
After Width: | Height: | Size: 288 B |
@@ -1,4 +1,4 @@
|
||||
Your task is to map a step from the conversation above to operations on symbols inside the provided source files.
|
||||
Your task is to map a step from the conversation above to suggestions on symbols inside the provided source files.
|
||||
|
||||
Guidelines:
|
||||
- There's no need to describe *what* to do, just *where* to do it.
|
||||
@@ -6,13 +6,13 @@ Guidelines:
|
||||
- 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.
|
||||
- Never include suggestions on a parent symbol and one of its children in the same suggestions block.
|
||||
- Never nest an operation with another operation or include CDATA or other content. All suggestions 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.
|
||||
- Descriptions are required for all suggestions except delete.
|
||||
- When generating multiple suggestions, 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.
|
||||
- Don't generate multiple suggestions at the same location. Instead, combine them together in a single operation with a succinct combined description.
|
||||
|
||||
Example 1:
|
||||
|
||||
@@ -33,12 +33,12 @@ impl Rectangle {
|
||||
<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>
|
||||
What are the suggestions for the step: <step>Add a new method 'calculate_area' to the Rectangle struct</step>
|
||||
|
||||
A (wrong):
|
||||
{
|
||||
"title": "Add Rectangle methods",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "AppendChild",
|
||||
"path": "src/shapes.rs",
|
||||
@@ -59,7 +59,7 @@ This demonstrates what NOT to do. NEVER append multiple children at the same loc
|
||||
A (corrected):
|
||||
{
|
||||
"title": "Add Rectangle methods",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "AppendChild",
|
||||
"path": "src/shapes.rs",
|
||||
@@ -70,12 +70,12 @@ A (corrected):
|
||||
}
|
||||
|
||||
User:
|
||||
What are the operations for the step: <step>Implement the 'Display' trait for the Rectangle struct</step>
|
||||
What are the suggestions for the step: <step>Implement the 'Display' trait for the Rectangle struct</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Implement Display for Rectangle",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "InsertSiblingAfter",
|
||||
"path": "src/shapes.rs",
|
||||
@@ -109,12 +109,12 @@ impl User {
|
||||
<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>
|
||||
What are the suggestions for the step: <step>Update the 'print_info' method to use formatted output</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Use formatted output",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Update",
|
||||
"path": "src/user.rs",
|
||||
@@ -125,12 +125,12 @@ A:
|
||||
}
|
||||
|
||||
User:
|
||||
What are the operations for the step: <step>Remove the 'email' field from the User struct</step>
|
||||
What are the suggestions for the step: <step>Remove the 'email' field from the User struct</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Remove email field",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Delete",
|
||||
"path": "src/user.rs",
|
||||
@@ -163,12 +163,12 @@ impl Vehicle {
|
||||
<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>
|
||||
What are the suggestions for the step: <step>Add a 'use std::fmt;' statement at the beginning of the file</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Add use std::fmt statement",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "PrependChild",
|
||||
"path": "src/vehicle.rs",
|
||||
@@ -178,12 +178,12 @@ A:
|
||||
}
|
||||
|
||||
User:
|
||||
What are the operations for the step: <step>Add a new method 'start_engine' in the Vehicle impl block</step>
|
||||
What are the suggestions for the step: <step>Add a new method 'start_engine' in the Vehicle impl block</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Add start_engine method",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "InsertSiblingAfter",
|
||||
"path": "src/vehicle.rs",
|
||||
@@ -222,12 +222,12 @@ impl Employee {
|
||||
|
||||
<step>Make salary an f32</step>
|
||||
|
||||
What are the operations for the step: <step>Make salary an f32</step>
|
||||
What are the suggestions for the step: <step>Make salary an f32</step>
|
||||
|
||||
A (wrong):
|
||||
{
|
||||
"title": "Change salary to f32",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Update",
|
||||
"path": "src/employee.rs",
|
||||
@@ -248,7 +248,7 @@ This example demonstrates what not to do. `struct Employee salary` is a child of
|
||||
A (corrected):
|
||||
{
|
||||
"title": "Change salary to f32",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Update",
|
||||
"path": "src/employee.rs",
|
||||
@@ -259,12 +259,12 @@ A (corrected):
|
||||
}
|
||||
|
||||
User:
|
||||
What are the correct operations for the step: <step>Remove the 'department' field and update the 'print_details' method</step>
|
||||
What are the correct suggestions for the step: <step>Remove the 'department' field and update the 'print_details' method</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Remove department",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Delete",
|
||||
"path": "src/employee.rs",
|
||||
@@ -311,7 +311,7 @@ impl Game {
|
||||
A:
|
||||
{
|
||||
"title": "Add level field to Player",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "InsertSiblingAfter",
|
||||
"path": "src/game.rs",
|
||||
@@ -349,7 +349,7 @@ impl Config {
|
||||
A:
|
||||
{
|
||||
"title": "Add load_from_file method",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "PrependChild",
|
||||
"path": "src/config.rs",
|
||||
@@ -389,7 +389,7 @@ impl Database {
|
||||
A:
|
||||
{
|
||||
"title": "Add error handling to query",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "PrependChild",
|
||||
"path": "src/database.rs",
|
||||
@@ -410,4 +410,4 @@ A:
|
||||
]
|
||||
}
|
||||
|
||||
Now generate the operations for the following step:
|
||||
Now generate the suggestions for the following step:
|
||||
|
||||
@@ -16,13 +16,13 @@ pub const ANTHROPIC_API_URL: &'static str = "https://api.anthropic.com";
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||
pub enum Model {
|
||||
#[default]
|
||||
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3-5-sonnet-20240620")]
|
||||
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-20240620")]
|
||||
Claude3_5Sonnet,
|
||||
#[serde(alias = "claude-3-opus", rename = "claude-3-opus-20240229")]
|
||||
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-20240229")]
|
||||
Claude3Opus,
|
||||
#[serde(alias = "claude-3-sonnet", rename = "claude-3-sonnet-20240229")]
|
||||
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-20240229")]
|
||||
Claude3Sonnet,
|
||||
#[serde(alias = "claude-3-haiku", rename = "claude-3-haiku-20240307")]
|
||||
#[serde(rename = "claude-3-haiku", alias = "claude-3-haiku-20240307")]
|
||||
Claude3Haiku,
|
||||
#[serde(rename = "custom")]
|
||||
Custom {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(target_os = "windows", allow(unused, dead_code))]
|
||||
|
||||
pub mod assistant_panel;
|
||||
pub mod assistant_settings;
|
||||
mod context;
|
||||
@@ -17,6 +19,7 @@ use client::{proto, Client};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
pub use context::*;
|
||||
pub use context_store::*;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::{actions, impl_actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
@@ -53,7 +56,7 @@ actions!(
|
||||
DeployPromptLibrary,
|
||||
ConfirmCommand,
|
||||
ToggleModelSelector,
|
||||
DebugEditSteps
|
||||
DebugWorkflowSteps
|
||||
]
|
||||
);
|
||||
|
||||
@@ -272,7 +275,6 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
||||
slash_command_registry.register_command(symbols_command::OutlineSlashCommand, true);
|
||||
slash_command_registry.register_command(tabs_command::TabsSlashCommand, true);
|
||||
slash_command_registry.register_command(project_command::ProjectSlashCommand, true);
|
||||
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
|
||||
slash_command_registry.register_command(term_command::TermSlashCommand, true);
|
||||
@@ -286,6 +288,13 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
||||
);
|
||||
}
|
||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||
|
||||
cx.observe_flag::<search_command::SearchSlashCommandFeatureFlag, _>(move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn humanize_token_count(count: usize) -> String {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -284,7 +284,8 @@ pub enum ContextEvent {
|
||||
AssistError(String),
|
||||
MessagesEdited,
|
||||
SummaryChanged,
|
||||
WorkflowStepsChanged,
|
||||
WorkflowStepsRemoved(Vec<Range<language::Anchor>>),
|
||||
WorkflowStepUpdated(Range<language::Anchor>),
|
||||
StreamedCompletion,
|
||||
PendingSlashCommandsUpdated {
|
||||
removed: Vec<Range<language::Anchor>>,
|
||||
@@ -348,37 +349,39 @@ pub struct SlashCommandId(clock::Lamport);
|
||||
#[derive(Debug)]
|
||||
pub struct WorkflowStep {
|
||||
pub tagged_range: Range<language::Anchor>,
|
||||
pub edit_suggestions: WorkflowStepEditSuggestions,
|
||||
pub status: WorkflowStepStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ResolvedWorkflowStepEditSuggestions {
|
||||
pub struct ResolvedWorkflowStep {
|
||||
pub title: String,
|
||||
pub edit_suggestions: HashMap<Model<Buffer>, Vec<EditSuggestionGroup>>,
|
||||
pub suggestions: HashMap<Model<Buffer>, Vec<WorkflowSuggestionGroup>>,
|
||||
}
|
||||
|
||||
pub enum WorkflowStepEditSuggestions {
|
||||
pub enum WorkflowStepStatus {
|
||||
Pending(Task<Option<()>>),
|
||||
Resolved(ResolvedWorkflowStepEditSuggestions),
|
||||
Resolved(ResolvedWorkflowStep),
|
||||
Error(Arc<anyhow::Error>),
|
||||
}
|
||||
|
||||
impl WorkflowStepEditSuggestions {
|
||||
pub fn as_resolved(&self) -> Option<&ResolvedWorkflowStepEditSuggestions> {
|
||||
impl WorkflowStepStatus {
|
||||
pub fn into_resolved(&self) -> Option<Result<ResolvedWorkflowStep, Arc<anyhow::Error>>> {
|
||||
match self {
|
||||
WorkflowStepEditSuggestions::Resolved(suggestions) => Some(suggestions),
|
||||
WorkflowStepEditSuggestions::Pending(_) => None,
|
||||
WorkflowStepStatus::Resolved(resolved) => Some(Ok(resolved.clone())),
|
||||
WorkflowStepStatus::Error(error) => Some(Err(error.clone())),
|
||||
WorkflowStepStatus::Pending(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct EditSuggestionGroup {
|
||||
pub struct WorkflowSuggestionGroup {
|
||||
pub context_range: Range<language::Anchor>,
|
||||
pub suggestions: Vec<EditSuggestion>,
|
||||
pub suggestions: Vec<WorkflowSuggestion>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum EditSuggestion {
|
||||
pub enum WorkflowSuggestion {
|
||||
Update {
|
||||
range: Range<language::Anchor>,
|
||||
description: String,
|
||||
@@ -407,40 +410,40 @@ pub enum EditSuggestion {
|
||||
},
|
||||
}
|
||||
|
||||
impl EditSuggestion {
|
||||
impl WorkflowSuggestion {
|
||||
pub fn range(&self) -> Range<language::Anchor> {
|
||||
match self {
|
||||
EditSuggestion::Update { range, .. } => range.clone(),
|
||||
EditSuggestion::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX,
|
||||
EditSuggestion::InsertSiblingBefore { position, .. }
|
||||
| EditSuggestion::InsertSiblingAfter { position, .. }
|
||||
| EditSuggestion::PrependChild { position, .. }
|
||||
| EditSuggestion::AppendChild { position, .. } => *position..*position,
|
||||
EditSuggestion::Delete { range } => range.clone(),
|
||||
WorkflowSuggestion::Update { range, .. } => range.clone(),
|
||||
WorkflowSuggestion::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX,
|
||||
WorkflowSuggestion::InsertSiblingBefore { position, .. }
|
||||
| WorkflowSuggestion::InsertSiblingAfter { position, .. }
|
||||
| WorkflowSuggestion::PrependChild { position, .. }
|
||||
| WorkflowSuggestion::AppendChild { position, .. } => *position..*position,
|
||||
WorkflowSuggestion::Delete { range } => range.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description(&self) -> Option<&str> {
|
||||
match self {
|
||||
EditSuggestion::Update { description, .. }
|
||||
| EditSuggestion::CreateFile { description }
|
||||
| EditSuggestion::InsertSiblingBefore { description, .. }
|
||||
| EditSuggestion::InsertSiblingAfter { description, .. }
|
||||
| EditSuggestion::PrependChild { description, .. }
|
||||
| EditSuggestion::AppendChild { description, .. } => Some(description),
|
||||
EditSuggestion::Delete { .. } => None,
|
||||
WorkflowSuggestion::Update { description, .. }
|
||||
| WorkflowSuggestion::CreateFile { description }
|
||||
| WorkflowSuggestion::InsertSiblingBefore { description, .. }
|
||||
| WorkflowSuggestion::InsertSiblingAfter { description, .. }
|
||||
| WorkflowSuggestion::PrependChild { description, .. }
|
||||
| WorkflowSuggestion::AppendChild { description, .. } => Some(description),
|
||||
WorkflowSuggestion::Delete { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn description_mut(&mut self) -> Option<&mut String> {
|
||||
match self {
|
||||
EditSuggestion::Update { description, .. }
|
||||
| EditSuggestion::CreateFile { description }
|
||||
| EditSuggestion::InsertSiblingBefore { description, .. }
|
||||
| EditSuggestion::InsertSiblingAfter { description, .. }
|
||||
| EditSuggestion::PrependChild { description, .. }
|
||||
| EditSuggestion::AppendChild { description, .. } => Some(description),
|
||||
EditSuggestion::Delete { .. } => None,
|
||||
WorkflowSuggestion::Update { description, .. }
|
||||
| WorkflowSuggestion::CreateFile { description }
|
||||
| WorkflowSuggestion::InsertSiblingBefore { description, .. }
|
||||
| WorkflowSuggestion::InsertSiblingAfter { description, .. }
|
||||
| WorkflowSuggestion::PrependChild { description, .. }
|
||||
| WorkflowSuggestion::AppendChild { description, .. } => Some(description),
|
||||
WorkflowSuggestion::Delete { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,16 +482,16 @@ impl EditSuggestion {
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
|
||||
match self {
|
||||
EditSuggestion::Update { range, description } => {
|
||||
WorkflowSuggestion::Update { range, description } => {
|
||||
initial_prompt = description.clone();
|
||||
suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
|
||||
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
|
||||
}
|
||||
EditSuggestion::CreateFile { description } => {
|
||||
WorkflowSuggestion::CreateFile { description } => {
|
||||
initial_prompt = description.clone();
|
||||
suggestion_range = editor::Anchor::min()..editor::Anchor::min();
|
||||
}
|
||||
EditSuggestion::InsertSiblingBefore {
|
||||
WorkflowSuggestion::InsertSiblingBefore {
|
||||
position,
|
||||
description,
|
||||
} => {
|
||||
@@ -498,12 +501,13 @@ impl EditSuggestion {
|
||||
buffer.start_transaction(cx);
|
||||
let line_start = buffer.insert_empty_line(position, true, true, cx);
|
||||
initial_transaction_id = buffer.end_transaction(cx);
|
||||
buffer.refresh_preview(cx);
|
||||
|
||||
let line_start = buffer.read(cx).anchor_before(line_start);
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
EditSuggestion::InsertSiblingAfter {
|
||||
WorkflowSuggestion::InsertSiblingAfter {
|
||||
position,
|
||||
description,
|
||||
} => {
|
||||
@@ -513,12 +517,13 @@ impl EditSuggestion {
|
||||
buffer.start_transaction(cx);
|
||||
let line_start = buffer.insert_empty_line(position, true, true, cx);
|
||||
initial_transaction_id = buffer.end_transaction(cx);
|
||||
buffer.refresh_preview(cx);
|
||||
|
||||
let line_start = buffer.read(cx).anchor_before(line_start);
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
EditSuggestion::PrependChild {
|
||||
WorkflowSuggestion::PrependChild {
|
||||
position,
|
||||
description,
|
||||
} => {
|
||||
@@ -528,12 +533,13 @@ impl EditSuggestion {
|
||||
buffer.start_transaction(cx);
|
||||
let line_start = buffer.insert_empty_line(position, false, true, cx);
|
||||
initial_transaction_id = buffer.end_transaction(cx);
|
||||
buffer.refresh_preview(cx);
|
||||
|
||||
let line_start = buffer.read(cx).anchor_before(line_start);
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
EditSuggestion::AppendChild {
|
||||
WorkflowSuggestion::AppendChild {
|
||||
position,
|
||||
description,
|
||||
} => {
|
||||
@@ -543,12 +549,13 @@ impl EditSuggestion {
|
||||
buffer.start_transaction(cx);
|
||||
let line_start = buffer.insert_empty_line(position, true, false, cx);
|
||||
initial_transaction_id = buffer.end_transaction(cx);
|
||||
buffer.refresh_preview(cx);
|
||||
|
||||
let line_start = buffer.read(cx).anchor_before(line_start);
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
EditSuggestion::Delete { range } => {
|
||||
WorkflowSuggestion::Delete { range } => {
|
||||
initial_prompt = "Delete".to_string();
|
||||
suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
|
||||
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
|
||||
@@ -569,17 +576,18 @@ impl EditSuggestion {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for WorkflowStepEditSuggestions {
|
||||
impl Debug for WorkflowStepStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WorkflowStepEditSuggestions::Pending(_) => write!(f, "EditStepOperations::Pending"),
|
||||
WorkflowStepEditSuggestions::Resolved(ResolvedWorkflowStepEditSuggestions {
|
||||
title,
|
||||
edit_suggestions,
|
||||
}) => f
|
||||
.debug_struct("EditStepOperations::Parsed")
|
||||
WorkflowStepStatus::Pending(_) => write!(f, "WorkflowStepStatus::Pending"),
|
||||
WorkflowStepStatus::Resolved(ResolvedWorkflowStep { title, suggestions }) => f
|
||||
.debug_struct("WorkflowStepStatus::Resolved")
|
||||
.field("title", title)
|
||||
.field("edit_suggestions", edit_suggestions)
|
||||
.field("suggestions", suggestions)
|
||||
.finish(),
|
||||
WorkflowStepStatus::Error(error) => f
|
||||
.debug_tuple("WorkflowStepStatus::Error")
|
||||
.field(error)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
@@ -1050,7 +1058,7 @@ impl Context {
|
||||
language::Event::Edited => {
|
||||
self.count_remaining_tokens(cx);
|
||||
self.reparse_slash_commands(cx);
|
||||
self.prune_invalid_edit_steps(cx);
|
||||
self.prune_invalid_workflow_steps(cx);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
}
|
||||
_ => {}
|
||||
@@ -1157,46 +1165,59 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
fn prune_invalid_edit_steps(&mut self, cx: &mut ModelContext<Self>) {
|
||||
fn prune_invalid_workflow_steps(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let prev_len = self.workflow_steps.len();
|
||||
let mut removed = Vec::new();
|
||||
self.workflow_steps.retain(|step| {
|
||||
step.tagged_range.start.is_valid(buffer) && step.tagged_range.end.is_valid(buffer)
|
||||
if step.tagged_range.start.is_valid(buffer) && step.tagged_range.end.is_valid(buffer) {
|
||||
true
|
||||
} else {
|
||||
removed.push(step.tagged_range.clone());
|
||||
false
|
||||
}
|
||||
});
|
||||
if self.workflow_steps.len() != prev_len {
|
||||
cx.emit(ContextEvent::WorkflowStepsChanged);
|
||||
cx.emit(ContextEvent::WorkflowStepsRemoved(removed));
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_edit_steps_in_range(
|
||||
fn parse_workflow_steps_in_range(
|
||||
&mut self,
|
||||
range: Range<usize>,
|
||||
project: Model<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let mut new_edit_steps = Vec::new();
|
||||
let mut edits = Vec::new();
|
||||
|
||||
let buffer = self.buffer.read(cx).snapshot();
|
||||
let mut message_lines = buffer.as_rope().chunks_in_range(range).lines();
|
||||
let mut in_step = false;
|
||||
let mut step_start = 0;
|
||||
let mut step_open_tag_start_ix = 0;
|
||||
let mut line_start_offset = message_lines.offset();
|
||||
|
||||
while let Some(line) = message_lines.next() {
|
||||
if let Some(step_start_index) = line.find("<step>") {
|
||||
if !in_step {
|
||||
in_step = true;
|
||||
step_start = line_start_offset + step_start_index;
|
||||
step_open_tag_start_ix = line_start_offset + step_start_index;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(step_end_index) = line.find("</step>") {
|
||||
if in_step {
|
||||
let start_anchor = buffer.anchor_after(step_start);
|
||||
let end_anchor =
|
||||
buffer.anchor_before(line_start_offset + step_end_index + "</step>".len());
|
||||
let tagged_range = start_anchor..end_anchor;
|
||||
let step_open_tag_end_ix = step_open_tag_start_ix + "<step>".len();
|
||||
let mut step_end_tag_start_ix = line_start_offset + step_end_index;
|
||||
let step_end_tag_end_ix = step_end_tag_start_ix + "</step>".len();
|
||||
if buffer.reversed_chars_at(step_end_tag_start_ix).next() == Some('\n') {
|
||||
step_end_tag_start_ix -= 1;
|
||||
}
|
||||
edits.push((step_open_tag_start_ix..step_open_tag_end_ix, ""));
|
||||
edits.push((step_end_tag_start_ix..step_end_tag_end_ix, ""));
|
||||
let tagged_range = buffer.anchor_after(step_open_tag_end_ix)
|
||||
..buffer.anchor_before(step_end_tag_start_ix);
|
||||
|
||||
// Check if a step with the same range already exists
|
||||
let existing_step_index = self
|
||||
@@ -1204,17 +1225,11 @@ impl Context {
|
||||
.binary_search_by(|probe| probe.tagged_range.cmp(&tagged_range, &buffer));
|
||||
|
||||
if let Err(ix) = existing_step_index {
|
||||
// Step doesn't exist, so add it
|
||||
let task = self.compute_workflow_step_edit_suggestions(
|
||||
tagged_range.clone(),
|
||||
project.clone(),
|
||||
cx,
|
||||
);
|
||||
new_edit_steps.push((
|
||||
ix,
|
||||
WorkflowStep {
|
||||
tagged_range,
|
||||
edit_suggestions: WorkflowStepEditSuggestions::Pending(task),
|
||||
status: WorkflowStepStatus::Pending(Task::ready(None)),
|
||||
},
|
||||
));
|
||||
}
|
||||
@@ -1226,146 +1241,176 @@ impl Context {
|
||||
line_start_offset = message_lines.offset();
|
||||
}
|
||||
|
||||
// Insert new steps and generate their corresponding tasks
|
||||
let mut updated = Vec::new();
|
||||
for (index, step) in new_edit_steps.into_iter().rev() {
|
||||
let step_range = step.tagged_range.clone();
|
||||
updated.push(step_range.clone());
|
||||
self.workflow_steps.insert(index, step);
|
||||
self.resolve_workflow_step(step_range, project.clone(), cx);
|
||||
}
|
||||
|
||||
cx.emit(ContextEvent::WorkflowStepsChanged);
|
||||
cx.notify();
|
||||
self.buffer
|
||||
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
|
||||
}
|
||||
|
||||
fn compute_workflow_step_edit_suggestions(
|
||||
&self,
|
||||
pub fn resolve_workflow_step(
|
||||
&mut self,
|
||||
tagged_range: Range<language::Anchor>,
|
||||
project: Model<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Option<()>> {
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||
return Task::ready(Err(anyhow!("no active model")).log_err());
|
||||
) {
|
||||
let Ok(step_index) = self
|
||||
.workflow_steps
|
||||
.binary_search_by(|step| step.tagged_range.cmp(&tagged_range, self.buffer.read(cx)))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut request = self.to_completion_request(cx);
|
||||
let step_text = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_for_range(tagged_range.clone())
|
||||
.collect::<String>();
|
||||
let Some(edit_step) = self.workflow_steps.get_mut(step_index) else {
|
||||
return;
|
||||
};
|
||||
|
||||
cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
let mut prompt = this.update(&mut cx, |this, _| {
|
||||
this.prompt_builder.generate_step_resolution_prompt()
|
||||
})??;
|
||||
prompt.push_str(&step_text);
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
let step_text = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_for_range(tagged_range.clone())
|
||||
.collect::<String>();
|
||||
|
||||
request.messages.push(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: prompt,
|
||||
});
|
||||
let tagged_range = tagged_range.clone();
|
||||
edit_step.status = WorkflowStepStatus::Pending(cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
let result = async {
|
||||
let mut prompt = this.update(&mut cx, |this, _| {
|
||||
this.prompt_builder.generate_step_resolution_prompt()
|
||||
})??;
|
||||
prompt.push_str(&step_text);
|
||||
|
||||
// Invoke the model to get its edit suggestions for this workflow step.
|
||||
let step_suggestions = model
|
||||
.use_tool::<tool::WorkflowStepEditSuggestions>(request, &cx)
|
||||
.await?;
|
||||
request.messages.push(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: prompt,
|
||||
});
|
||||
|
||||
// Translate the parsed suggestions to our internal types, which anchor the suggestions to locations in the code.
|
||||
let suggestion_tasks: Vec<_> = step_suggestions
|
||||
.edit_suggestions
|
||||
.iter()
|
||||
.map(|suggestion| suggestion.resolve(project.clone(), cx.clone()))
|
||||
.collect();
|
||||
// Invoke the model to get its edit suggestions for this workflow step.
|
||||
let resolution = model
|
||||
.use_tool::<tool::WorkflowStepResolution>(request, &cx)
|
||||
.await?;
|
||||
|
||||
// Expand the context ranges of each suggestion and group suggestions with overlapping context ranges.
|
||||
let suggestions = future::join_all(suggestion_tasks)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|task| task.log_err())
|
||||
.collect::<Vec<_>>();
|
||||
// Translate the parsed suggestions to our internal types, which anchor the suggestions to locations in the code.
|
||||
let suggestion_tasks: Vec<_> = resolution
|
||||
.suggestions
|
||||
.iter()
|
||||
.map(|suggestion| suggestion.resolve(project.clone(), cx.clone()))
|
||||
.collect();
|
||||
|
||||
let mut suggestions_by_buffer = HashMap::default();
|
||||
for (buffer, suggestion) in suggestions {
|
||||
suggestions_by_buffer
|
||||
.entry(buffer)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(suggestion);
|
||||
}
|
||||
// Expand the context ranges of each suggestion and group suggestions with overlapping context ranges.
|
||||
let suggestions = future::join_all(suggestion_tasks)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|task| task.log_err())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut suggestion_groups_by_buffer = HashMap::default();
|
||||
for (buffer, mut suggestions) in suggestions_by_buffer {
|
||||
let mut suggestion_groups = Vec::<EditSuggestionGroup>::new();
|
||||
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
|
||||
// Sort suggestions by their range so that earlier, larger ranges come first
|
||||
suggestions.sort_by(|a, b| a.range().cmp(&b.range(), &snapshot));
|
||||
|
||||
// Merge overlapping suggestions
|
||||
suggestions.dedup_by(|a, b| b.try_merge(&a, &snapshot));
|
||||
|
||||
// Create context ranges for each suggestion
|
||||
for suggestion in suggestions {
|
||||
let context_range = {
|
||||
let suggestion_point_range = suggestion.range().to_point(&snapshot);
|
||||
let start_row = suggestion_point_range.start.row.saturating_sub(5);
|
||||
let end_row = cmp::min(
|
||||
suggestion_point_range.end.row + 5,
|
||||
snapshot.max_point().row,
|
||||
);
|
||||
let start = snapshot.anchor_before(Point::new(start_row, 0));
|
||||
let end = snapshot
|
||||
.anchor_after(Point::new(end_row, snapshot.line_len(end_row)));
|
||||
start..end
|
||||
};
|
||||
|
||||
if let Some(last_group) = suggestion_groups.last_mut() {
|
||||
if last_group
|
||||
.context_range
|
||||
.end
|
||||
.cmp(&context_range.start, &snapshot)
|
||||
.is_ge()
|
||||
{
|
||||
// Merge with the previous group if context ranges overlap
|
||||
last_group.context_range.end = context_range.end;
|
||||
last_group.suggestions.push(suggestion);
|
||||
} else {
|
||||
// Create a new group
|
||||
suggestion_groups.push(EditSuggestionGroup {
|
||||
context_range,
|
||||
suggestions: vec![suggestion],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Create the first group
|
||||
suggestion_groups.push(EditSuggestionGroup {
|
||||
context_range,
|
||||
suggestions: vec![suggestion],
|
||||
});
|
||||
let mut suggestions_by_buffer = HashMap::default();
|
||||
for (buffer, suggestion) in suggestions {
|
||||
suggestions_by_buffer
|
||||
.entry(buffer)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
suggestion_groups_by_buffer.insert(buffer, suggestion_groups);
|
||||
let mut suggestion_groups_by_buffer = HashMap::default();
|
||||
for (buffer, mut suggestions) in suggestions_by_buffer {
|
||||
let mut suggestion_groups = Vec::<WorkflowSuggestionGroup>::new();
|
||||
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
|
||||
// Sort suggestions by their range so that earlier, larger ranges come first
|
||||
suggestions.sort_by(|a, b| a.range().cmp(&b.range(), &snapshot));
|
||||
|
||||
// Merge overlapping suggestions
|
||||
suggestions.dedup_by(|a, b| b.try_merge(&a, &snapshot));
|
||||
|
||||
// Create context ranges for each suggestion
|
||||
for suggestion in suggestions {
|
||||
let context_range = {
|
||||
let suggestion_point_range =
|
||||
suggestion.range().to_point(&snapshot);
|
||||
let start_row =
|
||||
suggestion_point_range.start.row.saturating_sub(5);
|
||||
let end_row = cmp::min(
|
||||
suggestion_point_range.end.row + 5,
|
||||
snapshot.max_point().row,
|
||||
);
|
||||
let start = snapshot.anchor_before(Point::new(start_row, 0));
|
||||
let end = snapshot.anchor_after(Point::new(
|
||||
end_row,
|
||||
snapshot.line_len(end_row),
|
||||
));
|
||||
start..end
|
||||
};
|
||||
|
||||
if let Some(last_group) = suggestion_groups.last_mut() {
|
||||
if last_group
|
||||
.context_range
|
||||
.end
|
||||
.cmp(&context_range.start, &snapshot)
|
||||
.is_ge()
|
||||
{
|
||||
// Merge with the previous group if context ranges overlap
|
||||
last_group.context_range.end = context_range.end;
|
||||
last_group.suggestions.push(suggestion);
|
||||
} else {
|
||||
// Create a new group
|
||||
suggestion_groups.push(WorkflowSuggestionGroup {
|
||||
context_range,
|
||||
suggestions: vec![suggestion],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Create the first group
|
||||
suggestion_groups.push(WorkflowSuggestionGroup {
|
||||
context_range,
|
||||
suggestions: vec![suggestion],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
suggestion_groups_by_buffer.insert(buffer, suggestion_groups);
|
||||
}
|
||||
|
||||
Ok((resolution.step_title, suggestion_groups_by_buffer))
|
||||
};
|
||||
|
||||
let result = result.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let step_index = this
|
||||
.workflow_steps
|
||||
.binary_search_by(|step| {
|
||||
step.tagged_range.cmp(&tagged_range, this.buffer.read(cx))
|
||||
})
|
||||
.map_err(|_| anyhow!("edit step not found"))?;
|
||||
if let Some(edit_step) = this.workflow_steps.get_mut(step_index) {
|
||||
edit_step.status = match result {
|
||||
Ok((title, suggestions)) => {
|
||||
WorkflowStepStatus::Resolved(ResolvedWorkflowStep {
|
||||
title,
|
||||
suggestions,
|
||||
})
|
||||
}
|
||||
Err(error) => WorkflowStepStatus::Error(Arc::new(error)),
|
||||
};
|
||||
cx.emit(ContextEvent::WorkflowStepUpdated(tagged_range));
|
||||
cx.notify();
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})?
|
||||
}
|
||||
.log_err()
|
||||
}));
|
||||
} else {
|
||||
edit_step.status = WorkflowStepStatus::Error(Arc::new(anyhow!("no active model")));
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let step_index = this
|
||||
.workflow_steps
|
||||
.binary_search_by(|step| {
|
||||
step.tagged_range.cmp(&tagged_range, this.buffer.read(cx))
|
||||
})
|
||||
.map_err(|_| anyhow!("edit step not found"))?;
|
||||
if let Some(edit_step) = this.workflow_steps.get_mut(step_index) {
|
||||
edit_step.edit_suggestions = WorkflowStepEditSuggestions::Resolved(
|
||||
ResolvedWorkflowStepEditSuggestions {
|
||||
title: step_suggestions.step_title,
|
||||
edit_suggestions: suggestion_groups_by_buffer,
|
||||
},
|
||||
);
|
||||
cx.emit(ContextEvent::WorkflowStepsChanged);
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})?
|
||||
}
|
||||
.log_err()
|
||||
})
|
||||
cx.emit(ContextEvent::WorkflowStepUpdated(tagged_range));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn pending_command_for_position(
|
||||
@@ -1584,7 +1629,7 @@ impl Context {
|
||||
message_start_offset..message_new_end_offset
|
||||
});
|
||||
if let Some(project) = this.project.clone() {
|
||||
this.parse_edit_steps_in_range(message_range, project, cx);
|
||||
this.parse_workflow_steps_in_range(message_range, project, cx);
|
||||
}
|
||||
cx.emit(ContextEvent::StreamedCompletion);
|
||||
|
||||
@@ -3008,13 +3053,13 @@ mod tests {
|
||||
vec![
|
||||
(
|
||||
Point::new(response_start_row + 2, 0)
|
||||
..Point::new(response_start_row + 14, 7),
|
||||
WorkflowStepEditSuggestionStatus::Pending
|
||||
..Point::new(response_start_row + 13, 3),
|
||||
WorkflowStepTestStatus::Pending
|
||||
),
|
||||
(
|
||||
Point::new(response_start_row + 16, 0)
|
||||
..Point::new(response_start_row + 28, 7),
|
||||
WorkflowStepEditSuggestionStatus::Pending
|
||||
Point::new(response_start_row + 15, 0)
|
||||
..Point::new(response_start_row + 26, 3),
|
||||
WorkflowStepTestStatus::Pending
|
||||
),
|
||||
]
|
||||
);
|
||||
@@ -3022,65 +3067,61 @@ mod tests {
|
||||
|
||||
model
|
||||
.as_fake()
|
||||
.respond_to_last_tool_use(Ok(serde_json::to_value(
|
||||
tool::WorkflowStepEditSuggestions {
|
||||
step_title: "Title".into(),
|
||||
edit_suggestions: vec![tool::EditSuggestion {
|
||||
path: "/root/hello.rs".into(),
|
||||
// Simulate a symbol name that's slightly different than our outline query
|
||||
kind: tool::EditSuggestionKind::Update {
|
||||
symbol: "fn main()".into(),
|
||||
description: "Extract a greeting function".into(),
|
||||
},
|
||||
}],
|
||||
},
|
||||
)
|
||||
.respond_to_last_tool_use(Ok(serde_json::to_value(tool::WorkflowStepResolution {
|
||||
step_title: "Title".into(),
|
||||
suggestions: vec![tool::WorkflowSuggestion {
|
||||
path: "/root/hello.rs".into(),
|
||||
// Simulate a symbol name that's slightly different than our outline query
|
||||
kind: tool::WorkflowSuggestionKind::Update {
|
||||
symbol: "fn main()".into(),
|
||||
description: "Extract a greeting function".into(),
|
||||
},
|
||||
}],
|
||||
})
|
||||
.unwrap()));
|
||||
|
||||
// Wait for tool use to be processed.
|
||||
cx.run_until_parked();
|
||||
|
||||
// Verify that the last edit step is not pending anymore.
|
||||
// Verify that the first edit step is not pending anymore.
|
||||
context.read_with(cx, |context, cx| {
|
||||
assert_eq!(
|
||||
workflow_steps(context, cx),
|
||||
vec![
|
||||
(
|
||||
Point::new(response_start_row + 2, 0)
|
||||
..Point::new(response_start_row + 14, 7),
|
||||
WorkflowStepEditSuggestionStatus::Pending
|
||||
..Point::new(response_start_row + 13, 3),
|
||||
WorkflowStepTestStatus::Resolved
|
||||
),
|
||||
(
|
||||
Point::new(response_start_row + 16, 0)
|
||||
..Point::new(response_start_row + 28, 7),
|
||||
WorkflowStepEditSuggestionStatus::Resolved
|
||||
Point::new(response_start_row + 15, 0)
|
||||
..Point::new(response_start_row + 26, 3),
|
||||
WorkflowStepTestStatus::Pending
|
||||
),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
enum WorkflowStepEditSuggestionStatus {
|
||||
enum WorkflowStepTestStatus {
|
||||
Pending,
|
||||
Resolved,
|
||||
Error,
|
||||
}
|
||||
|
||||
fn workflow_steps(
|
||||
context: &Context,
|
||||
cx: &AppContext,
|
||||
) -> Vec<(Range<Point>, WorkflowStepEditSuggestionStatus)> {
|
||||
) -> Vec<(Range<Point>, WorkflowStepTestStatus)> {
|
||||
context
|
||||
.workflow_steps
|
||||
.iter()
|
||||
.map(|step| {
|
||||
let buffer = context.buffer.read(cx);
|
||||
let status = match &step.edit_suggestions {
|
||||
WorkflowStepEditSuggestions::Pending(_) => {
|
||||
WorkflowStepEditSuggestionStatus::Pending
|
||||
}
|
||||
WorkflowStepEditSuggestions::Resolved { .. } => {
|
||||
WorkflowStepEditSuggestionStatus::Resolved
|
||||
}
|
||||
let status = match &step.status {
|
||||
WorkflowStepStatus::Pending(_) => WorkflowStepTestStatus::Pending,
|
||||
WorkflowStepStatus::Resolved { .. } => WorkflowStepTestStatus::Resolved,
|
||||
WorkflowStepStatus::Error(_) => WorkflowStepTestStatus::Error,
|
||||
};
|
||||
(step.tagged_range.to_point(buffer), status)
|
||||
})
|
||||
@@ -3490,15 +3531,15 @@ mod tool {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct WorkflowStepEditSuggestions {
|
||||
pub struct WorkflowStepResolution {
|
||||
/// An extremely short title for the edit step represented by these operations.
|
||||
pub step_title: String,
|
||||
/// A sequence of operations to apply to the codebase.
|
||||
/// When multiple operations are required for a step, be sure to include multiple operations in this list.
|
||||
pub edit_suggestions: Vec<EditSuggestion>,
|
||||
pub suggestions: Vec<WorkflowSuggestion>,
|
||||
}
|
||||
|
||||
impl LanguageModelTool for WorkflowStepEditSuggestions {
|
||||
impl LanguageModelTool for WorkflowStepResolution {
|
||||
fn name() -> String {
|
||||
"edit".into()
|
||||
}
|
||||
@@ -3527,19 +3568,19 @@ mod tool {
|
||||
/// programmatic changes to source code. It provides a structured way to describe
|
||||
/// edits for features like refactoring tools or AI-assisted coding suggestions.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct EditSuggestion {
|
||||
pub struct WorkflowSuggestion {
|
||||
/// The path to the file containing the relevant operation
|
||||
pub path: String,
|
||||
#[serde(flatten)]
|
||||
pub kind: EditSuggestionKind,
|
||||
pub kind: WorkflowSuggestionKind,
|
||||
}
|
||||
|
||||
impl EditSuggestion {
|
||||
impl WorkflowSuggestion {
|
||||
pub(super) async fn resolve(
|
||||
&self,
|
||||
project: Model<Project>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<(Model<Buffer>, super::EditSuggestion)> {
|
||||
) -> Result<(Model<Buffer>, super::WorkflowSuggestion)> {
|
||||
let path = self.path.clone();
|
||||
let kind = self.kind.clone();
|
||||
let buffer = project
|
||||
@@ -3561,7 +3602,7 @@ mod tool {
|
||||
|
||||
let suggestion;
|
||||
match kind {
|
||||
EditSuggestionKind::Update {
|
||||
WorkflowSuggestionKind::Update {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3578,12 +3619,12 @@ mod tool {
|
||||
snapshot.line_len(symbol.range.end.row),
|
||||
);
|
||||
let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
|
||||
suggestion = super::EditSuggestion::Update { range, description };
|
||||
suggestion = super::WorkflowSuggestion::Update { range, description };
|
||||
}
|
||||
EditSuggestionKind::Create { description } => {
|
||||
suggestion = super::EditSuggestion::CreateFile { description };
|
||||
WorkflowSuggestionKind::Create { description } => {
|
||||
suggestion = super::WorkflowSuggestion::CreateFile { description };
|
||||
}
|
||||
EditSuggestionKind::InsertSiblingBefore {
|
||||
WorkflowSuggestionKind::InsertSiblingBefore {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3598,12 +3639,12 @@ mod tool {
|
||||
annotation_range.start
|
||||
}),
|
||||
);
|
||||
suggestion = super::EditSuggestion::InsertSiblingBefore {
|
||||
suggestion = super::WorkflowSuggestion::InsertSiblingBefore {
|
||||
position,
|
||||
description,
|
||||
};
|
||||
}
|
||||
EditSuggestionKind::InsertSiblingAfter {
|
||||
WorkflowSuggestionKind::InsertSiblingAfter {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3612,12 +3653,12 @@ mod tool {
|
||||
.with_context(|| format!("symbol not found: {:?}", symbol))?
|
||||
.to_point(&snapshot);
|
||||
let position = snapshot.anchor_after(symbol.range.end);
|
||||
suggestion = super::EditSuggestion::InsertSiblingAfter {
|
||||
suggestion = super::WorkflowSuggestion::InsertSiblingAfter {
|
||||
position,
|
||||
description,
|
||||
};
|
||||
}
|
||||
EditSuggestionKind::PrependChild {
|
||||
WorkflowSuggestionKind::PrependChild {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3632,18 +3673,18 @@ mod tool {
|
||||
.body_range
|
||||
.map_or(symbol.range.start, |body_range| body_range.start),
|
||||
);
|
||||
suggestion = super::EditSuggestion::PrependChild {
|
||||
suggestion = super::WorkflowSuggestion::PrependChild {
|
||||
position,
|
||||
description,
|
||||
};
|
||||
} else {
|
||||
suggestion = super::EditSuggestion::PrependChild {
|
||||
suggestion = super::WorkflowSuggestion::PrependChild {
|
||||
position: language::Anchor::MIN,
|
||||
description,
|
||||
};
|
||||
}
|
||||
}
|
||||
EditSuggestionKind::AppendChild {
|
||||
WorkflowSuggestionKind::AppendChild {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3658,18 +3699,18 @@ mod tool {
|
||||
.body_range
|
||||
.map_or(symbol.range.end, |body_range| body_range.end),
|
||||
);
|
||||
suggestion = super::EditSuggestion::AppendChild {
|
||||
suggestion = super::WorkflowSuggestion::AppendChild {
|
||||
position,
|
||||
description,
|
||||
};
|
||||
} else {
|
||||
suggestion = super::EditSuggestion::PrependChild {
|
||||
suggestion = super::WorkflowSuggestion::PrependChild {
|
||||
position: language::Anchor::MAX,
|
||||
description,
|
||||
};
|
||||
}
|
||||
}
|
||||
EditSuggestionKind::Delete { symbol } => {
|
||||
WorkflowSuggestionKind::Delete { symbol } => {
|
||||
let symbol = outline
|
||||
.find_most_similar(&symbol)
|
||||
.with_context(|| format!("symbol not found: {:?}", symbol))?
|
||||
@@ -3683,7 +3724,7 @@ mod tool {
|
||||
snapshot.line_len(symbol.range.end.row),
|
||||
);
|
||||
let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
|
||||
suggestion = super::EditSuggestion::Delete { range };
|
||||
suggestion = super::WorkflowSuggestion::Delete { range };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3693,7 +3734,7 @@ mod tool {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum EditSuggestionKind {
|
||||
pub enum WorkflowSuggestionKind {
|
||||
/// Rewrites the specified symbol entirely based on the given description.
|
||||
/// This operation completely replaces the existing symbol with new content.
|
||||
Update {
|
||||
@@ -3754,7 +3795,7 @@ mod tool {
|
||||
},
|
||||
}
|
||||
|
||||
impl EditSuggestionKind {
|
||||
impl WorkflowSuggestionKind {
|
||||
pub fn symbol(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Update { symbol, .. } => Some(symbol),
|
||||
@@ -3781,14 +3822,14 @@ mod tool {
|
||||
|
||||
pub fn initial_insertion(&self) -> Option<InitialInsertion> {
|
||||
match self {
|
||||
EditSuggestionKind::InsertSiblingBefore { .. } => {
|
||||
WorkflowSuggestionKind::InsertSiblingBefore { .. } => {
|
||||
Some(InitialInsertion::NewlineAfter)
|
||||
}
|
||||
EditSuggestionKind::InsertSiblingAfter { .. } => {
|
||||
WorkflowSuggestionKind::InsertSiblingAfter { .. } => {
|
||||
Some(InitialInsertion::NewlineBefore)
|
||||
}
|
||||
EditSuggestionKind::PrependChild { .. } => Some(InitialInsertion::NewlineAfter),
|
||||
EditSuggestionKind::AppendChild { .. } => Some(InitialInsertion::NewlineBefore),
|
||||
WorkflowSuggestionKind::PrependChild { .. } => Some(InitialInsertion::NewlineAfter),
|
||||
WorkflowSuggestionKind::AppendChild { .. } => Some(InitialInsertion::NewlineBefore),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ pub struct InlineAssistant {
|
||||
assists: HashMap<InlineAssistId, InlineAssist>,
|
||||
assists_by_editor: HashMap<WeakView<Editor>, EditorInlineAssists>,
|
||||
assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>,
|
||||
assist_observations:
|
||||
HashMap<InlineAssistId, (async_watch::Sender<()>, async_watch::Receiver<()>)>,
|
||||
confirmed_assists: HashMap<InlineAssistId, Model<Codegen>>,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
@@ -88,6 +91,8 @@ impl InlineAssistant {
|
||||
assists: HashMap::default(),
|
||||
assists_by_editor: HashMap::default(),
|
||||
assist_groups: HashMap::default(),
|
||||
assist_observations: HashMap::default(),
|
||||
confirmed_assists: HashMap::default(),
|
||||
prompt_history: VecDeque::default(),
|
||||
prompt_builder,
|
||||
telemetry: Some(telemetry),
|
||||
@@ -343,6 +348,7 @@ impl InlineAssistant {
|
||||
height: prompt_editor_height,
|
||||
render: build_assist_editor_renderer(prompt_editor),
|
||||
disposition: BlockDisposition::Above,
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Sticky,
|
||||
@@ -357,6 +363,7 @@ impl InlineAssistant {
|
||||
.into_any_element()
|
||||
}),
|
||||
disposition: BlockDisposition::Below,
|
||||
priority: 0,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -654,8 +661,21 @@ impl InlineAssistant {
|
||||
|
||||
if undo {
|
||||
assist.codegen.update(cx, |codegen, cx| codegen.undo(cx));
|
||||
} else {
|
||||
self.confirmed_assists.insert(assist_id, assist.codegen);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the assist from the status updates map
|
||||
self.assist_observations.remove(&assist_id);
|
||||
}
|
||||
|
||||
pub fn undo_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) -> bool {
|
||||
let Some(codegen) = self.confirmed_assists.remove(&assist_id) else {
|
||||
return false;
|
||||
};
|
||||
codegen.update(cx, |this, cx| this.undo(cx));
|
||||
true
|
||||
}
|
||||
|
||||
fn dismiss_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) -> bool {
|
||||
@@ -854,6 +874,10 @@ impl InlineAssistant {
|
||||
)
|
||||
})
|
||||
.log_err();
|
||||
|
||||
if let Some((tx, _)) = self.assist_observations.get(&assist_id) {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
|
||||
@@ -864,19 +888,24 @@ impl InlineAssistant {
|
||||
};
|
||||
|
||||
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
|
||||
|
||||
if let Some((tx, _)) = self.assist_observations.get(&assist_id) {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn status_for_assist(
|
||||
&self,
|
||||
assist_id: InlineAssistId,
|
||||
cx: &WindowContext,
|
||||
) -> Option<CodegenStatus> {
|
||||
let assist = self.assists.get(&assist_id)?;
|
||||
match &assist.codegen.read(cx).status {
|
||||
CodegenStatus::Idle => Some(CodegenStatus::Idle),
|
||||
CodegenStatus::Pending => Some(CodegenStatus::Pending),
|
||||
CodegenStatus::Done => Some(CodegenStatus::Done),
|
||||
CodegenStatus::Error(error) => Some(CodegenStatus::Error(anyhow!("{:?}", error))),
|
||||
pub fn assist_status(&self, assist_id: InlineAssistId, cx: &AppContext) -> InlineAssistStatus {
|
||||
if let Some(assist) = self.assists.get(&assist_id) {
|
||||
match &assist.codegen.read(cx).status {
|
||||
CodegenStatus::Idle => InlineAssistStatus::Idle,
|
||||
CodegenStatus::Pending => InlineAssistStatus::Pending,
|
||||
CodegenStatus::Done => InlineAssistStatus::Done,
|
||||
CodegenStatus::Error(_) => InlineAssistStatus::Error,
|
||||
}
|
||||
} else if self.confirmed_assists.contains_key(&assist_id) {
|
||||
InlineAssistStatus::Confirmed
|
||||
} else {
|
||||
InlineAssistStatus::Canceled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1051,6 +1080,7 @@ impl InlineAssistant {
|
||||
.into_any_element()
|
||||
}),
|
||||
disposition: BlockDisposition::Above,
|
||||
priority: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1060,6 +1090,37 @@ impl InlineAssistant {
|
||||
.collect();
|
||||
})
|
||||
}
|
||||
|
||||
pub fn observe_assist(&mut self, assist_id: InlineAssistId) -> async_watch::Receiver<()> {
|
||||
if let Some((_, rx)) = self.assist_observations.get(&assist_id) {
|
||||
rx.clone()
|
||||
} else {
|
||||
let (tx, rx) = async_watch::channel(());
|
||||
self.assist_observations.insert(assist_id, (tx, rx.clone()));
|
||||
rx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum InlineAssistStatus {
|
||||
Idle,
|
||||
Pending,
|
||||
Done,
|
||||
Error,
|
||||
Confirmed,
|
||||
Canceled,
|
||||
}
|
||||
|
||||
impl InlineAssistStatus {
|
||||
pub(crate) fn is_pending(&self) -> bool {
|
||||
matches!(self, Self::Pending)
|
||||
}
|
||||
pub(crate) fn is_confirmed(&self) -> bool {
|
||||
matches!(self, Self::Confirmed)
|
||||
}
|
||||
pub(crate) fn is_done(&self) -> bool {
|
||||
matches!(self, Self::Done)
|
||||
}
|
||||
}
|
||||
|
||||
struct EditorInlineAssists {
|
||||
@@ -1964,6 +2025,8 @@ impl InlineAssist {
|
||||
|
||||
if assist.decorations.is_none() {
|
||||
this.finish_assist(assist_id, false, cx);
|
||||
} else if let Some(tx) = this.assist_observations.get(&assist_id) {
|
||||
tx.0.send(()).ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -2037,7 +2100,7 @@ pub struct Codegen {
|
||||
builder: Arc<PromptBuilder>,
|
||||
}
|
||||
|
||||
pub enum CodegenStatus {
|
||||
enum CodegenStatus {
|
||||
Idle,
|
||||
Pending,
|
||||
Done,
|
||||
@@ -2156,7 +2219,7 @@ impl Codegen {
|
||||
|
||||
if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
buffer.undo_transaction(transformation_transaction_id, cx)
|
||||
buffer.undo_transaction(transformation_transaction_id, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2510,10 +2573,12 @@ impl Codegen {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
if let Some(transaction_id) = self.transformation_transaction_id.take() {
|
||||
buffer.undo_transaction(transaction_id, cx);
|
||||
buffer.refresh_preview(cx);
|
||||
}
|
||||
|
||||
if let Some(transaction_id) = self.initial_transaction_id.take() {
|
||||
buffer.undo_transaction(transaction_id, cx);
|
||||
buffer.refresh_preview(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use feature_flags::ZedPro;
|
||||
use gpui::DismissEvent;
|
||||
use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
|
||||
use proto::Plan;
|
||||
|
||||
@@ -132,6 +133,8 @@ impl PickerDelegate for ModelPickerDelegate {
|
||||
model.is_selected = model.model.id() == selected_model_id
|
||||
&& model.model.provider_id() == selected_provider_id;
|
||||
}
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use super::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||
use feature_flags::FeatureFlag;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
|
||||
use semantic_index::SemanticIndex;
|
||||
@@ -17,6 +18,12 @@ use ui::{prelude::*, IconName};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct SearchSlashCommandFeatureFlag;
|
||||
|
||||
impl FeatureFlag for SearchSlashCommandFeatureFlag {
|
||||
const NAME: &'static str = "search-slash-command";
|
||||
}
|
||||
|
||||
pub(crate) struct SearchSlashCommand;
|
||||
|
||||
impl SlashCommand for SearchSlashCommand {
|
||||
|
||||
@@ -58,6 +58,7 @@ serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2.workspace = true
|
||||
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
|
||||
strum.workspace = true
|
||||
subtle.workspace = true
|
||||
rustc-demangle.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
|
||||
@@ -235,7 +235,8 @@ impl Config {
|
||||
}
|
||||
|
||||
/// The service mode that collab should run in.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ServiceMode {
|
||||
Api,
|
||||
Collab,
|
||||
|
||||
@@ -137,11 +137,25 @@ async fn perform_completion(
|
||||
.anthropic_api_key
|
||||
.as_ref()
|
||||
.context("no Anthropic AI API key configured on the server")?;
|
||||
|
||||
let mut request: anthropic::Request =
|
||||
serde_json::from_str(¶ms.provider_request.get())?;
|
||||
|
||||
// Parse the model, throw away the version that was included, and then set a specific
|
||||
// version that we control on the server.
|
||||
// Right now, we use the version that's defined in `model.id()`, but we will likely
|
||||
// want to change this code once a new version of an Anthropic model is released,
|
||||
// so that users can use the new version, without having to update Zed.
|
||||
request.model = match anthropic::Model::from_id(&request.model) {
|
||||
Ok(model) => model.id().to_string(),
|
||||
Err(_) => request.model,
|
||||
};
|
||||
|
||||
let chunks = anthropic::stream_completion(
|
||||
&state.http_client,
|
||||
anthropic::ANTHROPIC_API_URL,
|
||||
api_key,
|
||||
serde_json::from_str(¶ms.provider_request.get())?,
|
||||
request,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -279,10 +279,7 @@ async fn setup_llm_database(config: &Config) -> Result<()> {
|
||||
}
|
||||
|
||||
async fn handle_root(Extension(mode): Extension<ServiceMode>) -> String {
|
||||
format!(
|
||||
"collab {mode:?} v{VERSION} ({})",
|
||||
REVISION.unwrap_or("unknown")
|
||||
)
|
||||
format!("zed:{mode} v{VERSION} ({})", REVISION.unwrap_or("unknown"))
|
||||
}
|
||||
|
||||
async fn handle_liveness_probe(
|
||||
|
||||
@@ -449,6 +449,7 @@ impl ProjectDiagnosticsEditor {
|
||||
style: BlockStyle::Sticky,
|
||||
render: diagnostic_header_renderer(primary),
|
||||
disposition: BlockDisposition::Above,
|
||||
priority: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -470,6 +471,7 @@ impl ProjectDiagnosticsEditor {
|
||||
diagnostic, None, true, true,
|
||||
),
|
||||
disposition: BlockDisposition::Below,
|
||||
priority: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -508,6 +510,7 @@ impl ProjectDiagnosticsEditor {
|
||||
style: block.style,
|
||||
render: block.render,
|
||||
disposition: block.disposition,
|
||||
priority: 0,
|
||||
})
|
||||
}),
|
||||
Some(Autoscroll::fit()),
|
||||
|
||||
@@ -1281,12 +1281,14 @@ pub mod tests {
|
||||
position.to_point(&buffer),
|
||||
height
|
||||
);
|
||||
let priority = rng.gen_range(1..100);
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
position,
|
||||
height,
|
||||
disposition,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: priority,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -84,6 +84,7 @@ pub struct CustomBlock {
|
||||
style: BlockStyle,
|
||||
render: Arc<Mutex<RenderBlock>>,
|
||||
disposition: BlockDisposition,
|
||||
priority: usize,
|
||||
}
|
||||
|
||||
pub struct BlockProperties<P> {
|
||||
@@ -92,6 +93,7 @@ pub struct BlockProperties<P> {
|
||||
pub style: BlockStyle,
|
||||
pub render: RenderBlock,
|
||||
pub disposition: BlockDisposition,
|
||||
pub priority: usize,
|
||||
}
|
||||
|
||||
impl<P: Debug> Debug for BlockProperties<P> {
|
||||
@@ -182,6 +184,7 @@ pub(crate) enum BlockType {
|
||||
pub(crate) trait BlockLike {
|
||||
fn block_type(&self) -> BlockType;
|
||||
fn disposition(&self) -> BlockDisposition;
|
||||
fn priority(&self) -> usize;
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
@@ -215,6 +218,14 @@ impl BlockLike for Block {
|
||||
fn disposition(&self) -> BlockDisposition {
|
||||
self.disposition()
|
||||
}
|
||||
|
||||
fn priority(&self) -> usize {
|
||||
match self {
|
||||
Block::Custom(block) => block.priority,
|
||||
Block::ExcerptHeader { .. } => usize::MAX,
|
||||
Block::ExcerptFooter { .. } => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Block {
|
||||
@@ -660,7 +671,10 @@ impl BlockMap {
|
||||
(BlockType::Header, BlockType::Header) => Ordering::Equal,
|
||||
(BlockType::Header, _) => Ordering::Less,
|
||||
(_, BlockType::Header) => Ordering::Greater,
|
||||
(BlockType::Custom(a_id), BlockType::Custom(b_id)) => a_id.cmp(&b_id),
|
||||
(BlockType::Custom(a_id), BlockType::Custom(b_id)) => block_b
|
||||
.priority()
|
||||
.cmp(&block_a.priority())
|
||||
.then_with(|| a_id.cmp(&b_id)),
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -802,6 +816,7 @@ impl<'a> BlockMapWriter<'a> {
|
||||
render: Arc::new(Mutex::new(block.render)),
|
||||
disposition: block.disposition,
|
||||
style: block.style,
|
||||
priority: block.priority,
|
||||
});
|
||||
self.0.custom_blocks.insert(block_ix, new_block.clone());
|
||||
self.0.custom_blocks_by_id.insert(id, new_block);
|
||||
@@ -832,6 +847,7 @@ impl<'a> BlockMapWriter<'a> {
|
||||
style: block.style,
|
||||
render: block.render.clone(),
|
||||
disposition: block.disposition,
|
||||
priority: block.priority,
|
||||
};
|
||||
let new_block = Arc::new(new_block);
|
||||
*block = new_block.clone();
|
||||
@@ -1463,6 +1479,7 @@ mod tests {
|
||||
height: 1,
|
||||
disposition: BlockDisposition::Above,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
@@ -1470,6 +1487,7 @@ mod tests {
|
||||
height: 2,
|
||||
disposition: BlockDisposition::Above,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
@@ -1477,6 +1495,7 @@ mod tests {
|
||||
height: 3,
|
||||
disposition: BlockDisposition::Below,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1716,6 +1735,7 @@ mod tests {
|
||||
height: 1,
|
||||
disposition: BlockDisposition::Above,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
@@ -1723,6 +1743,7 @@ mod tests {
|
||||
height: 2,
|
||||
disposition: BlockDisposition::Above,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
@@ -1730,6 +1751,7 @@ mod tests {
|
||||
height: 3,
|
||||
disposition: BlockDisposition::Below,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1819,6 +1841,7 @@ mod tests {
|
||||
disposition: BlockDisposition::Above,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
height: 1,
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
@@ -1826,6 +1849,7 @@ mod tests {
|
||||
disposition: BlockDisposition::Below,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
height: 1,
|
||||
priority: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1924,6 +1948,7 @@ mod tests {
|
||||
height,
|
||||
disposition,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -1944,6 +1969,7 @@ mod tests {
|
||||
style: props.style,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
disposition: props.disposition,
|
||||
priority: 0,
|
||||
}));
|
||||
for (block_id, props) in block_ids.into_iter().zip(block_properties) {
|
||||
custom_blocks.push((block_id, props));
|
||||
@@ -2014,6 +2040,7 @@ mod tests {
|
||||
disposition: block.disposition,
|
||||
id: *id,
|
||||
height: block.height,
|
||||
priority: block.priority,
|
||||
},
|
||||
)
|
||||
}));
|
||||
@@ -2235,6 +2262,7 @@ mod tests {
|
||||
disposition: BlockDisposition,
|
||||
id: CustomBlockId,
|
||||
height: u32,
|
||||
priority: usize,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2250,6 +2278,14 @@ mod tests {
|
||||
fn disposition(&self) -> BlockDisposition {
|
||||
self.disposition()
|
||||
}
|
||||
|
||||
fn priority(&self) -> usize {
|
||||
match self {
|
||||
ExpectedBlock::Custom { priority, .. } => *priority,
|
||||
ExpectedBlock::ExcerptHeader { .. } => usize::MAX,
|
||||
ExpectedBlock::ExcerptFooter { .. } => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExpectedBlock {
|
||||
@@ -2277,6 +2313,7 @@ mod tests {
|
||||
id: block.id,
|
||||
disposition: block.disposition,
|
||||
height: block.height,
|
||||
priority: block.priority,
|
||||
},
|
||||
Block::ExcerptHeader {
|
||||
height,
|
||||
|
||||
@@ -9614,6 +9614,7 @@ impl Editor {
|
||||
}
|
||||
}),
|
||||
disposition: BlockDisposition::Below,
|
||||
priority: 0,
|
||||
}],
|
||||
Some(Autoscroll::fit()),
|
||||
cx,
|
||||
@@ -9877,6 +9878,7 @@ impl Editor {
|
||||
height: message_height,
|
||||
render: diagnostic_block_renderer(diagnostic, None, true, true),
|
||||
disposition: BlockDisposition::Below,
|
||||
priority: 0,
|
||||
}
|
||||
}),
|
||||
cx,
|
||||
@@ -10182,6 +10184,7 @@ impl Editor {
|
||||
if let Some(autoscroll) = autoscroll {
|
||||
self.request_autoscroll(autoscroll, cx);
|
||||
}
|
||||
cx.notify();
|
||||
blocks
|
||||
}
|
||||
|
||||
@@ -10196,6 +10199,7 @@ impl Editor {
|
||||
if let Some(autoscroll) = autoscroll {
|
||||
self.request_autoscroll(autoscroll, cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn replace_blocks(
|
||||
@@ -10208,9 +10212,8 @@ impl Editor {
|
||||
.update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
|
||||
if let Some(autoscroll) = autoscroll {
|
||||
self.request_autoscroll(autoscroll, cx);
|
||||
} else {
|
||||
cx.notify();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn remove_blocks(
|
||||
@@ -10225,6 +10228,7 @@ impl Editor {
|
||||
if let Some(autoscroll) = autoscroll {
|
||||
self.request_autoscroll(autoscroll, cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn row_for_block(
|
||||
|
||||
@@ -3785,6 +3785,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
|
||||
disposition: BlockDisposition::Below,
|
||||
height: 1,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}],
|
||||
Some(Autoscroll::fit()),
|
||||
cx,
|
||||
|
||||
@@ -6478,6 +6478,7 @@ mod tests {
|
||||
height: 3,
|
||||
position: Anchor::min(),
|
||||
render: Box::new(|cx| div().h(3. * cx.line_height()).into_any()),
|
||||
priority: 0,
|
||||
}],
|
||||
None,
|
||||
cx,
|
||||
|
||||
@@ -525,6 +525,7 @@ impl Editor {
|
||||
.child(editor_with_deleted_text.clone())
|
||||
.into_any_element()
|
||||
}),
|
||||
priority: 0,
|
||||
}),
|
||||
None,
|
||||
cx,
|
||||
|
||||
@@ -894,6 +894,10 @@ impl Item for Editor {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn preserve_preview(&self, cx: &AppContext) -> bool {
|
||||
self.buffer.read(cx).preserve_preview(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializableItem for Editor {
|
||||
|
||||
@@ -97,6 +97,7 @@ pub struct Buffer {
|
||||
/// The version vector when this buffer was last loaded from
|
||||
/// or saved to disk.
|
||||
saved_version: clock::Global,
|
||||
preview_version: clock::Global,
|
||||
transaction_depth: usize,
|
||||
was_dirty_before_starting_transaction: Option<bool>,
|
||||
reload_task: Option<Task<Result<()>>>,
|
||||
@@ -703,6 +704,7 @@ impl Buffer {
|
||||
Self {
|
||||
saved_mtime,
|
||||
saved_version: buffer.version(),
|
||||
preview_version: buffer.version(),
|
||||
reload_task: None,
|
||||
transaction_depth: 0,
|
||||
was_dirty_before_starting_transaction: None,
|
||||
@@ -1351,7 +1353,11 @@ impl Buffer {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let preserve_preview = self.preserve_preview();
|
||||
self.edit(edits, None, cx);
|
||||
if preserve_preview {
|
||||
self.refresh_preview();
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a minimal edit that will cause the given row to be indented
|
||||
@@ -2195,6 +2201,18 @@ impl Buffer {
|
||||
pub fn completion_triggers(&self) -> &[String] {
|
||||
&self.completion_triggers
|
||||
}
|
||||
|
||||
/// Call this directly after performing edits to prevent the preview tab
|
||||
/// from being dismissed by those edits. It causes `should_dismiss_preview`
|
||||
/// to return false until there are additional edits.
|
||||
pub fn refresh_preview(&mut self) {
|
||||
self.preview_version = self.version.clone();
|
||||
}
|
||||
|
||||
/// Whether we should preserve the preview status of a tab containing this buffer.
|
||||
pub fn preserve_preview(&self) -> bool {
|
||||
!self.has_edits_since(&self.preview_version)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
||||
@@ -1822,6 +1822,63 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_async_autoindents_preserve_preview(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| init_settings(cx, |_| {}));
|
||||
|
||||
// First we insert some newlines to request an auto-indent (asynchronously).
|
||||
// Then we request that a preview tab be preserved for the new version, even though it's edited.
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let text = "fn a() {}";
|
||||
let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
|
||||
|
||||
// This causes autoindent to be async.
|
||||
buffer.set_sync_parse_timeout(Duration::ZERO);
|
||||
|
||||
buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
|
||||
buffer.refresh_preview();
|
||||
|
||||
// Synchronously, we haven't auto-indented and we're still preserving the preview.
|
||||
assert_eq!(buffer.text(), "fn a() {\n\n}");
|
||||
assert!(buffer.preserve_preview());
|
||||
buffer
|
||||
});
|
||||
|
||||
// Now let the autoindent finish
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// The auto-indent applied, but didn't dismiss our preview
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
assert_eq!(buffer.text(), "fn a() {\n \n}");
|
||||
assert!(buffer.preserve_preview());
|
||||
|
||||
// Edit inserting another line. It will autoindent async.
|
||||
// Then refresh the preview version.
|
||||
buffer.edit(
|
||||
[(Point::new(1, 4)..Point::new(1, 4), "\n")],
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
buffer.refresh_preview();
|
||||
assert_eq!(buffer.text(), "fn a() {\n \n\n}");
|
||||
assert!(buffer.preserve_preview());
|
||||
|
||||
// Then perform another edit, this time without refreshing the preview version.
|
||||
buffer.edit([(Point::new(1, 4)..Point::new(1, 4), "x")], None, cx);
|
||||
// This causes the preview to not be preserved.
|
||||
assert!(!buffer.preserve_preview());
|
||||
});
|
||||
|
||||
// Let the async autoindent from the first edit finish.
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// The autoindent applies, but it shouldn't restore the preview status because we had an edit in the meantime.
|
||||
buffer.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn a() {\n x\n \n}");
|
||||
assert!(!buffer.preserve_preview());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_insert_empty_line(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
@@ -18,7 +18,7 @@ use settings::{Settings, SettingsStore};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use strum::IntoEnumIterator;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, Indicator};
|
||||
use ui::{prelude::*, Icon, IconName};
|
||||
use util::ResultExt;
|
||||
|
||||
const PROVIDER_ID: &str = "anthropic";
|
||||
@@ -535,9 +535,9 @@ impl Render for ConfigurationView {
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Indicator::dot().color(Color::Success))
|
||||
.child(Label::new("API key configured").size(LabelSize::Small)),
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::Check).color(Color::Success))
|
||||
.child(Label::new("API key configured.")),
|
||||
)
|
||||
.child(
|
||||
Button::new("reset-key", "Reset key")
|
||||
|
||||
@@ -18,8 +18,8 @@ use settings::{Settings, SettingsStore};
|
||||
use std::time::Duration;
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::{
|
||||
div, h_flex, v_flex, Button, ButtonCommon, Clickable, Color, Context, FixedWidth, IconName,
|
||||
IconPosition, IconSize, Indicator, IntoElement, Label, LabelCommon, ParentElement, Styled,
|
||||
div, h_flex, v_flex, Button, ButtonCommon, Clickable, Color, Context, FixedWidth, Icon,
|
||||
IconName, IconPosition, IconSize, IntoElement, Label, LabelCommon, ParentElement, Styled,
|
||||
ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
|
||||
@@ -305,8 +305,8 @@ impl Render for ConfigurationView {
|
||||
if self.state.read(cx).is_authenticated(cx) {
|
||||
const LABEL: &str = "Authorized.";
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Indicator::dot().color(Color::Success))
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::Check).color(Color::Success))
|
||||
.child(Label::new(LABEL))
|
||||
} else {
|
||||
let loading_icon = svg()
|
||||
|
||||
@@ -14,7 +14,7 @@ use settings::{Settings, SettingsStore};
|
||||
use std::{future, sync::Arc, time::Duration};
|
||||
use strum::IntoEnumIterator;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, Indicator};
|
||||
use ui::{prelude::*, Icon, IconName};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{
|
||||
@@ -454,9 +454,9 @@ impl Render for ConfigurationView {
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Indicator::dot().color(Color::Success))
|
||||
.child(Label::new("API key configured").size(LabelSize::Small)),
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::Check).color(Color::Success))
|
||||
.child(Label::new("API key configured.")),
|
||||
)
|
||||
.child(
|
||||
Button::new("reset-key", "Reset key")
|
||||
|
||||
@@ -16,7 +16,7 @@ use settings::{Settings, SettingsStore};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use strum::IntoEnumIterator;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, Indicator};
|
||||
use ui::{prelude::*, Icon, IconName};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{
|
||||
@@ -505,7 +505,7 @@ impl Render for ConfigurationView {
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::save_api_key))
|
||||
.children(
|
||||
INSTRUCTIONS.map(|instruction| Label::new(instruction).size(LabelSize::Small)),
|
||||
INSTRUCTIONS.map(|instruction| Label::new(instruction)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -530,9 +530,9 @@ impl Render for ConfigurationView {
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Indicator::dot().color(Color::Success))
|
||||
.child(Label::new("API key configured").size(LabelSize::Small)),
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::Check).color(Color::Success))
|
||||
.child(Label::new("API key configured.")),
|
||||
)
|
||||
.child(
|
||||
Button::new("reset-key", "Reset key")
|
||||
|
||||
@@ -279,10 +279,13 @@ impl MarkdownPreviewView {
|
||||
}
|
||||
|
||||
pub fn is_markdown_file<V>(editor: &View<Editor>, cx: &mut ViewContext<V>) -> bool {
|
||||
let language = editor.read(cx).buffer().read(cx).language_at(0, cx);
|
||||
language
|
||||
.map(|l| l.name().as_ref() == "Markdown")
|
||||
.unwrap_or(false)
|
||||
let buffer = editor.read(cx).buffer().read(cx);
|
||||
if let Some(buffer) = buffer.as_singleton() {
|
||||
if let Some(language) = buffer.read(cx).language() {
|
||||
return language.name().as_ref() == "Markdown";
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn set_editor(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
|
||||
|
||||
@@ -1762,6 +1762,23 @@ impl MultiBuffer {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Preserve preview tabs containing this multibuffer until additional edits occur.
|
||||
pub fn refresh_preview(&self, cx: &mut ModelContext<Self>) {
|
||||
for buffer_state in self.buffers.borrow().values() {
|
||||
buffer_state
|
||||
.buffer
|
||||
.update(cx, |buffer, _cx| buffer.refresh_preview());
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether we should preserve the preview status of a tab containing this multi-buffer.
|
||||
pub fn preserve_preview(&self, cx: &AppContext) -> bool {
|
||||
self.buffers
|
||||
.borrow()
|
||||
.values()
|
||||
.all(|state| state.buffer.read(cx).preserve_preview())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn is_parsing(&self, cx: &AppContext) -> bool {
|
||||
self.as_singleton().unwrap().read(cx).is_parsing()
|
||||
|
||||
@@ -669,7 +669,7 @@ impl RecentProjectsDelegate {
|
||||
.unwrap_or_default();
|
||||
this.update(&mut cx, move |picker, cx| {
|
||||
picker.delegate.set_workspaces(workspaces);
|
||||
picker.delegate.set_selected_index(ix - 1, cx);
|
||||
picker.delegate.set_selected_index(ix.saturating_sub(1), cx);
|
||||
picker.delegate.reset_selected_match_index = false;
|
||||
picker.update_matches(picker.query(cx), cx)
|
||||
})
|
||||
|
||||
@@ -87,6 +87,7 @@ impl EditorBlock {
|
||||
style: BlockStyle::Sticky,
|
||||
render: Self::create_output_area_renderer(execution_view.clone(), on_close.clone()),
|
||||
disposition: BlockDisposition::Below,
|
||||
priority: 0,
|
||||
};
|
||||
|
||||
let block_id = editor.insert_blocks([block], None, cx)[0];
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::{settings_store::SettingsStore, Settings};
|
||||
use anyhow::Result;
|
||||
use fs::Fs;
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use gpui::{AppContext, BackgroundExecutor, ReadGlobal, UpdateGlobal};
|
||||
@@ -67,7 +66,7 @@ pub fn watch_config_file(
|
||||
pub fn handle_settings_file_changes(
|
||||
mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
|
||||
cx: &mut AppContext,
|
||||
settings_changed: impl Fn(Result<()>, &mut AppContext) + 'static,
|
||||
settings_changed: impl Fn(Option<anyhow::Error>, &mut AppContext) + 'static,
|
||||
) {
|
||||
let user_settings_content = cx
|
||||
.background_executor()
|
||||
@@ -85,7 +84,7 @@ pub fn handle_settings_file_changes(
|
||||
if let Err(err) = &result {
|
||||
log::error!("Failed to load user settings: {err}");
|
||||
}
|
||||
settings_changed(result, cx);
|
||||
settings_changed(result.err(), cx);
|
||||
cx.refresh();
|
||||
});
|
||||
if result.is_err() {
|
||||
|
||||
@@ -81,11 +81,13 @@ pub struct Button {
|
||||
label_color: Option<Color>,
|
||||
label_size: Option<LabelSize>,
|
||||
selected_label: Option<SharedString>,
|
||||
selected_label_color: Option<Color>,
|
||||
icon: Option<IconName>,
|
||||
icon_position: Option<IconPosition>,
|
||||
icon_size: Option<IconSize>,
|
||||
icon_color: Option<Color>,
|
||||
selected_icon: Option<IconName>,
|
||||
selected_icon_color: Option<Color>,
|
||||
key_binding: Option<KeyBinding>,
|
||||
}
|
||||
|
||||
@@ -103,11 +105,13 @@ impl Button {
|
||||
label_color: None,
|
||||
label_size: None,
|
||||
selected_label: None,
|
||||
selected_label_color: None,
|
||||
icon: None,
|
||||
icon_position: None,
|
||||
icon_size: None,
|
||||
icon_color: None,
|
||||
selected_icon: None,
|
||||
selected_icon_color: None,
|
||||
key_binding: None,
|
||||
}
|
||||
}
|
||||
@@ -130,6 +134,12 @@ impl Button {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the label color used when the button is in a selected state.
|
||||
pub fn selected_label_color(mut self, color: impl Into<Option<Color>>) -> Self {
|
||||
self.selected_label_color = color.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Assigns an icon to the button.
|
||||
pub fn icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
|
||||
self.icon = icon.into();
|
||||
@@ -160,6 +170,12 @@ impl Button {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the icon color used when the button is in a selected state.
|
||||
pub fn selected_icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
|
||||
self.selected_icon_color = color.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Binds a key combination to the button for keyboard shortcuts.
|
||||
pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
|
||||
self.key_binding = key_binding.into();
|
||||
@@ -366,7 +382,7 @@ impl RenderOnce for Button {
|
||||
let label_color = if is_disabled {
|
||||
Color::Disabled
|
||||
} else if is_selected {
|
||||
Color::Selected
|
||||
self.selected_label_color.unwrap_or(Color::Selected)
|
||||
} else {
|
||||
self.label_color.unwrap_or_default()
|
||||
};
|
||||
@@ -380,6 +396,7 @@ impl RenderOnce for Button {
|
||||
.disabled(is_disabled)
|
||||
.selected(is_selected)
|
||||
.selected_icon(self.selected_icon)
|
||||
.selected_icon_color(self.selected_icon_color)
|
||||
.size(self.icon_size)
|
||||
.color(self.icon_color)
|
||||
}))
|
||||
@@ -402,6 +419,7 @@ impl RenderOnce for Button {
|
||||
.disabled(is_disabled)
|
||||
.selected(is_selected)
|
||||
.selected_icon(self.selected_icon)
|
||||
.selected_icon_color(self.selected_icon_color)
|
||||
.size(self.icon_size)
|
||||
.color(self.icon_color)
|
||||
}))
|
||||
|
||||
@@ -12,6 +12,7 @@ pub(super) struct ButtonIcon {
|
||||
disabled: bool,
|
||||
selected: bool,
|
||||
selected_icon: Option<IconName>,
|
||||
selected_icon_color: Option<Color>,
|
||||
selected_style: Option<ButtonStyle>,
|
||||
}
|
||||
|
||||
@@ -24,6 +25,7 @@ impl ButtonIcon {
|
||||
disabled: false,
|
||||
selected: false,
|
||||
selected_icon: None,
|
||||
selected_icon_color: None,
|
||||
selected_style: None,
|
||||
}
|
||||
}
|
||||
@@ -48,6 +50,11 @@ impl ButtonIcon {
|
||||
self.selected_icon = icon.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn selected_icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
|
||||
self.selected_icon_color = color.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Disableable for ButtonIcon {
|
||||
@@ -83,7 +90,7 @@ impl RenderOnce for ButtonIcon {
|
||||
} else if self.selected_style.is_some() && self.selected {
|
||||
self.selected_style.unwrap().into()
|
||||
} else if self.selected {
|
||||
Color::Selected
|
||||
self.selected_icon_color.unwrap_or(Color::Selected)
|
||||
} else {
|
||||
self.color
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ pub enum TintColor {
|
||||
Accent,
|
||||
Negative,
|
||||
Warning,
|
||||
Positive,
|
||||
}
|
||||
|
||||
impl TintColor {
|
||||
@@ -73,6 +74,12 @@ impl TintColor {
|
||||
label_color: cx.theme().colors().text,
|
||||
icon_color: cx.theme().colors().text,
|
||||
},
|
||||
TintColor::Positive => ButtonLikeStyles {
|
||||
background: cx.theme().status().success_background,
|
||||
border_color: cx.theme().status().success_border,
|
||||
label_color: cx.theme().colors().text,
|
||||
icon_color: cx.theme().colors().text,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,6 +90,7 @@ impl From<TintColor> for Color {
|
||||
TintColor::Accent => Color::Accent,
|
||||
TintColor::Negative => Color::Error,
|
||||
TintColor::Warning => Color::Warning,
|
||||
TintColor::Positive => Color::Success,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,6 +256,7 @@ pub enum IconName {
|
||||
TextSearch,
|
||||
Trash,
|
||||
TriangleRight,
|
||||
Undo,
|
||||
Update,
|
||||
WholeWord,
|
||||
XCircle,
|
||||
@@ -419,6 +420,7 @@ impl IconName {
|
||||
IconName::Trash => "icons/trash.svg",
|
||||
IconName::TriangleRight => "icons/triangle_right.svg",
|
||||
IconName::Update => "icons/update.svg",
|
||||
IconName::Undo => "icons/undo.svg",
|
||||
IconName::WholeWord => "icons/word_search.svg",
|
||||
IconName::XCircle => "icons/error.svg",
|
||||
IconName::ZedAssistant => "icons/zed_assistant.svg",
|
||||
|
||||
@@ -287,6 +287,10 @@ pub trait Item: FocusableView + EventEmitter<Self::Event> {
|
||||
fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Point<Pixels>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn preserve_preview(&self, _cx: &AppContext) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SerializableItem: Item {
|
||||
@@ -427,6 +431,7 @@ pub trait ItemHandle: 'static + Send {
|
||||
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
|
||||
fn downgrade_item(&self) -> Box<dyn WeakItemHandle>;
|
||||
fn workspace_settings<'a>(&self, cx: &'a AppContext) -> &'a WorkspaceSettings;
|
||||
fn preserve_preview(&self, cx: &AppContext) -> bool;
|
||||
}
|
||||
|
||||
pub trait WeakItemHandle: Send + Sync {
|
||||
@@ -818,6 +823,10 @@ impl<T: Item> ItemHandle for View<T> {
|
||||
) -> Option<Box<dyn SerializableItemHandle>> {
|
||||
SerializableItemRegistry::view_to_serializable_item_handle(self.to_any(), cx)
|
||||
}
|
||||
|
||||
fn preserve_preview(&self, cx: &AppContext) -> bool {
|
||||
self.read(cx).preserve_preview(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn ItemHandle>> for AnyView {
|
||||
|
||||
@@ -665,6 +665,12 @@ impl Pane {
|
||||
self.preview_item_id
|
||||
}
|
||||
|
||||
pub fn preview_item(&self) -> Option<Box<dyn ItemHandle>> {
|
||||
self.preview_item_id
|
||||
.and_then(|id| self.items.iter().find(|item| item.item_id() == id))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn preview_item_idx(&self) -> Option<usize> {
|
||||
if let Some(preview_item_id) = self.preview_item_id {
|
||||
self.items
|
||||
@@ -688,9 +694,9 @@ impl Pane {
|
||||
}
|
||||
|
||||
pub fn handle_item_edit(&mut self, item_id: EntityId, cx: &AppContext) {
|
||||
if let Some(preview_item_id) = self.preview_item_id {
|
||||
if preview_item_id == item_id {
|
||||
self.set_preview_item_id(None, cx)
|
||||
if let Some(preview_item) = self.preview_item() {
|
||||
if preview_item.item_id() == item_id && !preview_item.preserve_preview(cx) {
|
||||
self.set_preview_item_id(None, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ impl Render for Toolbar {
|
||||
let has_right_items = self.right_items().count() > 0;
|
||||
|
||||
v_flex()
|
||||
.group("toolbar")
|
||||
.p(Spacing::Large.rems(cx))
|
||||
.when(has_left_items || has_right_items, |this| {
|
||||
this.gap(Spacing::Large.rems(cx))
|
||||
|
||||
@@ -2611,6 +2611,25 @@ impl Workspace {
|
||||
open_project_item
|
||||
}
|
||||
|
||||
pub fn is_project_item_open<T>(
|
||||
&self,
|
||||
pane: &View<Pane>,
|
||||
project_item: &Model<T::Item>,
|
||||
cx: &AppContext,
|
||||
) -> bool
|
||||
where
|
||||
T: ProjectItem,
|
||||
{
|
||||
use project::Item as _;
|
||||
|
||||
project_item
|
||||
.read(cx)
|
||||
.entry_id(cx)
|
||||
.and_then(|entry_id| pane.read(cx).item_for_entry(entry_id, cx))
|
||||
.and_then(|item| item.downcast::<T>())
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn open_project_item<T>(
|
||||
&mut self,
|
||||
pane: View<Pane>,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.148.0"
|
||||
version = "0.149.0"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
stable
|
||||
dev
|
||||
@@ -430,7 +430,7 @@ fn main() {
|
||||
|
||||
settings::init(cx);
|
||||
handle_settings_file_changes(user_settings_file_rx, cx, handle_settings_changed);
|
||||
handle_keymap_file_changes(user_keymap_file_rx, cx);
|
||||
handle_keymap_file_changes(user_keymap_file_rx, cx, handle_keymap_changed);
|
||||
|
||||
client::init_settings(cx);
|
||||
let client = Client::production(cx);
|
||||
@@ -543,15 +543,39 @@ fn main() {
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_settings_changed(result: Result<()>, cx: &mut AppContext) {
|
||||
fn handle_keymap_changed(error: Option<anyhow::Error>, cx: &mut AppContext) {
|
||||
struct KeymapParseErrorNotification;
|
||||
let id = NotificationId::unique::<KeymapParseErrorNotification>();
|
||||
|
||||
for workspace in workspace::local_workspace_windows(cx) {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| match &error {
|
||||
Some(error) => {
|
||||
workspace.show_notification(id.clone(), cx, |cx| {
|
||||
cx.new_view(|_| {
|
||||
MessageNotification::new(format!("Invalid keymap file\n{error}"))
|
||||
.with_click_message("Open keymap file")
|
||||
.on_click(|cx| {
|
||||
cx.dispatch_action(zed_actions::OpenKeymap.boxed_clone());
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
None => workspace.dismiss_notification(&id, cx),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_settings_changed(error: Option<anyhow::Error>, cx: &mut AppContext) {
|
||||
struct SettingsParseErrorNotification;
|
||||
let id = NotificationId::unique::<SettingsParseErrorNotification>();
|
||||
|
||||
for workspace in workspace::local_workspace_windows(cx) {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| match &result {
|
||||
Ok(()) => workspace.dismiss_notification(&id, cx),
|
||||
Err(error) => {
|
||||
.update(cx, |workspace, cx| match &error {
|
||||
Some(error) => {
|
||||
workspace.show_notification(id.clone(), cx, |cx| {
|
||||
cx.new_view(|_| {
|
||||
MessageNotification::new(format!("Invalid settings file\n{error}"))
|
||||
@@ -563,6 +587,7 @@ fn handle_settings_changed(result: Result<()>, cx: &mut AppContext) {
|
||||
})
|
||||
});
|
||||
}
|
||||
None => workspace.dismiss_notification(&id, cx),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
@@ -733,6 +733,7 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
pub fn handle_keymap_file_changes(
|
||||
mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
|
||||
cx: &mut AppContext,
|
||||
keymap_changed: impl Fn(Option<anyhow::Error>, &mut AppContext) + 'static,
|
||||
) {
|
||||
BaseKeymap::register(cx);
|
||||
VimModeSetting::register(cx);
|
||||
@@ -761,10 +762,14 @@ pub fn handle_keymap_file_changes(
|
||||
_ = base_keymap_rx.next() => {}
|
||||
user_keymap_content = user_keymap_file_rx.next() => {
|
||||
if let Some(user_keymap_content) = user_keymap_content {
|
||||
if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
|
||||
user_keymap = keymap_content;
|
||||
} else {
|
||||
continue
|
||||
match KeymapFile::parse(&user_keymap_content) {
|
||||
Ok(keymap_content) => {
|
||||
cx.update(|cx| keymap_changed(None, cx)).log_err();
|
||||
user_keymap = keymap_content;
|
||||
}
|
||||
Err(error) => {
|
||||
cx.update(|cx| keymap_changed(Some(error), cx)).log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3097,7 +3102,7 @@ mod tests {
|
||||
PathBuf::from("/keymap.json"),
|
||||
);
|
||||
handle_settings_file_changes(settings_rx, cx, |_, _| {});
|
||||
handle_keymap_file_changes(keymap_rx, cx);
|
||||
handle_keymap_file_changes(keymap_rx, cx, |_, _| {});
|
||||
});
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
@@ -3237,7 +3242,7 @@ mod tests {
|
||||
);
|
||||
|
||||
handle_settings_file_changes(settings_rx, cx, |_, _| {});
|
||||
handle_keymap_file_changes(keymap_rx, cx);
|
||||
handle_keymap_file_changes(keymap_rx, cx, |_, _| {});
|
||||
});
|
||||
|
||||
cx.background_executor.run_until_parked();
|
||||
|
||||
@@ -26,3 +26,22 @@ Putting binary assets such as images in the Git repository will bloat the reposi
|
||||
The table of contents files (`theme/page-toc.js` and `theme/page-doc.css`) were initially generated by [`mdbook-pagetoc`](https://crates.io/crates/mdbook-pagetoc).
|
||||
|
||||
Since all these preprocessor does is generate the static assets, we don't need to keep it around once they have been generated.
|
||||
|
||||
### Highlight.js
|
||||
|
||||
mdBook by default uses Highlight.js with a custom theme for syntax highlighting with automatic language detection turned off for a list subset of common [supported languages](https://rust-lang.github.io/mdBook/format/theme/syntax-highlighting.html#supported-languages).
|
||||
|
||||
We've updated with a highlight.js that supports additional languages (`scheme`).
|
||||
To regenerate highlight.js run the following:
|
||||
|
||||
```
|
||||
git clone https://github.com/highlightjs/highlight.js
|
||||
cd highlight.js
|
||||
git checkout 10-stable
|
||||
node tools/build.js :common scheme
|
||||
|
||||
ZED_FOLDER=~/code/zed
|
||||
cp build/highlight.min.js $ZED_FOLDER/docs/theme/highlight.js
|
||||
```
|
||||
|
||||
Be sure to edit the code block above to include the any added languages (after `scheme`).
|
||||
|
||||
@@ -20,9 +20,8 @@ Clone down the [Zed repository](https://github.com/zed-industries/zed).
|
||||
rustup target add wasm32-wasi
|
||||
```
|
||||
|
||||
- Install [Visual Studio](https://visualstudio.microsoft.com/downloads/) with optional component `MSVC v*** - VS YYYY C++ x64/x86 build tools` and install Windows 11 or 10 SDK depending on your system
|
||||
|
||||
> `v***` is your VS version and `YYYY` is year when your VS was released.
|
||||
- Install [Visual Studio](https://visualstudio.microsoft.com/downloads/) with the optional component `MSVC v*** - VS YYYY C++ x64/x86 build tools` (`v***` is your VS version and `YYYY` is year when your VS was released)
|
||||
- Install Windows 11 or 10 SDK depending on your system, but ensure that at least `Windows 10 SDK version 2104 (10.0.20348.0)` is installed on your machine. You can download it from the [Windows SDK Archive](https://developer.microsoft.com/windows/downloads/windows-sdk/)
|
||||
|
||||
## Backend dependencies
|
||||
|
||||
|
||||
1250
docs/theme/highlight.js
vendored
Normal file
1250
docs/theme/highlight.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -11,9 +11,9 @@ else
|
||||
fi
|
||||
|
||||
# Install sqlx-cli if needed
|
||||
if [[ "$(sqlx --version)" != "sqlx-cli 0.5.7" ]]; then
|
||||
echo "sqlx-cli not found or not the required version, installing version 0.5.7..."
|
||||
cargo install sqlx-cli --version 0.5.7
|
||||
if [[ "$(sqlx --version)" != "sqlx-cli 0.7.2" ]]; then
|
||||
echo "sqlx-cli not found or not the required version, installing version 0.7.2..."
|
||||
cargo install sqlx-cli --version 0.7.2
|
||||
fi
|
||||
|
||||
cd crates/collab
|
||||
|
||||
@@ -9,10 +9,8 @@ prHygiene({
|
||||
});
|
||||
|
||||
const RELEASE_NOTES_PATTERN = new RegExp("Release Notes:\\r?\\n\\s+-", "gm");
|
||||
const body = danger.github.pr.body;
|
||||
|
||||
const hasReleaseNotes = RELEASE_NOTES_PATTERN.test(body);
|
||||
|
||||
const hasReleaseNotes = RELEASE_NOTES_PATTERN.test(danger.github.pr.body);
|
||||
if (!hasReleaseNotes) {
|
||||
warn(
|
||||
[
|
||||
@@ -23,7 +21,7 @@ if (!hasReleaseNotes) {
|
||||
"```",
|
||||
"Release Notes:",
|
||||
"",
|
||||
"- Added/Fixed/Improved ...",
|
||||
"- (Added|Fixed|Improved) ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/zed/issues/<public_issue_number_if_exists>)).",
|
||||
"```",
|
||||
"",
|
||||
'If your change is not user-facing, you can use "N/A" for the entry:',
|
||||
@@ -36,28 +34,21 @@ if (!hasReleaseNotes) {
|
||||
);
|
||||
}
|
||||
|
||||
const ISSUE_LINK_PATTERN = new RegExp(
|
||||
"(?:https://github\\.com/[\\w-]+/[\\w-]+/issues/\\d+|#\\d+)",
|
||||
"g",
|
||||
const INCORRECT_ISSUE_LINK_PATTERN = new RegExp("-.*\\(#\\d+\\)", "g");
|
||||
|
||||
const hasIncorrectIssueLinks = INCORRECT_ISSUE_LINK_PATTERN.test(
|
||||
danger.github.pr.body,
|
||||
);
|
||||
|
||||
const includesIssueUrl = ISSUE_LINK_PATTERN.test(body);
|
||||
|
||||
if (includesIssueUrl) {
|
||||
const matches = body.match(ISSUE_LINK_PATTERN);
|
||||
const issues = matches
|
||||
.map((match) =>
|
||||
match
|
||||
.replace(/^#/, "")
|
||||
.replace(/https:\/\/github\.com\/zed-industries\/zed\/issues\//, ""),
|
||||
)
|
||||
.filter((issue, index, self) => self.indexOf(issue) === index);
|
||||
|
||||
if (hasIncorrectIssueLinks) {
|
||||
warn(
|
||||
[
|
||||
"This PR includes links to the following GitHub Issues: " +
|
||||
issues.map((issue) => `#${issue}`).join(", "),
|
||||
"If this PR aims to close an issue, please include a `Closes #ISSUE` line at the top of the PR body.",
|
||||
"This PR has incorrectly formatted GitHub issue links in the release notes.",
|
||||
"",
|
||||
"GitHub issue links must be formatted as plain Markdown links:",
|
||||
"",
|
||||
"```",
|
||||
"- Improved something ([#ISSUE](https://github.com/zed-industries/zed/issues/ISSUE)).",
|
||||
"```",
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,38 +1,12 @@
|
||||
#!/usr/bin/env node --redirect-warnings=/dev/null
|
||||
|
||||
const { execFileSync } = require("child_process");
|
||||
const { GITHUB_ACCESS_TOKEN } = process.env;
|
||||
const GITHUB_URL = "https://github.com";
|
||||
const SKIPPABLE_NOTE_REGEX = /^\s*-?\s*n\/?a\s*/ims;
|
||||
const PULL_REQUEST_WEB_URL = "https://github.com/zed-industries/zed/pull";
|
||||
const PULL_REQUEST_API_URL =
|
||||
"https://api.github.com/repos/zed-industries/zed/pulls";
|
||||
const DIVIDER = "-".repeat(80);
|
||||
let { GITHUB_ACCESS_TOKEN } = process.env;
|
||||
const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im;
|
||||
|
||||
main();
|
||||
|
||||
async function main() {
|
||||
const STAFF_MEMBERS = new Set(
|
||||
(
|
||||
await (
|
||||
await fetch(
|
||||
"https://api.github.com/orgs/zed-industries/teams/staff/members",
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${GITHUB_ACCESS_TOKEN}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
},
|
||||
},
|
||||
)
|
||||
).json()
|
||||
).map(({ login }) => login.toLowerCase()),
|
||||
);
|
||||
|
||||
const isStaffMember = (githubHandle) => {
|
||||
githubHandle = githubHandle.toLowerCase();
|
||||
return STAFF_MEMBERS.has(githubHandle);
|
||||
};
|
||||
|
||||
// Get the last two preview tags
|
||||
const [newTag, oldTag] = execFileSync(
|
||||
"git",
|
||||
@@ -70,62 +44,51 @@ async function main() {
|
||||
|
||||
// Fetch the pull requests from the GitHub API.
|
||||
console.log("Merged Pull requests:");
|
||||
console.log(DIVIDER);
|
||||
for (const pullRequestNumber of newPullRequestNumbers) {
|
||||
const pullRequestApiURL = `${PULL_REQUEST_API_URL}/${pullRequestNumber}`;
|
||||
const webURL = `https://github.com/zed-industries/zed/pull/${pullRequestNumber}`;
|
||||
const apiURL = `https://api.github.com/repos/zed-industries/zed/pulls/${pullRequestNumber}`;
|
||||
|
||||
const response = await fetch(pullRequestApiURL, {
|
||||
const response = await fetch(apiURL, {
|
||||
headers: {
|
||||
Authorization: `token ${GITHUB_ACCESS_TOKEN}`,
|
||||
},
|
||||
});
|
||||
|
||||
// Print the pull request title and URL.
|
||||
const pullRequest = await response.json();
|
||||
const releaseNotesHeader = /^\s*Release Notes:(.+)/ims;
|
||||
|
||||
const releaseNotes = pullRequest.body || "";
|
||||
let contributor =
|
||||
pullRequest.user?.login ?? "Unable to identify contributor";
|
||||
let releaseNotes = pullRequest.body || "";
|
||||
let contributor = pullRequest.user?.login ?? "Unable to identify";
|
||||
const captures = releaseNotesHeader.exec(releaseNotes);
|
||||
let notes = captures ? captures[1] : "MISSING";
|
||||
notes = notes.trim();
|
||||
const isStaff = isStaffMember(contributor);
|
||||
const notes = captures ? captures[1] : "MISSING";
|
||||
const skippableNoteRegex = /^\s*-?\s*n\/?a\s*/ims;
|
||||
|
||||
if (SKIPPABLE_NOTE_REGEX.exec(notes) != null) {
|
||||
if (skippableNoteRegex.exec(notes) != null) {
|
||||
continue;
|
||||
}
|
||||
console.log("*", pullRequest.title);
|
||||
console.log(" PR URL: ", webURL);
|
||||
console.log(" Author: ", contributor);
|
||||
|
||||
const credit = getCreditString(pullRequestNumber, contributor, isStaff);
|
||||
contributor = isStaff ? `${contributor} (staff)` : contributor;
|
||||
// If the pull request contains a 'closes' line, print the closed issue.
|
||||
const fixesMatch = (pullRequest.body || "").match(FIXES_REGEX);
|
||||
if (fixesMatch) {
|
||||
const fixedIssueURL = fixesMatch[2];
|
||||
console.log(" Issue URL: ", fixedIssueURL);
|
||||
}
|
||||
|
||||
console.log(`PR Title: ${pullRequest.title}`);
|
||||
console.log(`Contributor: ${contributor}`);
|
||||
console.log(`Credit: (${credit})`);
|
||||
releaseNotes = notes.trim().split("\n");
|
||||
console.log(" Release Notes:");
|
||||
|
||||
for (const line of releaseNotes) {
|
||||
console.log(` ${line}`);
|
||||
}
|
||||
|
||||
console.log("Release Notes:");
|
||||
console.log();
|
||||
console.log(notes);
|
||||
|
||||
console.log(DIVIDER);
|
||||
}
|
||||
}
|
||||
|
||||
function getCreditString(pullRequestNumber, contributor, isStaff) {
|
||||
let credit = "";
|
||||
|
||||
if (pullRequestNumber) {
|
||||
const pullRequestMarkdownLink = `[#${pullRequestNumber}](${PULL_REQUEST_WEB_URL}/${pullRequestNumber})`;
|
||||
credit += pullRequestMarkdownLink;
|
||||
}
|
||||
|
||||
if (contributor && !isStaff) {
|
||||
const contributorMarkdownLink = `[${contributor}](${GITHUB_URL}/${contributor})`;
|
||||
credit += `; thanks ${contributorMarkdownLink}`;
|
||||
}
|
||||
|
||||
return credit;
|
||||
}
|
||||
|
||||
function getPullRequestNumbers(oldTag, newTag) {
|
||||
const pullRequestNumbers = execFileSync(
|
||||
"git",
|
||||
|
||||
4
script/update_top_ranking_issues/poetry.lock
generated
4
script/update_top_ranking_issues/poetry.lock
generated
@@ -529,5 +529,5 @@ files = [
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "3.12.4"
|
||||
content-hash = "9115a7d7c6e75ab0ec1b338a80a6a7461efe794295b5ee89ca1e17f62fc1a910"
|
||||
python-versions = "3.12.5"
|
||||
content-hash = "3e6aa4dc758eb933f7e2d1a305d1e397b13a960ac4846ef54c5a11b906b77015"
|
||||
|
||||
@@ -8,7 +8,7 @@ readme = "README.md"
|
||||
[tool.poetry.dependencies]
|
||||
mypy = "1.6.0"
|
||||
PyGithub = "1.55"
|
||||
python = "3.12.4"
|
||||
python = "3.12.5"
|
||||
pytz = "2022.1"
|
||||
typer = "0.9.0"
|
||||
types-pytz = "2023.3.1.1"
|
||||
|
||||
Reference in New Issue
Block a user