Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85c2dc909d | ||
|
|
c814b99fcb | ||
|
|
07ccff217a | ||
|
|
8ab52f3491 | ||
|
|
ecf410e57d | ||
|
|
ec0eeaf69d | ||
|
|
376335496d | ||
|
|
4f656cedfa | ||
|
|
0e9ee3cb55 | ||
|
|
bbe764794d | ||
|
|
3882323f79 | ||
|
|
b0b83ef5aa | ||
|
|
7beae757b8 | ||
|
|
a6e99c1c16 | ||
|
|
6d8d2e2989 | ||
|
|
877790a105 | ||
|
|
0c08bbca05 | ||
|
|
ba0b68779d | ||
|
|
45af5e4239 | ||
|
|
01f9b1e9b4 | ||
|
|
635b71c486 | ||
|
|
c4a7552a04 | ||
|
|
918aee550c |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -882,8 +882,7 @@ jobs:
|
||||
auto-release-preview:
|
||||
name: Auto release preview
|
||||
if: |
|
||||
false
|
||||
&& startsWith(github.ref, 'refs/tags/v')
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
|
||||
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64]
|
||||
runs-on:
|
||||
|
||||
20
.github/workflows/community_release_actions.yml
vendored
20
.github/workflows/community_release_actions.yml
vendored
@@ -38,26 +38,6 @@ jobs:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
|
||||
content: ${{ steps.get-content.outputs.string }}
|
||||
|
||||
publish-winget:
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- name: Set Package Name
|
||||
id: set-package-name
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
|
||||
PACKAGE_NAME=ZedIndustries.Zed.Preview
|
||||
else
|
||||
PACKAGE_NAME=ZedIndustries.Zed
|
||||
fi
|
||||
|
||||
echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
|
||||
- uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # v2
|
||||
with:
|
||||
identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
|
||||
max-versions-to-keep: 5
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
||||
send_release_notes_email:
|
||||
if: false && github.repository_owner == 'zed-industries' && !github.event.release.prerelease
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -1336,7 +1336,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "async-tar"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/zed-industries/async-tar?rev=8af312477196311c9ea4097f2a22022f6d609bf6#8af312477196311c9ea4097f2a22022f6d609bf6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a42f905d4f623faf634bbd1e001e84e0efc24694afa64be9ad239bf6ca49e1f8"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"filetime",
|
||||
@@ -21202,7 +21203,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.209.6"
|
||||
version = "0.210.0"
|
||||
dependencies = [
|
||||
"acp_tools",
|
||||
"activity_indicator",
|
||||
|
||||
@@ -457,7 +457,7 @@ async-dispatcher = "0.1"
|
||||
async-fs = "2.1"
|
||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
||||
async-recursion = "1.0.0"
|
||||
async-tar = { git = "https://github.com/zed-industries/async-tar", rev = "8af312477196311c9ea4097f2a22022f6d609bf6" }
|
||||
async-tar = "0.5.0"
|
||||
async-task = "4.7"
|
||||
async-trait = "0.1"
|
||||
async-tungstenite = "0.29.1"
|
||||
|
||||
@@ -1527,7 +1527,6 @@
|
||||
// A value of 45 preserves colorful themes while ensuring legibility.
|
||||
"minimum_contrast": 45
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
// Settings related to running tasks.
|
||||
"tasks": {
|
||||
"variables": {},
|
||||
@@ -1697,9 +1696,7 @@
|
||||
"preferred_line_length": 72
|
||||
},
|
||||
"Go": {
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
},
|
||||
"formatter": [{ "code_action": "source.organizeImports" }, "language_server"],
|
||||
"debuggers": ["Delve"]
|
||||
},
|
||||
"GraphQL": {
|
||||
@@ -1738,7 +1735,7 @@
|
||||
}
|
||||
},
|
||||
"Kotlin": {
|
||||
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."]
|
||||
"language_servers": ["kotlin-language-server", "!kotlin-lsp", "..."]
|
||||
},
|
||||
"LaTeX": {
|
||||
"formatter": "language_server",
|
||||
@@ -1816,11 +1813,10 @@
|
||||
},
|
||||
"SystemVerilog": {
|
||||
"format_on_save": "off",
|
||||
"language_servers": ["!slang", "..."],
|
||||
"use_on_type_format": false
|
||||
},
|
||||
"Vue.js": {
|
||||
"language_servers": ["vue-language-server", "vtsls", "..."],
|
||||
"language_servers": ["vue-language-server", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@ impl ToolCall {
|
||||
location: acp::ToolCallLocation,
|
||||
project: WeakEntity<Project>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Option<ResolvedLocation> {
|
||||
) -> Option<AgentLocation> {
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project
|
||||
@@ -350,14 +350,17 @@ impl ToolCall {
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
Some(ResolvedLocation { buffer, position })
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_locations(
|
||||
&self,
|
||||
project: Entity<Project>,
|
||||
cx: &mut App,
|
||||
) -> Task<Vec<Option<ResolvedLocation>>> {
|
||||
) -> Task<Vec<Option<AgentLocation>>> {
|
||||
let locations = self.locations.clone();
|
||||
project.update(cx, |_, cx| {
|
||||
cx.spawn(async move |project, cx| {
|
||||
@@ -371,23 +374,6 @@ impl ToolCall {
|
||||
}
|
||||
}
|
||||
|
||||
// Separate so we can hold a strong reference to the buffer
|
||||
// for saving on the thread
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
struct ResolvedLocation {
|
||||
buffer: Entity<Buffer>,
|
||||
position: Anchor,
|
||||
}
|
||||
|
||||
impl From<&ResolvedLocation> for AgentLocation {
|
||||
fn from(value: &ResolvedLocation) -> Self {
|
||||
Self {
|
||||
buffer: value.buffer.downgrade(),
|
||||
position: value.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ToolCallStatus {
|
||||
/// The tool call hasn't started running yet, but we start showing it to
|
||||
@@ -1407,46 +1393,35 @@ impl AcpThread {
|
||||
let task = tool_call.resolve_locations(project, cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
let resolved_locations = task.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
let project = this.project.clone();
|
||||
|
||||
for location in resolved_locations.iter().flatten() {
|
||||
this.shared_buffers
|
||||
.insert(location.buffer.clone(), location.buffer.read(cx).snapshot());
|
||||
}
|
||||
let Some((ix, tool_call)) = this.tool_call_mut(&id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(Some(location)) = resolved_locations.last() {
|
||||
project.update(cx, |project, cx| {
|
||||
let should_ignore = if let Some(agent_location) = project
|
||||
.agent_location()
|
||||
.filter(|agent_location| agent_location.buffer == location.buffer)
|
||||
{
|
||||
let snapshot = location.buffer.read(cx).snapshot();
|
||||
let old_position = agent_location.position.to_point(&snapshot);
|
||||
let new_position = location.position.to_point(&snapshot);
|
||||
|
||||
// ignore this so that when we get updates from the edit tool
|
||||
// the position doesn't reset to the startof line
|
||||
old_position.row == new_position.row
|
||||
&& old_position.column > new_position.column
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !should_ignore {
|
||||
project.set_agent_location(Some(location.into()), cx);
|
||||
if let Some(agent_location) = project.agent_location() {
|
||||
let should_ignore = agent_location.buffer == location.buffer
|
||||
&& location
|
||||
.buffer
|
||||
.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let old_position =
|
||||
agent_location.position.to_point(&snapshot);
|
||||
let new_position = location.position.to_point(&snapshot);
|
||||
// ignore this so that when we get updates from the edit tool
|
||||
// the position doesn't reset to the startof line
|
||||
old_position.row == new_position.row
|
||||
&& old_position.column > new_position.column
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
if !should_ignore {
|
||||
project.set_agent_location(Some(location.clone()), cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let resolved_locations = resolved_locations
|
||||
.iter()
|
||||
.map(|l| l.as_ref().map(|l| AgentLocation::from(l)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if tool_call.resolved_locations != resolved_locations {
|
||||
tool_call.resolved_locations = resolved_locations;
|
||||
cx.emit(AcpThreadEvent::EntryUpdated(ix));
|
||||
|
||||
@@ -9,6 +9,7 @@ use futures::io::BufReader;
|
||||
use project::Project;
|
||||
use project::agent_server_store::AgentServerCommand;
|
||||
use serde::Deserialize;
|
||||
use settings::{Settings as _, SettingsLocation};
|
||||
use task::Shell;
|
||||
use util::{ResultExt as _, get_default_system_shell_preferring_bash};
|
||||
|
||||
@@ -22,7 +23,7 @@ use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntit
|
||||
|
||||
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
|
||||
use terminal::TerminalBuilder;
|
||||
use terminal::terminal_settings::{AlternateScroll, CursorShape};
|
||||
use terminal::terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Unsupported version")]
|
||||
@@ -818,13 +819,25 @@ impl acp::Client for ClientDelegate {
|
||||
let mut env = if let Some(dir) = &args.cwd {
|
||||
project
|
||||
.update(&mut self.cx.clone(), |project, cx| {
|
||||
project.directory_environment(&task::Shell::System, dir.clone().into(), cx)
|
||||
let worktree = project.find_worktree(dir.as_path(), cx);
|
||||
let shell = TerminalSettings::get(
|
||||
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: &path,
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.shell
|
||||
.clone();
|
||||
project.directory_environment(&shell, dir.clone().into(), cx)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
// Disables paging for `git` and hopefully other commands
|
||||
env.insert("PAGER".into(), "".into());
|
||||
for var in args.env {
|
||||
env.insert(var.name, var.value);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use anyhow::Result;
|
||||
use editor::{CompletionProvider, Editor, ExcerptId};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{App, Entity, Task, WeakEntity};
|
||||
use language::{Buffer, CodeLabel, HighlightId};
|
||||
use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};
|
||||
use lsp::CompletionContext;
|
||||
use project::lsp_store::{CompletionDocumentation, SymbolLocation};
|
||||
use project::{
|
||||
@@ -673,7 +673,7 @@ impl ContextPickerCompletionProvider {
|
||||
|
||||
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||
let mut label = CodeLabel::default();
|
||||
let mut label = CodeLabelBuilder::default();
|
||||
|
||||
label.push_str(file_name, None);
|
||||
label.push_str(" ", None);
|
||||
@@ -682,9 +682,7 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx:
|
||||
label.push_str(directory, comment_id);
|
||||
}
|
||||
|
||||
label.filter_range = 0..label.text().len();
|
||||
|
||||
label
|
||||
label.build()
|
||||
}
|
||||
|
||||
impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
|
||||
@@ -278,7 +278,7 @@ pub struct AcpThreadView {
|
||||
thread_feedback: ThreadFeedbackState,
|
||||
list_state: ListState,
|
||||
auth_task: Option<Task<()>>,
|
||||
expanded_tool_calls: HashSet<acp::ToolCallId>,
|
||||
collapsed_tool_calls: HashSet<acp::ToolCallId>,
|
||||
expanded_thinking_blocks: HashSet<(usize, usize)>,
|
||||
edits_expanded: bool,
|
||||
plan_expanded: bool,
|
||||
@@ -292,8 +292,6 @@ pub struct AcpThreadView {
|
||||
resume_thread_metadata: Option<DbThreadMetadata>,
|
||||
_cancel_task: Option<Task<()>>,
|
||||
_subscriptions: [Subscription; 5],
|
||||
#[cfg(target_os = "windows")]
|
||||
show_codex_windows_warning: bool,
|
||||
}
|
||||
|
||||
enum ThreadState {
|
||||
@@ -396,10 +394,6 @@ impl AcpThreadView {
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
|
||||
== Some(crate::ExternalAgent::Codex);
|
||||
|
||||
Self {
|
||||
agent: agent.clone(),
|
||||
workspace: workspace.clone(),
|
||||
@@ -425,7 +419,7 @@ impl AcpThreadView {
|
||||
thread_error: None,
|
||||
thread_feedback: Default::default(),
|
||||
auth_task: None,
|
||||
expanded_tool_calls: HashSet::default(),
|
||||
collapsed_tool_calls: HashSet::default(),
|
||||
expanded_thinking_blocks: HashSet::default(),
|
||||
editing_message: None,
|
||||
edits_expanded: false,
|
||||
@@ -442,8 +436,6 @@ impl AcpThreadView {
|
||||
focus_handle: cx.focus_handle(),
|
||||
new_server_version_available: None,
|
||||
resume_thread_metadata: resume_thread,
|
||||
#[cfg(target_os = "windows")]
|
||||
show_codex_windows_warning,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -962,17 +954,17 @@ impl AcpThreadView {
|
||||
) {
|
||||
match &event.view_event {
|
||||
ViewEvent::NewDiff(tool_call_id) => {
|
||||
if AgentSettings::get_global(cx).expand_edit_card {
|
||||
self.expanded_tool_calls.insert(tool_call_id.clone());
|
||||
if !AgentSettings::get_global(cx).expand_edit_card {
|
||||
self.collapsed_tool_calls.insert(tool_call_id.clone());
|
||||
}
|
||||
}
|
||||
ViewEvent::NewTerminal(tool_call_id) => {
|
||||
if AgentSettings::get_global(cx).expand_terminal_card {
|
||||
self.expanded_tool_calls.insert(tool_call_id.clone());
|
||||
if !AgentSettings::get_global(cx).expand_terminal_card {
|
||||
self.collapsed_tool_calls.insert(tool_call_id.clone());
|
||||
}
|
||||
}
|
||||
ViewEvent::TerminalMovedToBackground(tool_call_id) => {
|
||||
self.expanded_tool_calls.remove(tool_call_id);
|
||||
self.collapsed_tool_calls.insert(tool_call_id.clone());
|
||||
}
|
||||
ViewEvent::MessageEditorEvent(_editor, MessageEditorEvent::Focus) => {
|
||||
if let Some(thread) = self.thread()
|
||||
@@ -2127,7 +2119,7 @@ impl AcpThreadView {
|
||||
|
||||
let is_collapsible = !tool_call.content.is_empty() && !needs_confirmation;
|
||||
|
||||
let is_open = needs_confirmation || self.expanded_tool_calls.contains(&tool_call.id);
|
||||
let is_open = needs_confirmation || !self.collapsed_tool_calls.contains(&tool_call.id);
|
||||
|
||||
let tool_output_display =
|
||||
if is_open {
|
||||
@@ -2277,9 +2269,9 @@ impl AcpThreadView {
|
||||
let id = tool_call.id.clone();
|
||||
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
|
||||
if is_open {
|
||||
this.expanded_tool_calls.remove(&id);
|
||||
this.collapsed_tool_calls.insert(id.clone());
|
||||
} else {
|
||||
this.expanded_tool_calls.insert(id.clone());
|
||||
this.collapsed_tool_calls.remove(&id);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
@@ -2481,7 +2473,7 @@ impl AcpThreadView {
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(cx.listener({
|
||||
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
|
||||
this.expanded_tool_calls.remove(&tool_call_id);
|
||||
this.collapsed_tool_calls.insert(tool_call_id.clone());
|
||||
cx.notify();
|
||||
}
|
||||
})),
|
||||
@@ -2759,7 +2751,7 @@ impl AcpThreadView {
|
||||
.map(|path| path.display().to_string())
|
||||
.unwrap_or_else(|| "current directory".to_string());
|
||||
|
||||
let is_expanded = self.expanded_tool_calls.contains(&tool_call.id);
|
||||
let is_expanded = !self.collapsed_tool_calls.contains(&tool_call.id);
|
||||
|
||||
let header = h_flex()
|
||||
.id(header_id)
|
||||
@@ -2894,9 +2886,9 @@ impl AcpThreadView {
|
||||
let id = tool_call.id.clone();
|
||||
move |this, _event, _window, _cx| {
|
||||
if is_expanded {
|
||||
this.expanded_tool_calls.remove(&id);
|
||||
this.collapsed_tool_calls.insert(id.clone());
|
||||
} else {
|
||||
this.expanded_tool_calls.insert(id.clone());
|
||||
this.collapsed_tool_calls.remove(&id);
|
||||
}
|
||||
}
|
||||
})),
|
||||
@@ -5033,49 +5025,6 @@ impl AcpThreadView {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn render_codex_windows_warning(&self, cx: &mut Context<Self>) -> Option<Callout> {
|
||||
if self.show_codex_windows_warning {
|
||||
Some(
|
||||
Callout::new()
|
||||
.icon(IconName::Warning)
|
||||
.severity(Severity::Warning)
|
||||
.title("Codex on Windows")
|
||||
.description(
|
||||
"For best performance, run Codex in Windows Subsystem for Linux (WSL2)",
|
||||
)
|
||||
.actions_slot(
|
||||
Button::new("open-wsl-modal", "Open in WSL")
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(cx.listener({
|
||||
move |_, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::wsl_actions::OpenWsl::default().boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
}
|
||||
})),
|
||||
)
|
||||
.dismiss_action(
|
||||
IconButton::new("dismiss", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(Tooltip::text("Dismiss Warning"))
|
||||
.on_click(cx.listener({
|
||||
move |this, _, _, cx| {
|
||||
this.show_codex_windows_warning = false;
|
||||
cx.notify();
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||
let content = match self.thread_error.as_ref()? {
|
||||
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
|
||||
@@ -5563,16 +5512,6 @@ impl Render for AcpThreadView {
|
||||
_ => this,
|
||||
})
|
||||
.children(self.render_thread_retry_status_callout(window, cx))
|
||||
.children({
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
self.render_codex_windows_warning(cx)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Vec::<Empty>::new()
|
||||
}
|
||||
})
|
||||
.children(self.render_thread_error(window, cx))
|
||||
.when_some(
|
||||
self.new_server_version_available.as_ref().filter(|_| {
|
||||
|
||||
@@ -222,11 +222,12 @@ enum WhichFontSize {
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum AgentType {
|
||||
#[default]
|
||||
NativeAgent,
|
||||
Zed,
|
||||
TextThread,
|
||||
Gemini,
|
||||
ClaudeCode,
|
||||
Codex,
|
||||
NativeAgent,
|
||||
Custom {
|
||||
name: SharedString,
|
||||
command: AgentServerCommand,
|
||||
@@ -236,7 +237,8 @@ pub enum AgentType {
|
||||
impl AgentType {
|
||||
fn label(&self) -> SharedString {
|
||||
match self {
|
||||
Self::NativeAgent | Self::TextThread => "Zed Agent".into(),
|
||||
Self::Zed | Self::TextThread => "Zed Agent".into(),
|
||||
Self::NativeAgent => "Agent 2".into(),
|
||||
Self::Gemini => "Gemini CLI".into(),
|
||||
Self::ClaudeCode => "Claude Code".into(),
|
||||
Self::Codex => "Codex".into(),
|
||||
@@ -246,7 +248,7 @@ impl AgentType {
|
||||
|
||||
fn icon(&self) -> Option<IconName> {
|
||||
match self {
|
||||
Self::NativeAgent | Self::TextThread => None,
|
||||
Self::Zed | Self::NativeAgent | Self::TextThread => None,
|
||||
Self::Gemini => Some(IconName::AiGemini),
|
||||
Self::ClaudeCode => Some(IconName::AiClaude),
|
||||
Self::Codex => Some(IconName::AiOpenAi),
|
||||
@@ -811,7 +813,7 @@ impl AgentPanel {
|
||||
|
||||
const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
struct LastUsedExternalAgent {
|
||||
agent: crate::ExternalAgent,
|
||||
}
|
||||
@@ -852,18 +854,18 @@ impl AgentPanel {
|
||||
.and_then(|value| {
|
||||
serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
|
||||
})
|
||||
.map(|agent| agent.agent)
|
||||
.unwrap_or(ExternalAgent::NativeAgent)
|
||||
.unwrap_or_default()
|
||||
.agent
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let server = ext_agent.server(fs, history);
|
||||
|
||||
if !loading {
|
||||
telemetry::event!("Agent Thread Started", agent = server.telemetry_id());
|
||||
telemetry::event!("Agent Thread Started", agent = ext_agent.name());
|
||||
}
|
||||
|
||||
let server = ext_agent.server(fs, history);
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let selected_agent = ext_agent.into();
|
||||
if this.selected_agent != selected_agent {
|
||||
@@ -1343,6 +1345,15 @@ impl AgentPanel {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match agent {
|
||||
AgentType::Zed => {
|
||||
window.dispatch_action(
|
||||
NewThread {
|
||||
from_thread_id: None,
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
AgentType::TextThread => {
|
||||
window.dispatch_action(NewTextThread.boxed_clone(), cx);
|
||||
}
|
||||
|
||||
@@ -161,9 +161,10 @@ pub struct NewNativeAgentThreadFromSummary {
|
||||
}
|
||||
|
||||
// TODO unify this with AgentType
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExternalAgent {
|
||||
enum ExternalAgent {
|
||||
#[default]
|
||||
Gemini,
|
||||
ClaudeCode,
|
||||
Codex,
|
||||
@@ -183,13 +184,13 @@ fn placeholder_command() -> AgentServerCommand {
|
||||
}
|
||||
|
||||
impl ExternalAgent {
|
||||
pub fn parse_built_in(server: &dyn agent_servers::AgentServer) -> Option<Self> {
|
||||
match server.telemetry_id() {
|
||||
"gemini-cli" => Some(Self::Gemini),
|
||||
"claude-code" => Some(Self::ClaudeCode),
|
||||
"codex" => Some(Self::Codex),
|
||||
"zed" => Some(Self::NativeAgent),
|
||||
_ => None,
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::NativeAgent => "zed",
|
||||
Self::Gemini => "gemini-cli",
|
||||
Self::ClaudeCode => "claude-code",
|
||||
Self::Codex => "codex",
|
||||
Self::Custom { .. } => "custom",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{App, Entity, Task, WeakEntity};
|
||||
use http_client::HttpClientWithUrl;
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, CodeLabel, HighlightId};
|
||||
use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};
|
||||
use lsp::CompletionContext;
|
||||
use project::lsp_store::SymbolLocation;
|
||||
use project::{
|
||||
@@ -686,7 +686,8 @@ impl ContextPickerCompletionProvider {
|
||||
};
|
||||
|
||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||
let mut label = CodeLabel::plain(symbol.name.clone(), None);
|
||||
let mut label = CodeLabelBuilder::default();
|
||||
label.push_str(&symbol.name, None);
|
||||
label.push_str(" ", None);
|
||||
label.push_str(&file_name, comment_id);
|
||||
label.push_str(&format!(" L{}", symbol.range.start.0.row + 1), comment_id);
|
||||
@@ -696,7 +697,7 @@ impl ContextPickerCompletionProvider {
|
||||
Some(Completion {
|
||||
replace_range: source_range.clone(),
|
||||
new_text,
|
||||
label,
|
||||
label: label.build(),
|
||||
documentation: None,
|
||||
source: project::CompletionSource::Custom,
|
||||
icon_path: Some(IconName::Code.path().into()),
|
||||
@@ -729,7 +730,7 @@ impl ContextPickerCompletionProvider {
|
||||
|
||||
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||
let mut label = CodeLabel::default();
|
||||
let mut label = CodeLabelBuilder::default();
|
||||
|
||||
label.push_str(file_name, None);
|
||||
label.push_str(" ", None);
|
||||
@@ -738,9 +739,7 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx:
|
||||
label.push_str(directory, comment_id);
|
||||
}
|
||||
|
||||
label.filter_range = 0..label.text().len();
|
||||
|
||||
label
|
||||
label.build()
|
||||
}
|
||||
|
||||
impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
|
||||
@@ -84,32 +84,10 @@ impl ZedAiOnboarding {
|
||||
self
|
||||
}
|
||||
|
||||
fn render_dismiss_button(&self) -> Option<AnyElement> {
|
||||
self.dismiss_onboarding.as_ref().map(|dismiss_callback| {
|
||||
let callback = dismiss_callback.clone();
|
||||
|
||||
h_flex()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.right_0()
|
||||
.child(
|
||||
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Dismiss"))
|
||||
.on_click(move |_, window, cx| {
|
||||
telemetry::event!("Banner Dismissed", source = "AI Onboarding",);
|
||||
callback(window, cx)
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
}
|
||||
|
||||
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
|
||||
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
|
||||
|
||||
v_flex()
|
||||
.relative()
|
||||
.gap_1()
|
||||
.child(Headline::new("Welcome to Zed AI"))
|
||||
.child(
|
||||
@@ -131,7 +109,6 @@ impl ZedAiOnboarding {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.children(self.render_dismiss_button())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -203,7 +180,27 @@ impl ZedAiOnboarding {
|
||||
)
|
||||
.child(PlanDefinitions.free_plan(is_v2)),
|
||||
)
|
||||
.children(self.render_dismiss_button())
|
||||
.when_some(
|
||||
self.dismiss_onboarding.as_ref(),
|
||||
|this, dismiss_callback| {
|
||||
let callback = dismiss_callback.clone();
|
||||
|
||||
this.child(
|
||||
h_flex().absolute().top_0().right_0().child(
|
||||
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Dismiss"))
|
||||
.on_click(move |_, window, cx| {
|
||||
telemetry::event!(
|
||||
"Banner Dismissed",
|
||||
source = "AI Onboarding",
|
||||
);
|
||||
callback(window, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.mt_2()
|
||||
@@ -248,7 +245,26 @@ impl ZedAiOnboarding {
|
||||
.mb_2(),
|
||||
)
|
||||
.child(PlanDefinitions.pro_trial(is_v2, false))
|
||||
.children(self.render_dismiss_button())
|
||||
.when_some(
|
||||
self.dismiss_onboarding.as_ref(),
|
||||
|this, dismiss_callback| {
|
||||
let callback = dismiss_callback.clone();
|
||||
this.child(
|
||||
h_flex().absolute().top_0().right_0().child(
|
||||
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Dismiss"))
|
||||
.on_click(move |_, window, cx| {
|
||||
telemetry::event!(
|
||||
"Banner Dismissed",
|
||||
source = "AI Onboarding",
|
||||
);
|
||||
callback(window, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -262,7 +278,26 @@ impl ZedAiOnboarding {
|
||||
.mb_2(),
|
||||
)
|
||||
.child(PlanDefinitions.pro_plan(is_v2, false))
|
||||
.children(self.render_dismiss_button())
|
||||
.when_some(
|
||||
self.dismiss_onboarding.as_ref(),
|
||||
|this, dismiss_callback| {
|
||||
let callback = dismiss_callback.clone();
|
||||
this.child(
|
||||
h_flex().absolute().top_0().right_0().child(
|
||||
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Dismiss"))
|
||||
.on_click(move |_, window, cx| {
|
||||
telemetry::event!(
|
||||
"Banner Dismissed",
|
||||
source = "AI Onboarding",
|
||||
);
|
||||
callback(window, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use anyhow::Result;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::{self, BoxStream};
|
||||
use gpui::{App, SharedString, Task, WeakEntity, Window};
|
||||
use language::CodeLabelBuilder;
|
||||
use language::HighlightId;
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||
pub use language_model::Role;
|
||||
@@ -328,15 +329,15 @@ impl SlashCommandLine {
|
||||
}
|
||||
|
||||
pub fn create_label_for_command(command_name: &str, arguments: &[&str], cx: &App) -> CodeLabel {
|
||||
let mut label = CodeLabel::default();
|
||||
let mut label = CodeLabelBuilder::default();
|
||||
label.push_str(command_name, None);
|
||||
label.respan_filter_range(None);
|
||||
label.push_str(" ", None);
|
||||
label.push_str(
|
||||
&arguments.join(" "),
|
||||
cx.theme().syntax().highlight_id("comment").map(HighlightId),
|
||||
);
|
||||
label.filter_range = 0..command_name.len();
|
||||
label
|
||||
label.build()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -7,7 +7,7 @@ use futures::Stream;
|
||||
use futures::channel::mpsc;
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{App, Entity, Task, WeakEntity};
|
||||
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
|
||||
use language::{BufferSnapshot, CodeLabelBuilder, HighlightId, LineEnding, LspAdapterDelegate};
|
||||
use project::{PathMatchCandidateSet, Project};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::stream::StreamExt;
|
||||
@@ -168,7 +168,7 @@ impl SlashCommand for FileSlashCommand {
|
||||
.display(path_style)
|
||||
.to_string();
|
||||
|
||||
let mut label = CodeLabel::default();
|
||||
let mut label = CodeLabelBuilder::default();
|
||||
let file_name = path_match.path.file_name()?;
|
||||
let label_text = if path_match.is_dir {
|
||||
format!("{}/ ", file_name)
|
||||
@@ -178,10 +178,10 @@ impl SlashCommand for FileSlashCommand {
|
||||
|
||||
label.push_str(label_text.as_str(), None);
|
||||
label.push_str(&text, comment_id);
|
||||
label.filter_range = 0..file_name.len();
|
||||
label.respan_filter_range(Some(file_name));
|
||||
|
||||
Some(ArgumentCompletion {
|
||||
label,
|
||||
label: label.build(),
|
||||
new_text: text,
|
||||
after_completion: AfterCompletion::Compose,
|
||||
replace_previous_arguments: false,
|
||||
|
||||
@@ -7,7 +7,7 @@ use collections::{HashMap, HashSet};
|
||||
use editor::Editor;
|
||||
use futures::future::join_all;
|
||||
use gpui::{Task, WeakEntity};
|
||||
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
|
||||
use language::{BufferSnapshot, CodeLabel, CodeLabelBuilder, HighlightId, LspAdapterDelegate};
|
||||
use std::sync::{Arc, atomic::AtomicBool};
|
||||
use ui::{ActiveTheme, App, Window, prelude::*};
|
||||
use util::{ResultExt, paths::PathStyle};
|
||||
@@ -308,10 +308,10 @@ fn create_tab_completion_label(
|
||||
comment_id: Option<HighlightId>,
|
||||
) -> CodeLabel {
|
||||
let (parent_path, file_name) = path_style.split(path);
|
||||
let mut label = CodeLabel::default();
|
||||
let mut label = CodeLabelBuilder::default();
|
||||
label.push_str(file_name, None);
|
||||
label.push_str(" ", None);
|
||||
label.push_str(parent_path.unwrap_or_default(), comment_id);
|
||||
label.filter_range = 0..file_name.len();
|
||||
label
|
||||
label.respan_filter_range(Some(file_name));
|
||||
label.build()
|
||||
}
|
||||
|
||||
@@ -68,10 +68,13 @@ struct Args {
|
||||
#[arg(short, long, overrides_with = "add")]
|
||||
new: bool,
|
||||
/// Sets a custom directory for all user data (e.g., database, extensions, logs).
|
||||
/// This overrides the default platform-specific data directory location.
|
||||
/// On macOS, the default is `~/Library/Application Support/Zed`.
|
||||
/// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/zed`.
|
||||
/// On Windows, the default is `%LOCALAPPDATA%\Zed`.
|
||||
/// This overrides the default platform-specific data directory location:
|
||||
#[cfg_attr(target_os = "macos", doc = "`~/Library/Application Support/Zed`.")]
|
||||
#[cfg_attr(target_os = "windows", doc = "`%LOCALAPPDATA%\\Zed`.")]
|
||||
#[cfg_attr(
|
||||
not(any(target_os = "windows", target_os = "macos")),
|
||||
doc = "`$XDG_DATA_HOME/zed`."
|
||||
)]
|
||||
#[arg(long, value_name = "DIR")]
|
||||
user_data_dir: Option<String>,
|
||||
/// The paths to open in Zed (space-separated).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use chrono::Duration;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
ops::Range,
|
||||
ops::{Add, Range, Sub},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -18,8 +18,8 @@ pub struct PredictEditsRequest {
|
||||
pub excerpt_path: Arc<Path>,
|
||||
/// Within file
|
||||
pub excerpt_range: Range<usize>,
|
||||
/// Within `excerpt`
|
||||
pub cursor_offset: usize,
|
||||
pub excerpt_line_range: Range<Line>,
|
||||
pub cursor_point: Point,
|
||||
/// Within `signatures`
|
||||
pub excerpt_parent: Option<usize>,
|
||||
pub signatures: Vec<Signature>,
|
||||
@@ -47,12 +47,13 @@ pub struct PredictEditsRequest {
|
||||
pub enum PromptFormat {
|
||||
MarkedExcerpt,
|
||||
LabeledSections,
|
||||
NumberedLines,
|
||||
/// Prompt format intended for use via zeta_cli
|
||||
OnlySnippets,
|
||||
}
|
||||
|
||||
impl PromptFormat {
|
||||
pub const DEFAULT: PromptFormat = PromptFormat::LabeledSections;
|
||||
pub const DEFAULT: PromptFormat = PromptFormat::NumberedLines;
|
||||
}
|
||||
|
||||
impl Default for PromptFormat {
|
||||
@@ -73,6 +74,7 @@ impl std::fmt::Display for PromptFormat {
|
||||
PromptFormat::MarkedExcerpt => write!(f, "Marked Excerpt"),
|
||||
PromptFormat::LabeledSections => write!(f, "Labeled Sections"),
|
||||
PromptFormat::OnlySnippets => write!(f, "Only Snippets"),
|
||||
PromptFormat::NumberedLines => write!(f, "Numbered Lines"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +99,7 @@ pub struct Signature {
|
||||
pub parent_index: Option<usize>,
|
||||
/// Range of `text` within the file, possibly truncated according to `text_is_truncated`. The
|
||||
/// file is implicitly the file that contains the descendant declaration or excerpt.
|
||||
pub range: Range<usize>,
|
||||
pub range: Range<Line>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -106,7 +108,7 @@ pub struct ReferencedDeclaration {
|
||||
pub text: String,
|
||||
pub text_is_truncated: bool,
|
||||
/// Range of `text` within file, possibly truncated according to `text_is_truncated`
|
||||
pub range: Range<usize>,
|
||||
pub range: Range<Line>,
|
||||
/// Range within `text`
|
||||
pub signature_range: Range<usize>,
|
||||
/// Index within `signatures`.
|
||||
@@ -169,10 +171,36 @@ pub struct DebugInfo {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Edit {
|
||||
pub path: Arc<Path>,
|
||||
pub range: Range<usize>,
|
||||
pub range: Range<Line>,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
|
||||
*value == T::default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct Point {
|
||||
pub line: Line,
|
||||
pub column: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
|
||||
#[serde(transparent)]
|
||||
pub struct Line(pub u32);
|
||||
|
||||
impl Add for Line {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Line {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//! Zeta2 prompt planning and generation code shared with cloud.
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use cloud_llm_client::predict_edits_v3::{self, Event, PromptFormat, ReferencedDeclaration};
|
||||
use cloud_llm_client::predict_edits_v3::{
|
||||
self, Event, Line, Point, PromptFormat, ReferencedDeclaration,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use ordered_float::OrderedFloat;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
@@ -43,6 +45,42 @@ const LABELED_SECTIONS_SYSTEM_PROMPT: &str = indoc! {r#"
|
||||
}
|
||||
"#};
|
||||
|
||||
const NUMBERED_LINES_SYSTEM_PROMPT: &str = indoc! {r#"
|
||||
# Instructions
|
||||
|
||||
You are a code completion assistant helping a programmer finish their work. Your task is to:
|
||||
|
||||
1. Analyze the edit history to understand what the programmer is trying to achieve
|
||||
2. Identify any incomplete refactoring or changes that need to be finished
|
||||
3. Make the remaining edits that a human programmer would logically make next
|
||||
4. Apply systematic changes consistently across the entire codebase - if you see a pattern starting, complete it everywhere.
|
||||
|
||||
Focus on:
|
||||
- Understanding the intent behind the changes (e.g., improving error handling, refactoring APIs, fixing bugs)
|
||||
- Completing any partially-applied changes across the codebase
|
||||
- Ensuring consistency with the programming style and patterns already established
|
||||
- Making edits that maintain or improve code quality
|
||||
- If the programmer started refactoring one instance of a pattern, find and update ALL similar instances
|
||||
- Don't write a lot of code if you're not sure what to do
|
||||
|
||||
Rules:
|
||||
- Do not just mechanically apply patterns - reason about what changes make sense given the context and the programmer's apparent goals.
|
||||
- Do not just fix syntax errors - look for the broader refactoring pattern and apply it systematically throughout the code.
|
||||
- Write the edits in the unified diff format as shown in the example.
|
||||
|
||||
# Example output:
|
||||
|
||||
```
|
||||
--- a/distill-claude/tmp-outs/edits_history.txt
|
||||
+++ b/distill-claude/tmp-outs/edits_history.txt
|
||||
@@ -1,3 +1,3 @@
|
||||
-
|
||||
-
|
||||
-import sys
|
||||
+import json
|
||||
```
|
||||
"#};
|
||||
|
||||
pub struct PlannedPrompt<'a> {
|
||||
request: &'a predict_edits_v3::PredictEditsRequest,
|
||||
/// Snippets to include in the prompt. These may overlap - they are merged / deduplicated in
|
||||
@@ -55,6 +93,7 @@ pub fn system_prompt(format: PromptFormat) -> &'static str {
|
||||
match format {
|
||||
PromptFormat::MarkedExcerpt => MARKED_EXCERPT_SYSTEM_PROMPT,
|
||||
PromptFormat::LabeledSections => LABELED_SECTIONS_SYSTEM_PROMPT,
|
||||
PromptFormat::NumberedLines => NUMBERED_LINES_SYSTEM_PROMPT,
|
||||
// only intended for use via zeta_cli
|
||||
PromptFormat::OnlySnippets => "",
|
||||
}
|
||||
@@ -63,7 +102,7 @@ pub fn system_prompt(format: PromptFormat) -> &'static str {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlannedSnippet<'a> {
|
||||
path: Arc<Path>,
|
||||
range: Range<usize>,
|
||||
range: Range<Line>,
|
||||
text: &'a str,
|
||||
// TODO: Indicate this in the output
|
||||
#[allow(dead_code)]
|
||||
@@ -79,7 +118,7 @@ pub enum DeclarationStyle {
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct SectionLabels {
|
||||
pub excerpt_index: usize,
|
||||
pub section_ranges: Vec<(Arc<Path>, Range<usize>)>,
|
||||
pub section_ranges: Vec<(Arc<Path>, Range<Line>)>,
|
||||
}
|
||||
|
||||
impl<'a> PlannedPrompt<'a> {
|
||||
@@ -196,10 +235,24 @@ impl<'a> PlannedPrompt<'a> {
|
||||
declaration.text.len()
|
||||
));
|
||||
};
|
||||
let signature_start_line = declaration.range.start
|
||||
+ Line(
|
||||
declaration.text[..declaration.signature_range.start]
|
||||
.lines()
|
||||
.count() as u32,
|
||||
);
|
||||
let signature_end_line = signature_start_line
|
||||
+ Line(
|
||||
declaration.text
|
||||
[declaration.signature_range.start..declaration.signature_range.end]
|
||||
.lines()
|
||||
.count() as u32,
|
||||
);
|
||||
let range = signature_start_line..signature_end_line;
|
||||
|
||||
PlannedSnippet {
|
||||
path: declaration.path.clone(),
|
||||
range: (declaration.signature_range.start + declaration.range.start)
|
||||
..(declaration.signature_range.end + declaration.range.start),
|
||||
range,
|
||||
text,
|
||||
text_is_truncated: declaration.text_is_truncated,
|
||||
}
|
||||
@@ -318,7 +371,7 @@ impl<'a> PlannedPrompt<'a> {
|
||||
}
|
||||
let excerpt_snippet = PlannedSnippet {
|
||||
path: self.request.excerpt_path.clone(),
|
||||
range: self.request.excerpt_range.clone(),
|
||||
range: self.request.excerpt_line_range.clone(),
|
||||
text: &self.request.excerpt,
|
||||
text_is_truncated: false,
|
||||
};
|
||||
@@ -328,32 +381,33 @@ impl<'a> PlannedPrompt<'a> {
|
||||
let mut excerpt_file_insertions = match self.request.prompt_format {
|
||||
PromptFormat::MarkedExcerpt => vec![
|
||||
(
|
||||
self.request.excerpt_range.start,
|
||||
Point {
|
||||
line: self.request.excerpt_line_range.start,
|
||||
column: 0,
|
||||
},
|
||||
EDITABLE_REGION_START_MARKER_WITH_NEWLINE,
|
||||
),
|
||||
(self.request.cursor_point, CURSOR_MARKER),
|
||||
(
|
||||
self.request.excerpt_range.start + self.request.cursor_offset,
|
||||
CURSOR_MARKER,
|
||||
),
|
||||
(
|
||||
self.request
|
||||
.excerpt_range
|
||||
.end
|
||||
.saturating_sub(0)
|
||||
.max(self.request.excerpt_range.start),
|
||||
Point {
|
||||
line: self.request.excerpt_line_range.end,
|
||||
column: 0,
|
||||
},
|
||||
EDITABLE_REGION_END_MARKER_WITH_NEWLINE,
|
||||
),
|
||||
],
|
||||
PromptFormat::LabeledSections => vec![(
|
||||
self.request.excerpt_range.start + self.request.cursor_offset,
|
||||
CURSOR_MARKER,
|
||||
)],
|
||||
PromptFormat::LabeledSections => vec![(self.request.cursor_point, CURSOR_MARKER)],
|
||||
PromptFormat::NumberedLines => vec![(self.request.cursor_point, CURSOR_MARKER)],
|
||||
PromptFormat::OnlySnippets => vec![],
|
||||
};
|
||||
|
||||
let mut prompt = String::new();
|
||||
prompt.push_str("## User Edits\n\n");
|
||||
Self::push_events(&mut prompt, &self.request.events);
|
||||
if self.request.events.is_empty() {
|
||||
prompt.push_str("No edits yet.\n");
|
||||
} else {
|
||||
Self::push_events(&mut prompt, &self.request.events);
|
||||
}
|
||||
|
||||
prompt.push_str("\n## Code\n\n");
|
||||
let section_labels =
|
||||
@@ -391,13 +445,17 @@ impl<'a> PlannedPrompt<'a> {
|
||||
if *predicted {
|
||||
writeln!(
|
||||
output,
|
||||
"User accepted prediction {:?}:\n```diff\n{}\n```\n",
|
||||
"User accepted prediction {:?}:\n`````diff\n{}\n`````\n",
|
||||
path, diff
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(output, "User edited {:?}:\n```diff\n{}\n```\n", path, diff)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
"User edited {:?}:\n`````diff\n{}\n`````\n",
|
||||
path, diff
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,7 +465,7 @@ impl<'a> PlannedPrompt<'a> {
|
||||
fn push_file_snippets(
|
||||
&self,
|
||||
output: &mut String,
|
||||
excerpt_file_insertions: &mut Vec<(usize, &'static str)>,
|
||||
excerpt_file_insertions: &mut Vec<(Point, &'static str)>,
|
||||
file_snippets: Vec<(&'a Path, Vec<&'a PlannedSnippet>, bool)>,
|
||||
) -> Result<SectionLabels> {
|
||||
let mut section_ranges = Vec::new();
|
||||
@@ -417,15 +475,13 @@ impl<'a> PlannedPrompt<'a> {
|
||||
snippets.sort_by_key(|s| (s.range.start, Reverse(s.range.end)));
|
||||
|
||||
// TODO: What if the snippets get expanded too large to be editable?
|
||||
let mut current_snippet: Option<(&PlannedSnippet, Range<usize>)> = None;
|
||||
let mut disjoint_snippets: Vec<(&PlannedSnippet, Range<usize>)> = Vec::new();
|
||||
let mut current_snippet: Option<(&PlannedSnippet, Range<Line>)> = None;
|
||||
let mut disjoint_snippets: Vec<(&PlannedSnippet, Range<Line>)> = Vec::new();
|
||||
for snippet in snippets {
|
||||
if let Some((_, current_snippet_range)) = current_snippet.as_mut()
|
||||
&& snippet.range.start < current_snippet_range.end
|
||||
&& snippet.range.start <= current_snippet_range.end
|
||||
{
|
||||
if snippet.range.end > current_snippet_range.end {
|
||||
current_snippet_range.end = snippet.range.end;
|
||||
}
|
||||
current_snippet_range.end = current_snippet_range.end.max(snippet.range.end);
|
||||
continue;
|
||||
}
|
||||
if let Some(current_snippet) = current_snippet.take() {
|
||||
@@ -437,21 +493,24 @@ impl<'a> PlannedPrompt<'a> {
|
||||
disjoint_snippets.push(current_snippet);
|
||||
}
|
||||
|
||||
writeln!(output, "```{}", file_path.display()).ok();
|
||||
// TODO: remove filename=?
|
||||
writeln!(output, "`````filename={}", file_path.display()).ok();
|
||||
let mut skipped_last_snippet = false;
|
||||
for (snippet, range) in disjoint_snippets {
|
||||
let section_index = section_ranges.len();
|
||||
|
||||
match self.request.prompt_format {
|
||||
PromptFormat::MarkedExcerpt | PromptFormat::OnlySnippets => {
|
||||
if range.start > 0 && !skipped_last_snippet {
|
||||
PromptFormat::MarkedExcerpt
|
||||
| PromptFormat::OnlySnippets
|
||||
| PromptFormat::NumberedLines => {
|
||||
if range.start.0 > 0 && !skipped_last_snippet {
|
||||
output.push_str("…\n");
|
||||
}
|
||||
}
|
||||
PromptFormat::LabeledSections => {
|
||||
if is_excerpt_file
|
||||
&& range.start <= self.request.excerpt_range.start
|
||||
&& range.end >= self.request.excerpt_range.end
|
||||
&& range.start <= self.request.excerpt_line_range.start
|
||||
&& range.end >= self.request.excerpt_line_range.end
|
||||
{
|
||||
writeln!(output, "<|current_section|>").ok();
|
||||
} else {
|
||||
@@ -460,46 +519,83 @@ impl<'a> PlannedPrompt<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let push_full_snippet = |output: &mut String| {
|
||||
if self.request.prompt_format == PromptFormat::NumberedLines {
|
||||
for (i, line) in snippet.text.lines().enumerate() {
|
||||
writeln!(output, "{}|{}", i as u32 + range.start.0 + 1, line)?;
|
||||
}
|
||||
} else {
|
||||
output.push_str(&snippet.text);
|
||||
}
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
if is_excerpt_file {
|
||||
if self.request.prompt_format == PromptFormat::OnlySnippets {
|
||||
if range.start >= self.request.excerpt_range.start
|
||||
&& range.end <= self.request.excerpt_range.end
|
||||
if range.start >= self.request.excerpt_line_range.start
|
||||
&& range.end <= self.request.excerpt_line_range.end
|
||||
{
|
||||
skipped_last_snippet = true;
|
||||
} else {
|
||||
skipped_last_snippet = false;
|
||||
output.push_str(snippet.text);
|
||||
}
|
||||
} else {
|
||||
let mut last_offset = range.start;
|
||||
let mut i = 0;
|
||||
while i < excerpt_file_insertions.len() {
|
||||
let (offset, insertion) = &excerpt_file_insertions[i];
|
||||
let found = *offset >= range.start && *offset <= range.end;
|
||||
} else if !excerpt_file_insertions.is_empty() {
|
||||
let lines = snippet.text.lines().collect::<Vec<_>>();
|
||||
let push_line = |output: &mut String, line_ix: usize| {
|
||||
if self.request.prompt_format == PromptFormat::NumberedLines {
|
||||
write!(output, "{}|", line_ix as u32 + range.start.0 + 1)?;
|
||||
}
|
||||
anyhow::Ok(writeln!(output, "{}", lines[line_ix])?)
|
||||
};
|
||||
let mut last_line_ix = 0;
|
||||
let mut insertion_ix = 0;
|
||||
while insertion_ix < excerpt_file_insertions.len() {
|
||||
let (point, insertion) = &excerpt_file_insertions[insertion_ix];
|
||||
let found = point.line >= range.start && point.line <= range.end;
|
||||
if found {
|
||||
excerpt_index = Some(section_index);
|
||||
output.push_str(
|
||||
&snippet.text[last_offset - range.start..offset - range.start],
|
||||
);
|
||||
output.push_str(insertion);
|
||||
last_offset = *offset;
|
||||
excerpt_file_insertions.remove(i);
|
||||
let insertion_line_ix = (point.line.0 - range.start.0) as usize;
|
||||
for line_ix in last_line_ix..insertion_line_ix {
|
||||
push_line(output, line_ix)?;
|
||||
}
|
||||
if let Some(next_line) = lines.get(insertion_line_ix) {
|
||||
if self.request.prompt_format == PromptFormat::NumberedLines {
|
||||
write!(
|
||||
output,
|
||||
"{}|",
|
||||
insertion_line_ix as u32 + range.start.0 + 1
|
||||
)?
|
||||
}
|
||||
output.push_str(&next_line[..point.column as usize]);
|
||||
output.push_str(insertion);
|
||||
writeln!(output, "{}", &next_line[point.column as usize..])?;
|
||||
} else {
|
||||
writeln!(output, "{}", insertion)?;
|
||||
}
|
||||
last_line_ix = insertion_line_ix + 1;
|
||||
excerpt_file_insertions.remove(insertion_ix);
|
||||
continue;
|
||||
}
|
||||
i += 1;
|
||||
insertion_ix += 1;
|
||||
}
|
||||
skipped_last_snippet = false;
|
||||
output.push_str(&snippet.text[last_offset - range.start..]);
|
||||
for line_ix in last_line_ix..lines.len() {
|
||||
push_line(output, line_ix)?;
|
||||
}
|
||||
} else {
|
||||
skipped_last_snippet = false;
|
||||
push_full_snippet(output)?;
|
||||
}
|
||||
} else {
|
||||
skipped_last_snippet = false;
|
||||
output.push_str(snippet.text);
|
||||
push_full_snippet(output)?;
|
||||
}
|
||||
|
||||
section_ranges.push((snippet.path.clone(), range));
|
||||
}
|
||||
|
||||
output.push_str("```\n\n");
|
||||
output.push_str("`````\n\n");
|
||||
}
|
||||
|
||||
Ok(SectionLabels {
|
||||
|
||||
@@ -30,9 +30,9 @@ impl fmt::Display for ZedVersion {
|
||||
|
||||
impl ZedVersion {
|
||||
pub fn can_collaborate(&self) -> bool {
|
||||
// v0.198.4 is the first version where we no longer connect to Collab automatically.
|
||||
// We reject any clients older than that to prevent them from connecting to Collab just for authentication.
|
||||
if self.0 < SemanticVersion::new(0, 198, 4) {
|
||||
// v0.204.1 was the first version after the auto-update bug.
|
||||
// We reject any clients older than that to hope we can persuade them to upgrade.
|
||||
if self.0 < SemanticVersion::new(0, 204, 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -669,11 +669,7 @@ impl ConsoleQueryBarCompletionProvider {
|
||||
&snapshot,
|
||||
),
|
||||
new_text: string_match.string.clone(),
|
||||
label: CodeLabel {
|
||||
filter_range: 0..string_match.string.len(),
|
||||
text: string_match.string.clone(),
|
||||
runs: Vec::new(),
|
||||
},
|
||||
label: CodeLabel::plain(string_match.string.clone(), None),
|
||||
icon_path: None,
|
||||
documentation: Some(CompletionDocumentation::MultiLineMarkdown(
|
||||
variable_value.into(),
|
||||
@@ -782,11 +778,7 @@ impl ConsoleQueryBarCompletionProvider {
|
||||
&snapshot,
|
||||
),
|
||||
new_text,
|
||||
label: CodeLabel {
|
||||
filter_range: 0..completion.label.len(),
|
||||
text: completion.label,
|
||||
runs: Vec::new(),
|
||||
},
|
||||
label: CodeLabel::plain(completion.label, None),
|
||||
icon_path: None,
|
||||
documentation: completion.detail.map(|detail| {
|
||||
CompletionDocumentation::MultiLineMarkdown(detail.into())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use cloud_llm_client::predict_edits_v3::{self, Line};
|
||||
use language::{Language, LanguageId};
|
||||
use project::ProjectEntryId;
|
||||
use std::ops::Range;
|
||||
@@ -91,6 +92,18 @@ impl Declaration {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn item_line_range(&self) -> Range<Line> {
|
||||
match self {
|
||||
Declaration::File { declaration, .. } => declaration.item_line_range.clone(),
|
||||
Declaration::Buffer {
|
||||
declaration, rope, ..
|
||||
} => {
|
||||
Line(rope.offset_to_point(declaration.item_range.start).row)
|
||||
..Line(rope.offset_to_point(declaration.item_range.end).row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn item_text(&self) -> (Cow<'_, str>, bool) {
|
||||
match self {
|
||||
Declaration::File { declaration, .. } => (
|
||||
@@ -130,6 +143,18 @@ impl Declaration {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signature_line_range(&self) -> Range<Line> {
|
||||
match self {
|
||||
Declaration::File { declaration, .. } => declaration.signature_line_range.clone(),
|
||||
Declaration::Buffer {
|
||||
declaration, rope, ..
|
||||
} => {
|
||||
Line(rope.offset_to_point(declaration.signature_range.start).row)
|
||||
..Line(rope.offset_to_point(declaration.signature_range.end).row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signature_range_in_item_text(&self) -> Range<usize> {
|
||||
let signature_range = self.signature_range();
|
||||
let item_range = self.item_range();
|
||||
@@ -142,7 +167,7 @@ fn expand_range_to_line_boundaries_and_truncate(
|
||||
range: &Range<usize>,
|
||||
limit: usize,
|
||||
rope: &Rope,
|
||||
) -> (Range<usize>, bool) {
|
||||
) -> (Range<usize>, Range<predict_edits_v3::Line>, bool) {
|
||||
let mut point_range = rope.offset_to_point(range.start)..rope.offset_to_point(range.end);
|
||||
point_range.start.column = 0;
|
||||
point_range.end.row += 1;
|
||||
@@ -155,7 +180,10 @@ fn expand_range_to_line_boundaries_and_truncate(
|
||||
item_range.end = item_range.start + limit;
|
||||
}
|
||||
item_range.end = rope.clip_offset(item_range.end, Bias::Left);
|
||||
(item_range, is_truncated)
|
||||
|
||||
let line_range =
|
||||
predict_edits_v3::Line(point_range.start.row)..predict_edits_v3::Line(point_range.end.row);
|
||||
(item_range, line_range, is_truncated)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -164,25 +192,30 @@ pub struct FileDeclaration {
|
||||
pub identifier: Identifier,
|
||||
/// offset range of the declaration in the file, expanded to line boundaries and truncated
|
||||
pub item_range: Range<usize>,
|
||||
/// line range of the declaration in the file, potentially truncated
|
||||
pub item_line_range: Range<predict_edits_v3::Line>,
|
||||
/// text of `item_range`
|
||||
pub text: Arc<str>,
|
||||
/// whether `text` was truncated
|
||||
pub text_is_truncated: bool,
|
||||
/// offset range of the signature in the file, expanded to line boundaries and truncated
|
||||
pub signature_range: Range<usize>,
|
||||
/// line range of the signature in the file, truncated
|
||||
pub signature_line_range: Range<Line>,
|
||||
/// whether `signature` was truncated
|
||||
pub signature_is_truncated: bool,
|
||||
}
|
||||
|
||||
impl FileDeclaration {
|
||||
pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> FileDeclaration {
|
||||
let (item_range_in_file, text_is_truncated) = expand_range_to_line_boundaries_and_truncate(
|
||||
&declaration.item_range,
|
||||
ITEM_TEXT_TRUNCATION_LENGTH,
|
||||
rope,
|
||||
);
|
||||
let (item_range_in_file, item_line_range_in_file, text_is_truncated) =
|
||||
expand_range_to_line_boundaries_and_truncate(
|
||||
&declaration.item_range,
|
||||
ITEM_TEXT_TRUNCATION_LENGTH,
|
||||
rope,
|
||||
);
|
||||
|
||||
let (mut signature_range_in_file, mut signature_is_truncated) =
|
||||
let (mut signature_range_in_file, signature_line_range, mut signature_is_truncated) =
|
||||
expand_range_to_line_boundaries_and_truncate(
|
||||
&declaration.signature_range,
|
||||
ITEM_TEXT_TRUNCATION_LENGTH,
|
||||
@@ -202,6 +235,7 @@ impl FileDeclaration {
|
||||
parent: None,
|
||||
identifier: declaration.identifier,
|
||||
signature_range: signature_range_in_file,
|
||||
signature_line_range,
|
||||
signature_is_truncated,
|
||||
text: rope
|
||||
.chunks_in_range(item_range_in_file.clone())
|
||||
@@ -209,6 +243,7 @@ impl FileDeclaration {
|
||||
.into(),
|
||||
text_is_truncated,
|
||||
item_range: item_range_in_file,
|
||||
item_line_range: item_line_range_in_file,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,12 +260,13 @@ pub struct BufferDeclaration {
|
||||
|
||||
impl BufferDeclaration {
|
||||
pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> Self {
|
||||
let (item_range, item_range_is_truncated) = expand_range_to_line_boundaries_and_truncate(
|
||||
&declaration.item_range,
|
||||
ITEM_TEXT_TRUNCATION_LENGTH,
|
||||
rope,
|
||||
);
|
||||
let (signature_range, signature_range_is_truncated) =
|
||||
let (item_range, _item_line_range, item_range_is_truncated) =
|
||||
expand_range_to_line_boundaries_and_truncate(
|
||||
&declaration.item_range,
|
||||
ITEM_TEXT_TRUNCATION_LENGTH,
|
||||
rope,
|
||||
);
|
||||
let (signature_range, _signature_line_range, signature_range_is_truncated) =
|
||||
expand_range_to_line_boundaries_and_truncate(
|
||||
&declaration.signature_range,
|
||||
ITEM_TEXT_TRUNCATION_LENGTH,
|
||||
|
||||
@@ -9,6 +9,7 @@ pub mod text_similarity;
|
||||
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use cloud_llm_client::predict_edits_v3;
|
||||
use collections::HashMap;
|
||||
use gpui::{App, AppContext as _, Entity, Task};
|
||||
use language::BufferSnapshot;
|
||||
@@ -21,6 +22,8 @@ pub use imports::*;
|
||||
pub use reference::*;
|
||||
pub use syntax_index::*;
|
||||
|
||||
pub use predict_edits_v3::Line;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct EditPredictionContextOptions {
|
||||
pub use_imports: bool,
|
||||
@@ -32,7 +35,7 @@ pub struct EditPredictionContextOptions {
|
||||
pub struct EditPredictionContext {
|
||||
pub excerpt: EditPredictionExcerpt,
|
||||
pub excerpt_text: EditPredictionExcerptText,
|
||||
pub cursor_offset_in_excerpt: usize,
|
||||
pub cursor_point: Point,
|
||||
pub declarations: Vec<ScoredDeclaration>,
|
||||
}
|
||||
|
||||
@@ -124,8 +127,6 @@ impl EditPredictionContext {
|
||||
);
|
||||
|
||||
let cursor_offset_in_file = cursor_point.to_offset(buffer);
|
||||
// TODO fix this to not need saturating_sub
|
||||
let cursor_offset_in_excerpt = cursor_offset_in_file.saturating_sub(excerpt.range.start);
|
||||
|
||||
let declarations = if let Some(index_state) = index_state {
|
||||
let references = get_references(&excerpt, &excerpt_text, buffer);
|
||||
@@ -148,7 +149,7 @@ impl EditPredictionContext {
|
||||
Some(Self {
|
||||
excerpt,
|
||||
excerpt_text,
|
||||
cursor_offset_in_excerpt,
|
||||
cursor_point,
|
||||
declarations,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use text::{Point, ToOffset as _, ToPoint as _};
|
||||
use tree_sitter::{Node, TreeCursor};
|
||||
use util::RangeExt;
|
||||
|
||||
use crate::{BufferDeclaration, declaration::DeclarationId, syntax_index::SyntaxIndexState};
|
||||
use crate::{BufferDeclaration, Line, declaration::DeclarationId, syntax_index::SyntaxIndexState};
|
||||
|
||||
// TODO:
|
||||
//
|
||||
@@ -35,6 +35,7 @@ pub struct EditPredictionExcerptOptions {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EditPredictionExcerpt {
|
||||
pub range: Range<usize>,
|
||||
pub line_range: Range<Line>,
|
||||
pub parent_declarations: Vec<(DeclarationId, Range<usize>)>,
|
||||
pub size: usize,
|
||||
}
|
||||
@@ -86,12 +87,19 @@ impl EditPredictionExcerpt {
|
||||
buffer.len(),
|
||||
options.max_bytes
|
||||
);
|
||||
return Some(EditPredictionExcerpt::new(0..buffer.len(), Vec::new()));
|
||||
let offset_range = 0..buffer.len();
|
||||
let line_range = Line(0)..Line(buffer.max_point().row);
|
||||
return Some(EditPredictionExcerpt::new(
|
||||
offset_range,
|
||||
line_range,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
|
||||
let query_offset = query_point.to_offset(buffer);
|
||||
let query_range = Point::new(query_point.row, 0).to_offset(buffer)
|
||||
..Point::new(query_point.row + 1, 0).to_offset(buffer);
|
||||
let query_line_range = query_point.row..query_point.row + 1;
|
||||
let query_range = Point::new(query_line_range.start, 0).to_offset(buffer)
|
||||
..Point::new(query_line_range.end, 0).to_offset(buffer);
|
||||
if query_range.len() >= options.max_bytes {
|
||||
return None;
|
||||
}
|
||||
@@ -107,6 +115,7 @@ impl EditPredictionExcerpt {
|
||||
let excerpt_selector = ExcerptSelector {
|
||||
query_offset,
|
||||
query_range,
|
||||
query_line_range: Line(query_line_range.start)..Line(query_line_range.end),
|
||||
parent_declarations: &parent_declarations,
|
||||
buffer,
|
||||
options,
|
||||
@@ -130,7 +139,11 @@ impl EditPredictionExcerpt {
|
||||
excerpt_selector.select_lines()
|
||||
}
|
||||
|
||||
fn new(range: Range<usize>, parent_declarations: Vec<(DeclarationId, Range<usize>)>) -> Self {
|
||||
fn new(
|
||||
range: Range<usize>,
|
||||
line_range: Range<Line>,
|
||||
parent_declarations: Vec<(DeclarationId, Range<usize>)>,
|
||||
) -> Self {
|
||||
let size = range.len()
|
||||
+ parent_declarations
|
||||
.iter()
|
||||
@@ -140,10 +153,11 @@ impl EditPredictionExcerpt {
|
||||
range,
|
||||
parent_declarations,
|
||||
size,
|
||||
line_range,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_expanded_range(&self, new_range: Range<usize>) -> Self {
|
||||
fn with_expanded_range(&self, new_range: Range<usize>, new_line_range: Range<Line>) -> Self {
|
||||
if !new_range.contains_inclusive(&self.range) {
|
||||
// this is an issue because parent_signature_ranges may be incorrect
|
||||
log::error!("bug: with_expanded_range called with disjoint range");
|
||||
@@ -155,7 +169,7 @@ impl EditPredictionExcerpt {
|
||||
}
|
||||
parent_declarations.push((*declaration_id, range.clone()));
|
||||
}
|
||||
Self::new(new_range, parent_declarations)
|
||||
Self::new(new_range, new_line_range, parent_declarations)
|
||||
}
|
||||
|
||||
fn parent_signatures_size(&self) -> usize {
|
||||
@@ -166,6 +180,7 @@ impl EditPredictionExcerpt {
|
||||
struct ExcerptSelector<'a> {
|
||||
query_offset: usize,
|
||||
query_range: Range<usize>,
|
||||
query_line_range: Range<Line>,
|
||||
parent_declarations: &'a [(DeclarationId, &'a BufferDeclaration)],
|
||||
buffer: &'a BufferSnapshot,
|
||||
options: &'a EditPredictionExcerptOptions,
|
||||
@@ -178,10 +193,13 @@ impl<'a> ExcerptSelector<'a> {
|
||||
let mut cursor = selected_layer_root.walk();
|
||||
|
||||
loop {
|
||||
let excerpt_range = node_line_start(cursor.node()).to_offset(&self.buffer)
|
||||
..node_line_end(cursor.node()).to_offset(&self.buffer);
|
||||
let line_start = node_line_start(cursor.node());
|
||||
let line_end = node_line_end(cursor.node());
|
||||
let line_range = Line(line_start.row)..Line(line_end.row);
|
||||
let excerpt_range =
|
||||
line_start.to_offset(&self.buffer)..line_end.to_offset(&self.buffer);
|
||||
if excerpt_range.contains_inclusive(&self.query_range) {
|
||||
let excerpt = self.make_excerpt(excerpt_range);
|
||||
let excerpt = self.make_excerpt(excerpt_range, line_range);
|
||||
if excerpt.size <= self.options.max_bytes {
|
||||
return Some(self.expand_to_siblings(&mut cursor, excerpt));
|
||||
}
|
||||
@@ -272,9 +290,13 @@ impl<'a> ExcerptSelector<'a> {
|
||||
|
||||
let mut forward = None;
|
||||
while !forward_done {
|
||||
let new_end = node_line_end(forward_cursor.node()).to_offset(&self.buffer);
|
||||
let new_end_point = node_line_end(forward_cursor.node());
|
||||
let new_end = new_end_point.to_offset(&self.buffer);
|
||||
if new_end > excerpt.range.end {
|
||||
let new_excerpt = excerpt.with_expanded_range(excerpt.range.start..new_end);
|
||||
let new_excerpt = excerpt.with_expanded_range(
|
||||
excerpt.range.start..new_end,
|
||||
excerpt.line_range.start..Line(new_end_point.row),
|
||||
);
|
||||
if new_excerpt.size <= self.options.max_bytes {
|
||||
forward = Some(new_excerpt);
|
||||
break;
|
||||
@@ -289,9 +311,13 @@ impl<'a> ExcerptSelector<'a> {
|
||||
|
||||
let mut backward = None;
|
||||
while !backward_done {
|
||||
let new_start = node_line_start(backward_cursor.node()).to_offset(&self.buffer);
|
||||
let new_start_point = node_line_start(backward_cursor.node());
|
||||
let new_start = new_start_point.to_offset(&self.buffer);
|
||||
if new_start < excerpt.range.start {
|
||||
let new_excerpt = excerpt.with_expanded_range(new_start..excerpt.range.end);
|
||||
let new_excerpt = excerpt.with_expanded_range(
|
||||
new_start..excerpt.range.end,
|
||||
Line(new_start_point.row)..excerpt.line_range.end,
|
||||
);
|
||||
if new_excerpt.size <= self.options.max_bytes {
|
||||
backward = Some(new_excerpt);
|
||||
break;
|
||||
@@ -339,7 +365,7 @@ impl<'a> ExcerptSelector<'a> {
|
||||
|
||||
fn select_lines(&self) -> Option<EditPredictionExcerpt> {
|
||||
// early return if line containing query_offset is already too large
|
||||
let excerpt = self.make_excerpt(self.query_range.clone());
|
||||
let excerpt = self.make_excerpt(self.query_range.clone(), self.query_line_range.clone());
|
||||
if excerpt.size > self.options.max_bytes {
|
||||
log::debug!(
|
||||
"excerpt for cursor line is {} bytes, which exceeds the window",
|
||||
@@ -353,24 +379,24 @@ impl<'a> ExcerptSelector<'a> {
|
||||
let before_bytes =
|
||||
(self.options.target_before_cursor_over_total_bytes * bytes_remaining as f32) as usize;
|
||||
|
||||
let start_point = {
|
||||
let start_line = {
|
||||
let offset = self.query_offset.saturating_sub(before_bytes);
|
||||
let point = offset.to_point(self.buffer);
|
||||
Point::new(point.row + 1, 0)
|
||||
Line(point.row + 1)
|
||||
};
|
||||
let start_offset = start_point.to_offset(&self.buffer);
|
||||
let end_point = {
|
||||
let start_offset = Point::new(start_line.0, 0).to_offset(&self.buffer);
|
||||
let end_line = {
|
||||
let offset = start_offset + bytes_remaining;
|
||||
let point = offset.to_point(self.buffer);
|
||||
Point::new(point.row, 0)
|
||||
Line(point.row)
|
||||
};
|
||||
let end_offset = end_point.to_offset(&self.buffer);
|
||||
let end_offset = Point::new(end_line.0, 0).to_offset(&self.buffer);
|
||||
|
||||
// this could be expanded further since recalculated `signature_size` may be smaller, but
|
||||
// skipping that for now for simplicity
|
||||
//
|
||||
// TODO: could also consider checking if lines immediately before / after fit.
|
||||
let excerpt = self.make_excerpt(start_offset..end_offset);
|
||||
let excerpt = self.make_excerpt(start_offset..end_offset, start_line..end_line);
|
||||
if excerpt.size > self.options.max_bytes {
|
||||
log::error!(
|
||||
"bug: line-based excerpt selection has size {}, \
|
||||
@@ -382,14 +408,14 @@ impl<'a> ExcerptSelector<'a> {
|
||||
return Some(excerpt);
|
||||
}
|
||||
|
||||
fn make_excerpt(&self, range: Range<usize>) -> EditPredictionExcerpt {
|
||||
fn make_excerpt(&self, range: Range<usize>, line_range: Range<Line>) -> EditPredictionExcerpt {
|
||||
let parent_declarations = self
|
||||
.parent_declarations
|
||||
.iter()
|
||||
.filter(|(_, declaration)| declaration.item_range.contains_inclusive(&range))
|
||||
.map(|(id, declaration)| (*id, declaration.signature_range.clone()))
|
||||
.collect();
|
||||
EditPredictionExcerpt::new(range, parent_declarations)
|
||||
EditPredictionExcerpt::new(range, line_range, parent_declarations)
|
||||
}
|
||||
|
||||
/// Returns `true` if the `forward` excerpt is a better choice than the `backward` excerpt.
|
||||
|
||||
@@ -328,11 +328,7 @@ impl CompletionsMenu {
|
||||
.map(|choice| Completion {
|
||||
replace_range: selection.start.text_anchor..selection.end.text_anchor,
|
||||
new_text: choice.to_string(),
|
||||
label: CodeLabel {
|
||||
text: choice.to_string(),
|
||||
runs: Default::default(),
|
||||
filter_range: Default::default(),
|
||||
},
|
||||
label: CodeLabel::plain(choice.to_string(), None),
|
||||
icon_path: None,
|
||||
documentation: None,
|
||||
confirm: None,
|
||||
|
||||
@@ -594,11 +594,7 @@ impl DisplayMap {
|
||||
self.block_map.read(snapshot, edits);
|
||||
}
|
||||
|
||||
pub fn remove_inlays_for_excerpts(
|
||||
&mut self,
|
||||
excerpts_removed: &[ExcerptId],
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
pub fn remove_inlays_for_excerpts(&mut self, excerpts_removed: &[ExcerptId]) {
|
||||
let to_remove = self
|
||||
.inlay_map
|
||||
.current_inlays()
|
||||
@@ -610,7 +606,7 @@ impl DisplayMap {
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.splice_inlays(&to_remove, Vec::new(), cx);
|
||||
self.inlay_map.splice(&to_remove, Vec::new());
|
||||
}
|
||||
|
||||
fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {
|
||||
|
||||
@@ -3899,9 +3899,6 @@ impl Editor {
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if selection_ranges.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let ranges = match columnar_state {
|
||||
ColumnarSelectionState::FromMouse { .. } => {
|
||||
@@ -5291,8 +5288,8 @@ impl Editor {
|
||||
{
|
||||
self.splice_inlays(&to_remove, to_insert, cx);
|
||||
}
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.remove_inlays_for_excerpts(&excerpts_removed, cx)
|
||||
self.display_map.update(cx, |display_map, _| {
|
||||
display_map.remove_inlays_for_excerpts(&excerpts_removed)
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -23080,11 +23077,7 @@ fn snippet_completions(
|
||||
}),
|
||||
lsp_defaults: None,
|
||||
},
|
||||
label: CodeLabel {
|
||||
text: matching_prefix.clone(),
|
||||
runs: Vec::new(),
|
||||
filter_range: 0..matching_prefix.len(),
|
||||
},
|
||||
label: CodeLabel::plain(matching_prefix.clone(), None),
|
||||
icon_path: None,
|
||||
documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
|
||||
single_line: snippet.name.clone().into(),
|
||||
|
||||
@@ -14878,12 +14878,7 @@ async fn test_multiline_completion(cx: &mut TestAppContext) {
|
||||
} else {
|
||||
item.label.clone()
|
||||
};
|
||||
let len = text.len();
|
||||
Some(language::CodeLabel {
|
||||
text,
|
||||
runs: Vec::new(),
|
||||
filter_range: 0..len,
|
||||
})
|
||||
Some(language::CodeLabel::plain(text, None))
|
||||
})),
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
|
||||
@@ -651,6 +651,7 @@ impl EditorElement {
|
||||
fn mouse_left_down(
|
||||
editor: &mut Editor,
|
||||
event: &MouseDownEvent,
|
||||
hovered_hunk: Option<Range<Anchor>>,
|
||||
position_map: &PositionMap,
|
||||
line_numbers: &HashMap<MultiBufferRow, LineNumberLayout>,
|
||||
window: &mut Window,
|
||||
@@ -666,20 +667,7 @@ impl EditorElement {
|
||||
let mut click_count = event.click_count;
|
||||
let mut modifiers = event.modifiers;
|
||||
|
||||
if let Some(hovered_hunk) =
|
||||
position_map
|
||||
.display_hunks
|
||||
.iter()
|
||||
.find_map(|(hunk, hunk_hitbox)| match hunk {
|
||||
DisplayDiffHunk::Folded { .. } => None,
|
||||
DisplayDiffHunk::Unfolded {
|
||||
multi_buffer_range, ..
|
||||
} => hunk_hitbox
|
||||
.as_ref()
|
||||
.is_some_and(|hitbox| hitbox.is_hovered(window))
|
||||
.then(|| multi_buffer_range.clone()),
|
||||
})
|
||||
{
|
||||
if let Some(hovered_hunk) = hovered_hunk {
|
||||
editor.toggle_single_diff_hunk(hovered_hunk, cx);
|
||||
cx.notify();
|
||||
return;
|
||||
@@ -7259,6 +7247,26 @@ impl EditorElement {
|
||||
window.on_mouse_event({
|
||||
let position_map = layout.position_map.clone();
|
||||
let editor = self.editor.clone();
|
||||
let diff_hunk_range =
|
||||
layout
|
||||
.display_hunks
|
||||
.iter()
|
||||
.find_map(|(hunk, hunk_hitbox)| match hunk {
|
||||
DisplayDiffHunk::Folded { .. } => None,
|
||||
DisplayDiffHunk::Unfolded {
|
||||
multi_buffer_range, ..
|
||||
} => {
|
||||
if hunk_hitbox
|
||||
.as_ref()
|
||||
.map(|hitbox| hitbox.is_hovered(window))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some(multi_buffer_range.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
let line_numbers = layout.line_numbers.clone();
|
||||
|
||||
move |event: &MouseDownEvent, phase, window, cx| {
|
||||
@@ -7275,6 +7283,7 @@ impl EditorElement {
|
||||
Self::mouse_left_down(
|
||||
editor,
|
||||
event,
|
||||
diff_hunk_range.clone(),
|
||||
&position_map,
|
||||
line_numbers.as_ref(),
|
||||
window,
|
||||
|
||||
@@ -493,15 +493,22 @@ pub fn show_link_definition(
|
||||
}
|
||||
|
||||
let trigger_anchor = trigger_point.anchor();
|
||||
let anchor = snapshot.buffer_snapshot().anchor_before(*trigger_anchor);
|
||||
let Some(buffer) = editor.buffer().read(cx).buffer_for_anchor(anchor, cx) else {
|
||||
let Some((buffer, buffer_position)) = editor
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(*trigger_anchor, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Anchor {
|
||||
excerpt_id,
|
||||
text_anchor,
|
||||
..
|
||||
} = anchor;
|
||||
|
||||
let Some((excerpt_id, _, _)) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_containing(*trigger_anchor, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let same_kind = hovered_link_state.preferred_kind == preferred_kind
|
||||
|| hovered_link_state
|
||||
.links
|
||||
@@ -531,7 +538,7 @@ pub fn show_link_definition(
|
||||
async move {
|
||||
let result = match &trigger_point {
|
||||
TriggerPoint::Text(_) => {
|
||||
if let Some((url_range, url)) = find_url(&buffer, text_anchor, cx.clone()) {
|
||||
if let Some((url_range, url)) = find_url(&buffer, buffer_position, cx.clone()) {
|
||||
this.read_with(cx, |_, _| {
|
||||
let range = maybe!({
|
||||
let start =
|
||||
@@ -543,7 +550,7 @@ pub fn show_link_definition(
|
||||
})
|
||||
.ok()
|
||||
} else if let Some((filename_range, filename)) =
|
||||
find_file(&buffer, project.clone(), text_anchor, cx).await
|
||||
find_file(&buffer, project.clone(), buffer_position, cx).await
|
||||
{
|
||||
let range = maybe!({
|
||||
let start =
|
||||
@@ -555,7 +562,7 @@ pub fn show_link_definition(
|
||||
Some((range, vec![HoverLink::File(filename)]))
|
||||
} else if let Some(provider) = provider {
|
||||
let task = cx.update(|_, cx| {
|
||||
provider.definitions(&buffer, text_anchor, preferred_kind, cx)
|
||||
provider.definitions(&buffer, buffer_position, preferred_kind, cx)
|
||||
})?;
|
||||
if let Some(task) = task {
|
||||
task.await.ok().flatten().map(|definition_result| {
|
||||
|
||||
@@ -669,7 +669,7 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
) -> Option<Self::ListItem> {
|
||||
let settings = FileFinderSettings::get_global(cx);
|
||||
let candidate = self.get_entry(ix)?;
|
||||
let mut match_positions = match &self.directory_state {
|
||||
let match_positions = match &self.directory_state {
|
||||
DirectoryState::List { .. } => self.string_matches.get(ix)?.positions.clone(),
|
||||
DirectoryState::Create { user_input, .. } => {
|
||||
if let Some(user_input) = user_input {
|
||||
@@ -710,38 +710,29 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
});
|
||||
|
||||
match &self.directory_state {
|
||||
DirectoryState::List { parent_path, .. } => {
|
||||
let (label, indices) = if *parent_path == self.prompt_root {
|
||||
match_positions.iter_mut().for_each(|position| {
|
||||
*position += self.prompt_root.len();
|
||||
});
|
||||
(
|
||||
format!("{}{}", self.prompt_root, candidate.path.string),
|
||||
DirectoryState::List { parent_path, .. } => Some(
|
||||
ListItem::new(ix)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot::<Icon>(file_icon)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(HighlightedLabel::new(
|
||||
if parent_path == &self.prompt_root {
|
||||
format!("{}{}", self.prompt_root, candidate.path.string)
|
||||
} else if is_current_dir_candidate {
|
||||
"open this directory".to_string()
|
||||
} else {
|
||||
candidate.path.string
|
||||
},
|
||||
match_positions,
|
||||
)
|
||||
} else if is_current_dir_candidate {
|
||||
("open this directory".to_string(), vec![])
|
||||
} else {
|
||||
(candidate.path.string, match_positions)
|
||||
};
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot::<Icon>(file_icon)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(HighlightedLabel::new(label, indices)),
|
||||
)
|
||||
}
|
||||
)),
|
||||
),
|
||||
DirectoryState::Create {
|
||||
parent_path,
|
||||
user_input,
|
||||
..
|
||||
} => {
|
||||
let (label, delta) = if *parent_path == self.prompt_root {
|
||||
match_positions.iter_mut().for_each(|position| {
|
||||
*position += self.prompt_root.len();
|
||||
});
|
||||
let (label, delta) = if parent_path == &self.prompt_root {
|
||||
(
|
||||
format!("{}{}", self.prompt_root, candidate.path.string),
|
||||
self.prompt_root.len(),
|
||||
@@ -749,10 +740,10 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
} else {
|
||||
(candidate.path.string.clone(), 0)
|
||||
};
|
||||
let label_len = label.len();
|
||||
|
||||
let label_with_highlights = match user_input {
|
||||
Some(user_input) => {
|
||||
let label_len = label.len();
|
||||
if user_input.file.string == candidate.path.string {
|
||||
if user_input.exists {
|
||||
let label = if user_input.is_dir {
|
||||
@@ -764,7 +755,7 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
.with_default_highlights(
|
||||
&window.text_style(),
|
||||
vec![(
|
||||
delta..delta + label_len,
|
||||
delta..label_len,
|
||||
HighlightStyle::color(Color::Conflict.color(cx)),
|
||||
)],
|
||||
)
|
||||
@@ -774,17 +765,27 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
.with_default_highlights(
|
||||
&window.text_style(),
|
||||
vec![(
|
||||
delta..delta + label_len,
|
||||
delta..label_len,
|
||||
HighlightStyle::color(Color::Created.color(cx)),
|
||||
)],
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
} else {
|
||||
HighlightedLabel::new(label, match_positions).into_any_element()
|
||||
let mut highlight_positions = match_positions;
|
||||
highlight_positions.iter_mut().for_each(|position| {
|
||||
*position += delta;
|
||||
});
|
||||
HighlightedLabel::new(label, highlight_positions).into_any_element()
|
||||
}
|
||||
}
|
||||
None => HighlightedLabel::new(label, match_positions).into_any_element(),
|
||||
None => {
|
||||
let mut highlight_positions = match_positions;
|
||||
highlight_positions.iter_mut().for_each(|position| {
|
||||
*position += delta;
|
||||
});
|
||||
HighlightedLabel::new(label, highlight_positions).into_any_element()
|
||||
}
|
||||
};
|
||||
|
||||
Some(
|
||||
|
||||
@@ -228,7 +228,7 @@ impl PickerDelegate for PickerPromptDelegate {
|
||||
let highlights: Vec<_> = hit
|
||||
.positions
|
||||
.iter()
|
||||
.filter(|&&index| index < self.max_match_length)
|
||||
.filter(|index| index < &&self.max_match_length)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
App, Application, Bounds, Context, FontStyle, FontWeight, StyledText, Window, WindowBounds,
|
||||
WindowOptions, div, prelude::*, px, size,
|
||||
App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px,
|
||||
size,
|
||||
};
|
||||
|
||||
struct HelloWorld {}
|
||||
@@ -71,12 +71,6 @@ impl Render for HelloWorld {
|
||||
.child("100%"),
|
||||
),
|
||||
)
|
||||
.child(div().flex().gap_2().justify_between().child(
|
||||
StyledText::new("ABCD").with_highlights([
|
||||
(0..1, FontWeight::EXTRA_BOLD.into()),
|
||||
(2..3, FontStyle::Italic.into()),
|
||||
]),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,8 +180,7 @@ impl StyledText {
|
||||
"Can't use `with_default_highlights` and `with_highlights`"
|
||||
);
|
||||
let runs = Self::compute_runs(&self.text, default_style, highlights);
|
||||
self.runs = Some(runs);
|
||||
self
|
||||
self.with_runs(runs)
|
||||
}
|
||||
|
||||
/// Set the styling attributes for the given text, as well as
|
||||
@@ -194,7 +193,15 @@ impl StyledText {
|
||||
self.runs.is_none(),
|
||||
"Can't use `with_highlights` and `with_default_highlights`"
|
||||
);
|
||||
self.delayed_highlights = Some(highlights.into_iter().collect::<Vec<_>>());
|
||||
self.delayed_highlights = Some(
|
||||
highlights
|
||||
.into_iter()
|
||||
.inspect(|(run, _)| {
|
||||
debug_assert!(self.text.is_char_boundary(run.start));
|
||||
debug_assert!(self.text.is_char_boundary(run.end));
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -207,8 +214,10 @@ impl StyledText {
|
||||
let mut ix = 0;
|
||||
for (range, highlight) in highlights {
|
||||
if ix < range.start {
|
||||
debug_assert!(text.is_char_boundary(range.start));
|
||||
runs.push(default_style.clone().to_run(range.start - ix));
|
||||
}
|
||||
debug_assert!(text.is_char_boundary(range.end));
|
||||
runs.push(
|
||||
default_style
|
||||
.clone()
|
||||
@@ -225,6 +234,11 @@ impl StyledText {
|
||||
|
||||
/// Set the text runs for this piece of text.
|
||||
pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
|
||||
let mut text = &**self.text;
|
||||
for run in &runs {
|
||||
text = text.get(run.len..).expect("invalid text run");
|
||||
}
|
||||
assert!(text.is_empty(), "invalid text run");
|
||||
self.runs = Some(runs);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -449,12 +449,11 @@ impl MacTextSystemState {
|
||||
// to prevent core text from forming ligatures between them
|
||||
let needs_zwnj = last_font_run.replace(run.font_id) == Some(run.font_id);
|
||||
|
||||
let n_zwnjs = self.zwnjs_scratch_space.len(); // from previous loop
|
||||
let utf16_start = string.char_len(); // insert at end of string
|
||||
let n_zwnjs = self.zwnjs_scratch_space.len();
|
||||
let utf16_start = ix_converter.utf16_ix + n_zwnjs * ZWNJ_SIZE_16;
|
||||
ix_converter.advance_to_utf8_ix(ix_converter.utf8_ix + run.len);
|
||||
|
||||
// note: replace_str may silently ignore codepoints it dislikes (e.g., BOM at start of string)
|
||||
string.replace_str(&CFString::new(text), CFRange::init(utf16_start, 0));
|
||||
string.replace_str(&CFString::new(text), CFRange::init(utf16_start as isize, 0));
|
||||
if needs_zwnj {
|
||||
let zwnjs_pos = string.char_len();
|
||||
self.zwnjs_scratch_space.push((n_zwnjs, zwnjs_pos as usize));
|
||||
@@ -463,9 +462,10 @@ impl MacTextSystemState {
|
||||
CFRange::init(zwnjs_pos, 0),
|
||||
);
|
||||
}
|
||||
let utf16_end = string.char_len();
|
||||
let utf16_end = string.char_len() as usize;
|
||||
|
||||
let cf_range = CFRange::init(utf16_start, utf16_end - utf16_start);
|
||||
let cf_range =
|
||||
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
|
||||
let font = &self.fonts[run.font_id.0];
|
||||
|
||||
let font_metrics = font.metrics();
|
||||
@@ -548,12 +548,10 @@ impl MacTextSystemState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
struct StringIndexConverter<'a> {
|
||||
text: &'a str,
|
||||
/// Index in UTF-8 bytes
|
||||
utf8_ix: usize,
|
||||
/// Index in UTF-16 code units
|
||||
utf16_ix: usize,
|
||||
}
|
||||
|
||||
@@ -734,25 +732,6 @@ mod tests {
|
||||
assert_eq!(layout.runs[0].glyphs[0].id, GlyphId(68u32)); // a
|
||||
// There's no glyph for \u{feff}
|
||||
assert_eq!(layout.runs[0].glyphs[1].id, GlyphId(69u32)); // b
|
||||
|
||||
let line = "\u{feff}ab";
|
||||
let font_runs = &[
|
||||
FontRun {
|
||||
len: "\u{feff}".len(),
|
||||
font_id,
|
||||
},
|
||||
FontRun {
|
||||
len: "ab".len(),
|
||||
font_id,
|
||||
},
|
||||
];
|
||||
let layout = fonts.layout_line(line, px(16.), font_runs);
|
||||
assert_eq!(layout.len, line.len());
|
||||
assert_eq!(layout.runs.len(), 1);
|
||||
assert_eq!(layout.runs[0].glyphs.len(), 2);
|
||||
// There's no glyph for \u{feff}
|
||||
assert_eq!(layout.runs[0].glyphs[0].id, GlyphId(68u32)); // a
|
||||
assert_eq!(layout.runs[0].glyphs[1].id, GlyphId(69u32)); // b
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -530,18 +530,8 @@ impl WindowsWindowInner {
|
||||
};
|
||||
let scale_factor = lock.scale_factor;
|
||||
let wheel_scroll_amount = match modifiers.shift {
|
||||
true => {
|
||||
self.system_settings
|
||||
.borrow()
|
||||
.mouse_wheel_settings
|
||||
.wheel_scroll_chars
|
||||
}
|
||||
false => {
|
||||
self.system_settings
|
||||
.borrow()
|
||||
.mouse_wheel_settings
|
||||
.wheel_scroll_lines
|
||||
}
|
||||
true => lock.system_settings.mouse_wheel_settings.wheel_scroll_chars,
|
||||
false => lock.system_settings.mouse_wheel_settings.wheel_scroll_lines,
|
||||
};
|
||||
drop(lock);
|
||||
|
||||
@@ -584,11 +574,7 @@ impl WindowsWindowInner {
|
||||
return Some(1);
|
||||
};
|
||||
let scale_factor = lock.scale_factor;
|
||||
let wheel_scroll_chars = self
|
||||
.system_settings
|
||||
.borrow()
|
||||
.mouse_wheel_settings
|
||||
.wheel_scroll_chars;
|
||||
let wheel_scroll_chars = lock.system_settings.mouse_wheel_settings.wheel_scroll_chars;
|
||||
drop(lock);
|
||||
|
||||
let wheel_distance =
|
||||
@@ -721,8 +707,11 @@ impl WindowsWindowInner {
|
||||
// used by Chrome. However, it may result in one row of pixels being obscured
|
||||
// in our client area. But as Chrome says, "there seems to be no better solution."
|
||||
if is_maximized
|
||||
&& let Some(ref taskbar_position) =
|
||||
self.system_settings.borrow().auto_hide_taskbar_position
|
||||
&& let Some(ref taskbar_position) = self
|
||||
.state
|
||||
.borrow()
|
||||
.system_settings
|
||||
.auto_hide_taskbar_position
|
||||
{
|
||||
// For the auto-hide taskbar, adjust in by 1 pixel on taskbar edge,
|
||||
// so the window isn't treated as a "fullscreen app", which would cause
|
||||
@@ -1112,11 +1101,9 @@ impl WindowsWindowInner {
|
||||
if wparam.0 != 0 {
|
||||
let mut lock = self.state.borrow_mut();
|
||||
let display = lock.display;
|
||||
lock.system_settings.update(display, wparam.0);
|
||||
lock.click_state.system_update(wparam.0);
|
||||
lock.border_offset.update(handle).log_err();
|
||||
// system settings may emit a window message which wants to take the refcell lock, so drop it
|
||||
drop(lock);
|
||||
self.system_settings.borrow_mut().update(display, wparam.0);
|
||||
} else {
|
||||
self.handle_system_theme_changed(handle, lparam)?;
|
||||
};
|
||||
|
||||
@@ -51,6 +51,7 @@ pub struct WindowsWindowState {
|
||||
pub renderer: DirectXRenderer,
|
||||
|
||||
pub click_state: ClickState,
|
||||
pub system_settings: WindowsSystemSettings,
|
||||
pub current_cursor: Option<HCURSOR>,
|
||||
pub nc_button_pressed: Option<u32>,
|
||||
|
||||
@@ -65,7 +66,6 @@ pub(crate) struct WindowsWindowInner {
|
||||
pub(super) this: Weak<Self>,
|
||||
drop_target_helper: IDropTargetHelper,
|
||||
pub(crate) state: RefCell<WindowsWindowState>,
|
||||
pub(crate) system_settings: RefCell<WindowsSystemSettings>,
|
||||
pub(crate) handle: AnyWindowHandle,
|
||||
pub(crate) hide_title_bar: bool,
|
||||
pub(crate) is_movable: bool,
|
||||
@@ -115,6 +115,7 @@ impl WindowsWindowState {
|
||||
let system_key_handled = false;
|
||||
let hovered = false;
|
||||
let click_state = ClickState::new();
|
||||
let system_settings = WindowsSystemSettings::new(display);
|
||||
let nc_button_pressed = None;
|
||||
let fullscreen = None;
|
||||
let initial_placement = None;
|
||||
@@ -137,6 +138,7 @@ impl WindowsWindowState {
|
||||
hovered,
|
||||
renderer,
|
||||
click_state,
|
||||
system_settings,
|
||||
current_cursor,
|
||||
nc_button_pressed,
|
||||
display,
|
||||
@@ -229,7 +231,6 @@ impl WindowsWindowInner {
|
||||
validation_number: context.validation_number,
|
||||
main_receiver: context.main_receiver.clone(),
|
||||
platform_window_handle: context.platform_window_handle,
|
||||
system_settings: RefCell::new(WindowsSystemSettings::new(context.display)),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -643,12 +644,10 @@ impl PlatformWindow for WindowsWindow {
|
||||
let mut btn_encoded = Vec::new();
|
||||
for (index, btn) in answers.iter().enumerate() {
|
||||
let encoded = HSTRING::from(btn.label().as_ref());
|
||||
let button_id = match btn {
|
||||
PromptButton::Ok(_) => IDOK.0,
|
||||
PromptButton::Cancel(_) => IDCANCEL.0,
|
||||
// the first few low integer values are reserved for known buttons
|
||||
// so for simplicity we just go backwards from -1
|
||||
PromptButton::Other(_) => -(index as i32) - 1,
|
||||
let button_id = if btn.is_cancel() {
|
||||
IDCANCEL.0
|
||||
} else {
|
||||
index as i32 - 100
|
||||
};
|
||||
button_id_map.push(button_id);
|
||||
buttons.push(TASKDIALOG_BUTTON {
|
||||
@@ -666,11 +665,11 @@ impl PlatformWindow for WindowsWindow {
|
||||
.context("unable to create task dialog")
|
||||
.log_err();
|
||||
|
||||
if let Some(clicked) =
|
||||
button_id_map.iter().position(|&button_id| button_id == res)
|
||||
{
|
||||
let _ = done_tx.send(clicked);
|
||||
}
|
||||
let clicked = button_id_map
|
||||
.iter()
|
||||
.position(|&button_id| button_id == res)
|
||||
.unwrap();
|
||||
let _ = done_tx.send(clicked);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -424,6 +424,7 @@ impl WindowTextSystem {
|
||||
font_runs.clear();
|
||||
let line_end = line_start + line_text.len();
|
||||
|
||||
let mut last_font: Option<FontId> = None;
|
||||
let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
|
||||
let mut run_start = line_start;
|
||||
while run_start < line_end {
|
||||
@@ -452,13 +453,14 @@ impl WindowTextSystem {
|
||||
true
|
||||
};
|
||||
|
||||
let font_id = self.resolve_font(&run.font);
|
||||
if let Some(font_run) = font_runs.last_mut()
|
||||
&& font_id == font_run.font_id
|
||||
&& Some(font_run.font_id) == last_font
|
||||
&& !decoration_changed
|
||||
{
|
||||
font_run.len += run_len_within_line;
|
||||
} else {
|
||||
let font_id = self.resolve_font(&run.font);
|
||||
last_font = Some(font_id);
|
||||
font_runs.push(FontRun {
|
||||
len: run_len_within_line,
|
||||
font_id,
|
||||
|
||||
@@ -225,19 +225,15 @@ impl LineWrapper {
|
||||
|
||||
fn update_runs_after_truncation(result: &str, ellipsis: &str, runs: &mut Vec<TextRun>) {
|
||||
let mut truncate_at = result.len() - ellipsis.len();
|
||||
let mut run_end = None;
|
||||
for (run_index, run) in runs.iter_mut().enumerate() {
|
||||
if run.len <= truncate_at {
|
||||
truncate_at -= run.len;
|
||||
} else {
|
||||
run.len = truncate_at + ellipsis.len();
|
||||
run_end = Some(run_index + 1);
|
||||
runs.truncate(run_index + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(run_end) = run_end {
|
||||
runs.truncate(run_end);
|
||||
}
|
||||
}
|
||||
|
||||
/// A fragment of a line that can be wrapped.
|
||||
|
||||
@@ -670,6 +670,16 @@ pub struct CodeLabel {
|
||||
pub filter_range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct CodeLabelBuilder {
|
||||
/// The text to display.
|
||||
text: String,
|
||||
/// Syntax highlighting runs.
|
||||
runs: Vec<(Range<usize>, HighlightId)>,
|
||||
/// The portion of the text that should be used in fuzzy filtering.
|
||||
filter_range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, JsonSchema)]
|
||||
pub struct LanguageConfig {
|
||||
/// Human-readable name of the language.
|
||||
@@ -2223,6 +2233,34 @@ impl Grammar {
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeLabelBuilder {
|
||||
pub fn respan_filter_range(&mut self, filter_text: Option<&str>) {
|
||||
self.filter_range = filter_text
|
||||
.and_then(|filter| self.text.find(filter).map(|ix| ix..ix + filter.len()))
|
||||
.unwrap_or(0..self.text.len());
|
||||
}
|
||||
|
||||
pub fn push_str(&mut self, text: &str, highlight: Option<HighlightId>) {
|
||||
let start_ix = self.text.len();
|
||||
self.text.push_str(text);
|
||||
if let Some(highlight) = highlight {
|
||||
let end_ix = self.text.len();
|
||||
self.runs.push((start_ix..end_ix, highlight));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(mut self) -> CodeLabel {
|
||||
if self.filter_range.end == 0 {
|
||||
self.respan_filter_range(None);
|
||||
}
|
||||
CodeLabel {
|
||||
text: self.text,
|
||||
runs: self.runs,
|
||||
filter_range: self.filter_range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeLabel {
|
||||
pub fn fallback_for_completion(
|
||||
item: &lsp::CompletionItem,
|
||||
@@ -2286,22 +2324,36 @@ impl CodeLabel {
|
||||
}
|
||||
|
||||
pub fn plain(text: String, filter_text: Option<&str>) -> Self {
|
||||
Self::filtered(text, filter_text, Vec::new())
|
||||
}
|
||||
|
||||
pub fn filtered(
|
||||
text: String,
|
||||
filter_text: Option<&str>,
|
||||
runs: Vec<(Range<usize>, HighlightId)>,
|
||||
) -> Self {
|
||||
let filter_range = filter_text
|
||||
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
|
||||
.unwrap_or(0..text.len());
|
||||
Self {
|
||||
runs: Vec::new(),
|
||||
filter_range,
|
||||
text,
|
||||
}
|
||||
Self::new(text, filter_range, runs)
|
||||
}
|
||||
|
||||
pub fn push_str(&mut self, text: &str, highlight: Option<HighlightId>) {
|
||||
let start_ix = self.text.len();
|
||||
self.text.push_str(text);
|
||||
let end_ix = self.text.len();
|
||||
if let Some(highlight) = highlight {
|
||||
self.runs.push((start_ix..end_ix, highlight));
|
||||
pub fn new(
|
||||
text: String,
|
||||
filter_range: Range<usize>,
|
||||
runs: Vec<(Range<usize>, HighlightId)>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
text.get(filter_range.clone()).is_some(),
|
||||
"invalid filter range"
|
||||
);
|
||||
runs.iter().for_each(|(range, _)| {
|
||||
assert!(text.get(range.clone()).is_some(), "invalid run range");
|
||||
});
|
||||
Self {
|
||||
runs,
|
||||
filter_range,
|
||||
text,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,8 +142,6 @@ pub struct LanguageSettings {
|
||||
pub auto_indent_on_paste: bool,
|
||||
/// Controls how the editor handles the autoclosed characters.
|
||||
pub always_treat_brackets_as_autoclosed: bool,
|
||||
/// Which code actions to run on save
|
||||
pub code_actions_on_format: HashMap<String, bool>,
|
||||
/// Whether to perform linked edits
|
||||
pub linked_edits: bool,
|
||||
/// Task configuration for this language.
|
||||
@@ -578,7 +576,6 @@ impl settings::Settings for AllLanguageSettings {
|
||||
always_treat_brackets_as_autoclosed: settings
|
||||
.always_treat_brackets_as_autoclosed
|
||||
.unwrap(),
|
||||
code_actions_on_format: settings.code_actions_on_format.unwrap(),
|
||||
linked_edits: settings.linked_edits.unwrap(),
|
||||
tasks: LanguageTaskSettings {
|
||||
variables: tasks.variables.unwrap_or_default(),
|
||||
|
||||
@@ -463,11 +463,7 @@ fn build_code_label(
|
||||
|
||||
let filter_range = label.filter_range.clone();
|
||||
text.get(filter_range.clone())?;
|
||||
Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range,
|
||||
})
|
||||
Some(CodeLabel::new(text, filter_range, runs))
|
||||
}
|
||||
|
||||
fn lsp_completion_to_extension(value: lsp::CompletionItem) -> extension::Completion {
|
||||
@@ -615,11 +611,7 @@ fn test_build_code_label() {
|
||||
|
||||
assert_eq!(
|
||||
label,
|
||||
CodeLabel {
|
||||
text: label_text,
|
||||
runs: label_runs,
|
||||
filter_range: label.filter_range.clone()
|
||||
}
|
||||
CodeLabel::new(label_text, label.filter_range.clone(), label_runs)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -188,11 +188,7 @@ impl super::LspAdapter for CLspAdapter {
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(detail.len() + 1..text.len());
|
||||
return Some(CodeLabel {
|
||||
filter_range,
|
||||
text,
|
||||
runs,
|
||||
});
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
|
||||
if completion.detail.is_some() =>
|
||||
@@ -208,11 +204,7 @@ impl super::LspAdapter for CLspAdapter {
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(detail.len() + 1..text.len());
|
||||
return Some(CodeLabel {
|
||||
filter_range,
|
||||
text,
|
||||
runs,
|
||||
});
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)
|
||||
if completion.detail.is_some() =>
|
||||
@@ -236,11 +228,7 @@ impl super::LspAdapter for CLspAdapter {
|
||||
filter_start..filter_end
|
||||
});
|
||||
|
||||
return Some(CodeLabel {
|
||||
filter_range,
|
||||
text,
|
||||
runs,
|
||||
});
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some(kind) => {
|
||||
let highlight_name = match kind {
|
||||
@@ -324,11 +312,11 @@ impl super::LspAdapter for CLspAdapter {
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(CodeLabel {
|
||||
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||
text: text[display_range].to_string(),
|
||||
Some(CodeLabel::new(
|
||||
text[display_range.clone()].to_string(),
|
||||
filter_range,
|
||||
})
|
||||
language.highlight_text(&text.as_str().into(), display_range),
|
||||
))
|
||||
}
|
||||
|
||||
fn prepare_initialize_params(
|
||||
|
||||
@@ -222,7 +222,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
Some((lsp::CompletionItemKind::MODULE, detail)) => {
|
||||
let text = format!("{label} {detail}");
|
||||
let source = Rope::from(format!("import {text}").as_str());
|
||||
let runs = language.highlight_text(&source, 7..7 + text[name_offset..].len());
|
||||
let runs = language.highlight_text(&source, 7..7 + text.len());
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
.as_deref()
|
||||
@@ -231,11 +231,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(0..label.len());
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range,
|
||||
});
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some((
|
||||
lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE,
|
||||
@@ -246,7 +242,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
Rope::from(format!("var {} {}", &text[name_offset..], detail).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 4..4 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 4..4 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
@@ -256,18 +252,14 @@ impl LspAdapter for GoLspAdapter {
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(0..label.len());
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range,
|
||||
});
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some((lsp::CompletionItemKind::STRUCT, _)) => {
|
||||
let text = format!("{label} struct {{}}");
|
||||
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 5..5 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 5..5 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
@@ -277,18 +269,14 @@ impl LspAdapter for GoLspAdapter {
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(0..label.len());
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range,
|
||||
});
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some((lsp::CompletionItemKind::INTERFACE, _)) => {
|
||||
let text = format!("{label} interface {{}}");
|
||||
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 5..5 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 5..5 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
@@ -298,11 +286,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(0..label.len());
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range,
|
||||
});
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some((lsp::CompletionItemKind::FIELD, detail)) => {
|
||||
let text = format!("{label} {detail}");
|
||||
@@ -310,7 +294,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 16..16 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 16..16 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
@@ -320,11 +304,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(0..label.len());
|
||||
return Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range,
|
||||
});
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some((lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD, detail)) => {
|
||||
if let Some(signature) = detail.strip_prefix("func") {
|
||||
@@ -332,7 +312,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str());
|
||||
let runs = adjust_runs(
|
||||
name_offset,
|
||||
language.highlight_text(&source, 5..5 + text[name_offset..].len()),
|
||||
language.highlight_text(&source, 5..5 + text.len()),
|
||||
);
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
@@ -342,11 +322,7 @@ impl LspAdapter for GoLspAdapter {
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(0..label.len());
|
||||
return Some(CodeLabel {
|
||||
filter_range,
|
||||
text,
|
||||
runs,
|
||||
});
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -406,11 +382,11 @@ impl LspAdapter for GoLspAdapter {
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(CodeLabel {
|
||||
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||
text: text[display_range].to_string(),
|
||||
Some(CodeLabel::new(
|
||||
text[display_range.clone()].to_string(),
|
||||
filter_range,
|
||||
})
|
||||
language.highlight_text(&text.as_str().into(), display_range),
|
||||
))
|
||||
}
|
||||
|
||||
fn diagnostic_message_to_markdown(&self, message: &str) -> Option<String> {
|
||||
@@ -810,15 +786,15 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "Hello(a B) c.D".to_string(),
|
||||
filter_range: 0..5,
|
||||
runs: vec![
|
||||
Some(CodeLabel::new(
|
||||
"Hello(a B) c.D".to_string(),
|
||||
0..5,
|
||||
vec![
|
||||
(0..5, highlight_function),
|
||||
(8..9, highlight_type),
|
||||
(13..14, highlight_type),
|
||||
],
|
||||
})
|
||||
]
|
||||
))
|
||||
);
|
||||
|
||||
// Nested methods
|
||||
@@ -834,15 +810,15 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "one.two.Three() [3]interface{}".to_string(),
|
||||
filter_range: 0..13,
|
||||
runs: vec![
|
||||
Some(CodeLabel::new(
|
||||
"one.two.Three() [3]interface{}".to_string(),
|
||||
0..13,
|
||||
vec![
|
||||
(8..13, highlight_function),
|
||||
(17..18, highlight_number),
|
||||
(19..28, highlight_keyword),
|
||||
],
|
||||
})
|
||||
))
|
||||
);
|
||||
|
||||
// Nested fields
|
||||
@@ -858,11 +834,11 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "two.Three a.Bcd".to_string(),
|
||||
filter_range: 0..9,
|
||||
runs: vec![(4..9, highlight_field), (12..15, highlight_type)],
|
||||
})
|
||||
Some(CodeLabel::new(
|
||||
"two.Three a.Bcd".to_string(),
|
||||
0..9,
|
||||
vec![(4..9, highlight_field), (12..15, highlight_type)],
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -407,11 +407,6 @@ impl LspAdapter for PyrightLspAdapter {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let filter_range = item
|
||||
.filter_text
|
||||
.as_deref()
|
||||
.and_then(|filter| label.find(filter).map(|ix| ix..ix + filter.len()))
|
||||
.unwrap_or(0..label.len());
|
||||
let mut text = label.clone();
|
||||
if let Some(completion_details) = item
|
||||
.label_details
|
||||
@@ -420,14 +415,14 @@ impl LspAdapter for PyrightLspAdapter {
|
||||
{
|
||||
write!(&mut text, " {}", completion_details).ok();
|
||||
}
|
||||
Some(language::CodeLabel {
|
||||
runs: highlight_id
|
||||
Some(language::CodeLabel::filtered(
|
||||
text,
|
||||
item.filter_text.as_deref(),
|
||||
highlight_id
|
||||
.map(|id| (0..label.len(), id))
|
||||
.into_iter()
|
||||
.collect(),
|
||||
text,
|
||||
filter_range,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
@@ -458,11 +453,11 @@ impl LspAdapter for PyrightLspAdapter {
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(language::CodeLabel {
|
||||
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||
text: text[display_range].to_string(),
|
||||
Some(language::CodeLabel::new(
|
||||
text[display_range.clone()].to_string(),
|
||||
filter_range,
|
||||
})
|
||||
language.highlight_text(&text.as_str().into(), display_range),
|
||||
))
|
||||
}
|
||||
|
||||
async fn workspace_configuration(
|
||||
@@ -1424,16 +1419,11 @@ impl LspAdapter for PyLspAdapter {
|
||||
lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
|
||||
_ => return None,
|
||||
};
|
||||
let filter_range = item
|
||||
.filter_text
|
||||
.as_deref()
|
||||
.and_then(|filter| label.find(filter).map(|ix| ix..ix + filter.len()))
|
||||
.unwrap_or(0..label.len());
|
||||
Some(language::CodeLabel {
|
||||
text: label.clone(),
|
||||
runs: vec![(0..label.len(), highlight_id)],
|
||||
filter_range,
|
||||
})
|
||||
Some(language::CodeLabel::filtered(
|
||||
label.clone(),
|
||||
item.filter_text.as_deref(),
|
||||
vec![(0..label.len(), highlight_id)],
|
||||
))
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
@@ -1463,12 +1453,11 @@ impl LspAdapter for PyLspAdapter {
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(language::CodeLabel {
|
||||
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||
text: text[display_range].to_string(),
|
||||
Some(language::CodeLabel::new(
|
||||
text[display_range.clone()].to_string(),
|
||||
filter_range,
|
||||
})
|
||||
language.highlight_text(&text.as_str().into(), display_range),
|
||||
))
|
||||
}
|
||||
|
||||
async fn workspace_configuration(
|
||||
@@ -1708,11 +1697,6 @@ impl LspAdapter for BasedPyrightLspAdapter {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let filter_range = item
|
||||
.filter_text
|
||||
.as_deref()
|
||||
.and_then(|filter| label.find(filter).map(|ix| ix..ix + filter.len()))
|
||||
.unwrap_or(0..label.len());
|
||||
let mut text = label.clone();
|
||||
if let Some(completion_details) = item
|
||||
.label_details
|
||||
@@ -1721,14 +1705,14 @@ impl LspAdapter for BasedPyrightLspAdapter {
|
||||
{
|
||||
write!(&mut text, " {}", completion_details).ok();
|
||||
}
|
||||
Some(language::CodeLabel {
|
||||
runs: highlight_id
|
||||
Some(language::CodeLabel::filtered(
|
||||
text,
|
||||
item.filter_text.as_deref(),
|
||||
highlight_id
|
||||
.map(|id| (0..label.len(), id))
|
||||
.into_iter()
|
||||
.collect(),
|
||||
text,
|
||||
filter_range,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
@@ -1758,12 +1742,11 @@ impl LspAdapter for BasedPyrightLspAdapter {
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(language::CodeLabel {
|
||||
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||
text: text[display_range].to_string(),
|
||||
Some(language::CodeLabel::new(
|
||||
text[display_range.clone()].to_string(),
|
||||
filter_range,
|
||||
})
|
||||
language.highlight_text(&text.as_str().into(), display_range),
|
||||
))
|
||||
}
|
||||
|
||||
async fn workspace_configuration(
|
||||
|
||||
@@ -245,11 +245,7 @@ impl LspAdapter for RustLspAdapter {
|
||||
})
|
||||
.unwrap_or_else(filter_range);
|
||||
|
||||
CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range,
|
||||
}
|
||||
CodeLabel::new(text, filter_range, runs)
|
||||
};
|
||||
let mut label = match (detail_right, completion.kind) {
|
||||
(Some(signature), Some(lsp::CompletionItemKind::FIELD)) => {
|
||||
@@ -400,11 +396,11 @@ impl LspAdapter for RustLspAdapter {
|
||||
|
||||
let filter_range = prefix.len()..prefix.len() + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
Some(CodeLabel {
|
||||
runs: language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range),
|
||||
text: format!("{prefix}{name}"),
|
||||
Some(CodeLabel::new(
|
||||
format!("{prefix}{name}"),
|
||||
filter_range,
|
||||
})
|
||||
language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range),
|
||||
))
|
||||
}
|
||||
|
||||
fn prepare_initialize_params(
|
||||
@@ -1202,10 +1198,10 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||
filter_range: 0..5,
|
||||
runs: vec![
|
||||
Some(CodeLabel::new(
|
||||
"hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||
0..5,
|
||||
vec![
|
||||
(0..5, highlight_function),
|
||||
(7..10, highlight_keyword),
|
||||
(11..17, highlight_type),
|
||||
@@ -1213,7 +1209,7 @@ mod tests {
|
||||
(25..28, highlight_type),
|
||||
(29..30, highlight_type),
|
||||
],
|
||||
})
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
adapter
|
||||
@@ -1230,10 +1226,10 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||
filter_range: 0..5,
|
||||
runs: vec![
|
||||
Some(CodeLabel::new(
|
||||
"hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||
0..5,
|
||||
vec![
|
||||
(0..5, highlight_function),
|
||||
(7..10, highlight_keyword),
|
||||
(11..17, highlight_type),
|
||||
@@ -1241,7 +1237,7 @@ mod tests {
|
||||
(25..28, highlight_type),
|
||||
(29..30, highlight_type),
|
||||
],
|
||||
})
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
adapter
|
||||
@@ -1255,11 +1251,11 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "len: usize".to_string(),
|
||||
filter_range: 0..3,
|
||||
runs: vec![(0..3, highlight_field), (5..10, highlight_type),],
|
||||
})
|
||||
Some(CodeLabel::new(
|
||||
"len: usize".to_string(),
|
||||
0..3,
|
||||
vec![(0..3, highlight_field), (5..10, highlight_type),],
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1278,10 +1274,10 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||
filter_range: 0..5,
|
||||
runs: vec![
|
||||
Some(CodeLabel::new(
|
||||
"hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||
0..5,
|
||||
vec![
|
||||
(0..5, highlight_function),
|
||||
(7..10, highlight_keyword),
|
||||
(11..17, highlight_type),
|
||||
@@ -1289,7 +1285,7 @@ mod tests {
|
||||
(25..28, highlight_type),
|
||||
(29..30, highlight_type),
|
||||
],
|
||||
})
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1307,10 +1303,10 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||
filter_range: 0..5,
|
||||
runs: vec![
|
||||
Some(CodeLabel::new(
|
||||
"hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||
0..5,
|
||||
vec![
|
||||
(0..5, highlight_function),
|
||||
(7..10, highlight_keyword),
|
||||
(11..17, highlight_type),
|
||||
@@ -1318,7 +1314,7 @@ mod tests {
|
||||
(25..28, highlight_type),
|
||||
(29..30, highlight_type),
|
||||
],
|
||||
})
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1337,16 +1333,16 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "await.as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(),
|
||||
filter_range: 6..18,
|
||||
runs: vec![
|
||||
Some(CodeLabel::new(
|
||||
"await.as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(),
|
||||
6..18,
|
||||
vec![
|
||||
(6..18, HighlightId(2)),
|
||||
(20..23, HighlightId(1)),
|
||||
(33..40, HighlightId(0)),
|
||||
(45..46, HighlightId(0))
|
||||
],
|
||||
})
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1367,10 +1363,10 @@ mod tests {
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "pub fn as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(),
|
||||
filter_range: 7..19,
|
||||
runs: vec![
|
||||
Some(CodeLabel::new(
|
||||
"pub fn as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(),
|
||||
7..19,
|
||||
vec![
|
||||
(0..3, HighlightId(1)),
|
||||
(4..6, HighlightId(1)),
|
||||
(7..19, HighlightId(2)),
|
||||
@@ -1378,7 +1374,7 @@ mod tests {
|
||||
(34..41, HighlightId(0)),
|
||||
(46..47, HighlightId(0))
|
||||
],
|
||||
})
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1394,11 +1390,11 @@ mod tests {
|
||||
&language,
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "inner_value: String".to_string(),
|
||||
filter_range: 6..11,
|
||||
runs: vec![(0..11, HighlightId(3)), (13..19, HighlightId(0))],
|
||||
})
|
||||
Some(CodeLabel::new(
|
||||
"inner_value: String".to_string(),
|
||||
6..11,
|
||||
vec![(0..11, HighlightId(3)), (13..19, HighlightId(0))],
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1424,22 +1420,22 @@ mod tests {
|
||||
adapter
|
||||
.label_for_symbol("hello", lsp::SymbolKind::FUNCTION, &language)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "fn hello".to_string(),
|
||||
filter_range: 3..8,
|
||||
runs: vec![(0..2, highlight_keyword), (3..8, highlight_function)],
|
||||
})
|
||||
Some(CodeLabel::new(
|
||||
"fn hello".to_string(),
|
||||
3..8,
|
||||
vec![(0..2, highlight_keyword), (3..8, highlight_function)],
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
adapter
|
||||
.label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER, &language)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "type World".to_string(),
|
||||
filter_range: 5..10,
|
||||
runs: vec![(0..4, highlight_keyword), (5..10, highlight_type)],
|
||||
})
|
||||
Some(CodeLabel::new(
|
||||
"type World".to_string(),
|
||||
5..10,
|
||||
vec![(0..4, highlight_keyword), (5..10, highlight_type)],
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -777,16 +777,11 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
} else {
|
||||
item.label.clone()
|
||||
};
|
||||
let filter_range = item
|
||||
.filter_text
|
||||
.as_deref()
|
||||
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
|
||||
.unwrap_or(0..len);
|
||||
Some(language::CodeLabel {
|
||||
Some(language::CodeLabel::filtered(
|
||||
text,
|
||||
runs: vec![(0..len, highlight_id)],
|
||||
filter_range,
|
||||
})
|
||||
item.filter_text.as_deref(),
|
||||
vec![(0..len, highlight_id)],
|
||||
))
|
||||
}
|
||||
|
||||
async fn initialization_options(
|
||||
|
||||
@@ -201,16 +201,11 @@ impl LspAdapter for VtslsLspAdapter {
|
||||
} else {
|
||||
item.label.clone()
|
||||
};
|
||||
let filter_range = item
|
||||
.filter_text
|
||||
.as_deref()
|
||||
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
|
||||
.unwrap_or(0..len);
|
||||
Some(language::CodeLabel {
|
||||
Some(language::CodeLabel::filtered(
|
||||
text,
|
||||
runs: vec![(0..len, highlight_id)],
|
||||
filter_range,
|
||||
})
|
||||
item.filter_text.as_deref(),
|
||||
vec![(0..len, highlight_id)],
|
||||
))
|
||||
}
|
||||
|
||||
async fn workspace_configuration(
|
||||
|
||||
@@ -292,18 +292,16 @@ impl<'a> MarkdownParser<'a> {
|
||||
finder.kinds(&[linkify::LinkKind::Url]);
|
||||
let mut last_link_len = prev_len;
|
||||
for link in finder.links(t) {
|
||||
let start = link.start();
|
||||
let end = link.end();
|
||||
let range = (prev_len + start)..(prev_len + end);
|
||||
let start = prev_len + link.start();
|
||||
let end = prev_len + link.end();
|
||||
let range = start..end;
|
||||
link_ranges.push(range.clone());
|
||||
link_urls.push(link.as_str().to_string());
|
||||
|
||||
// If there is a style before we match a link, we have to add this to the highlighted ranges
|
||||
if style != MarkdownHighlightStyle::default()
|
||||
&& last_link_len < link.start()
|
||||
{
|
||||
if style != MarkdownHighlightStyle::default() && last_link_len < start {
|
||||
highlights.push((
|
||||
last_link_len..link.start(),
|
||||
last_link_len..start,
|
||||
MarkdownHighlight::Style(style.clone()),
|
||||
));
|
||||
}
|
||||
@@ -376,15 +374,11 @@ impl<'a> MarkdownParser<'a> {
|
||||
if !text.is_empty() {
|
||||
let parsed_regions = MarkdownParagraphChunk::Text(ParsedMarkdownText {
|
||||
source_range: source_range.clone(),
|
||||
contents: text.into(),
|
||||
highlights: highlights.clone(),
|
||||
region_ranges: region_ranges.clone(),
|
||||
regions: regions.clone(),
|
||||
contents: mem::take(&mut text).into(),
|
||||
highlights: mem::take(&mut highlights),
|
||||
region_ranges: mem::take(&mut region_ranges),
|
||||
regions: mem::take(&mut regions),
|
||||
});
|
||||
text = String::new();
|
||||
highlights = vec![];
|
||||
region_ranges = vec![];
|
||||
regions = vec![];
|
||||
markdown_text_like.push(parsed_regions);
|
||||
}
|
||||
image = Image::identify(
|
||||
@@ -409,9 +403,6 @@ impl<'a> MarkdownParser<'a> {
|
||||
if let Some(mut image) = image.take() {
|
||||
if !text.is_empty() {
|
||||
image.set_alt_text(std::mem::take(&mut text).into());
|
||||
mem::take(&mut highlights);
|
||||
mem::take(&mut region_ranges);
|
||||
mem::take(&mut regions);
|
||||
}
|
||||
markdown_text_like.push(MarkdownParagraphChunk::Image(image));
|
||||
}
|
||||
@@ -1280,40 +1271,17 @@ mod tests {
|
||||
panic!("Expected a paragraph");
|
||||
};
|
||||
assert_eq!(
|
||||
paragraph[0],
|
||||
MarkdownParagraphChunk::Image(Image {
|
||||
source_range: 0..111,
|
||||
link: Link::Web {
|
||||
url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
|
||||
},
|
||||
alt_text: Some("test".into()),
|
||||
height: None,
|
||||
width: None,
|
||||
},)
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_image_alt_text() {
|
||||
let parsed = parse("[](https://zed.dev)\n ").await;
|
||||
|
||||
let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
|
||||
text
|
||||
} else {
|
||||
panic!("Expected a paragraph");
|
||||
};
|
||||
assert_eq!(
|
||||
paragraph[0],
|
||||
MarkdownParagraphChunk::Image(Image {
|
||||
source_range: 0..142,
|
||||
link: Link::Web {
|
||||
url: "https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/zed-industries/zed/main/assets/badge/v0.json".to_string(),
|
||||
},
|
||||
alt_text: Some("Zed".into()),
|
||||
height: None,
|
||||
width: None,
|
||||
},)
|
||||
);
|
||||
paragraph[0],
|
||||
MarkdownParagraphChunk::Image(Image {
|
||||
source_range: 0..111,
|
||||
link: Link::Web {
|
||||
url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
|
||||
},
|
||||
alt_text: Some("test".into()),
|
||||
height: None,
|
||||
width: None,
|
||||
},)
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -118,8 +118,8 @@ pub(crate) mod m_2025_10_03 {
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_10_16 {
|
||||
pub(crate) mod m_2025_10_10 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::restore_code_actions_on_format;
|
||||
pub(crate) use settings::remove_code_actions_on_format;
|
||||
}
|
||||
|
||||
70
crates/migrator/src/migrations/m_2025_10_10/settings.rs
Normal file
70
crates/migrator/src/migrations/m_2025_10_10/settings.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn remove_code_actions_on_format(value: &mut Value) -> Result<()> {
|
||||
remove_code_actions_on_format_inner(value, &[])?;
|
||||
let languages = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("languages"))
|
||||
.and_then(|languages| languages.as_object_mut());
|
||||
if let Some(languages) = languages {
|
||||
for (language_name, language) in languages.iter_mut() {
|
||||
let path = vec!["languages", language_name];
|
||||
remove_code_actions_on_format_inner(language, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Result<()> {
|
||||
let Some(obj) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(code_actions_on_format) = obj.get("code_actions_on_format").cloned() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
fn fmt_path(path: &[&str], key: &str) -> String {
|
||||
let mut path = path.to_vec();
|
||||
path.push(key);
|
||||
path.join(".")
|
||||
}
|
||||
|
||||
anyhow::ensure!(
|
||||
code_actions_on_format.is_object(),
|
||||
r#"The `code_actions_on_format` setting is deprecated, but it is in an invalid state and cannot be migrated at {}. Please ensure the code_actions_on_format setting is a Map<String, bool>"#,
|
||||
fmt_path(path, "code_actions_on_format"),
|
||||
);
|
||||
|
||||
let code_actions_map = code_actions_on_format.as_object().unwrap();
|
||||
let mut code_actions = vec![];
|
||||
for (code_action, code_action_enabled) in code_actions_map {
|
||||
if code_action_enabled.as_bool().map_or(false, |b| !b) {
|
||||
continue;
|
||||
}
|
||||
code_actions.push(code_action.clone());
|
||||
}
|
||||
|
||||
let mut formatter_array = vec![];
|
||||
if let Some(formatter) = obj.get("formatter") {
|
||||
if let Some(array) = formatter.as_array() {
|
||||
formatter_array = array.clone();
|
||||
} else {
|
||||
formatter_array.insert(0, formatter.clone());
|
||||
}
|
||||
};
|
||||
let found_code_actions = !code_actions.is_empty();
|
||||
formatter_array.splice(
|
||||
0..0,
|
||||
code_actions
|
||||
.into_iter()
|
||||
.map(|code_action| serde_json::json!({"code_action": code_action})),
|
||||
);
|
||||
|
||||
obj.remove("code_actions_on_format");
|
||||
if found_code_actions {
|
||||
obj.insert("formatter".to_string(), Value::Array(formatter_array));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::patterns::migrate_language_setting;
|
||||
|
||||
pub fn restore_code_actions_on_format(value: &mut Value) -> Result<()> {
|
||||
migrate_language_setting(value, restore_code_actions_on_format_inner)
|
||||
}
|
||||
|
||||
fn restore_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Result<()> {
|
||||
let Some(obj) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
let code_actions_on_format = obj
|
||||
.get("code_actions_on_format")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Value::Object(Default::default()));
|
||||
|
||||
fn fmt_path(path: &[&str], key: &str) -> String {
|
||||
let mut path = path.to_vec();
|
||||
path.push(key);
|
||||
path.join(".")
|
||||
}
|
||||
|
||||
let Some(mut code_actions_map) = code_actions_on_format.as_object().cloned() else {
|
||||
anyhow::bail!(
|
||||
r#"The `code_actions_on_format` is in an invalid state and cannot be migrated at {}. Please ensure the code_actions_on_format setting is a Map<String, bool>"#,
|
||||
fmt_path(path, "code_actions_on_format"),
|
||||
);
|
||||
};
|
||||
|
||||
let Some(formatter) = obj.get("formatter") else {
|
||||
return Ok(());
|
||||
};
|
||||
let formatter_array = if let Some(array) = formatter.as_array() {
|
||||
array.clone()
|
||||
} else {
|
||||
vec![formatter.clone()]
|
||||
};
|
||||
if formatter_array.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut code_action_formatters = Vec::new();
|
||||
for formatter in formatter_array {
|
||||
let Some(code_action) = formatter.get("code_action") else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(code_action_name) = code_action.as_str() else {
|
||||
anyhow::bail!(
|
||||
r#"The `code_action` is in an invalid state and cannot be migrated at {}. Please ensure the code_action setting is a String"#,
|
||||
fmt_path(path, "formatter"),
|
||||
);
|
||||
};
|
||||
code_action_formatters.push(code_action_name.to_string());
|
||||
}
|
||||
|
||||
code_actions_map.extend(
|
||||
code_action_formatters
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|code_action| (code_action, Value::Bool(true))),
|
||||
);
|
||||
|
||||
obj.remove("formatter");
|
||||
obj.insert(
|
||||
"code_actions_on_format".into(),
|
||||
Value::Object(code_actions_map),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -213,7 +213,7 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
|
||||
migrations::m_2025_10_03::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_10_03,
|
||||
),
|
||||
MigrationType::Json(migrations::m_2025_10_16::restore_code_actions_on_format),
|
||||
MigrationType::Json(migrations::m_2025_10_10::remove_code_actions_on_format),
|
||||
];
|
||||
run_migrations(text, migrations)
|
||||
}
|
||||
@@ -367,7 +367,6 @@ mod tests {
|
||||
pretty_assertions::assert_eq!(migrated.as_deref(), output);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_migrate_settings(input: &str, output: Option<&str>) {
|
||||
let migrated = migrate_settings(input).unwrap();
|
||||
assert_migrated_correctly(migrated, output);
|
||||
@@ -1342,11 +1341,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_flatten_code_action_formatters_basic_array() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_01::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_10_01,
|
||||
)],
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
@@ -1373,11 +1368,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_flatten_code_action_formatters_basic_object() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_01::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_10_01,
|
||||
)],
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
@@ -1509,11 +1500,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_flatten_code_action_formatters_array_with_multiple_action_blocks_in_defaults_and_multiple_languages()
|
||||
{
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_01::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_10_01,
|
||||
)],
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
@@ -1929,109 +1916,300 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_restore_code_actions_on_format() {
|
||||
fn test_code_actions_on_format_migration_basic() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_16::restore_code_actions_on_format,
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"formatter": {
|
||||
"code_action": "foo"
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true,
|
||||
"source.fixAll": true
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"code_actions_on_format": {
|
||||
"foo": true
|
||||
}
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
},
|
||||
{
|
||||
"code_action": "source.fixAll"
|
||||
}
|
||||
]
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_actions_on_format_migration_filters_false_values() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_16::restore_code_actions_on_format,
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{ "code_action": "foo" },
|
||||
"auto"
|
||||
]
|
||||
"code_actions_on_format": {
|
||||
"a": true,
|
||||
"b": false,
|
||||
"c": true
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
None,
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "a"
|
||||
},
|
||||
{
|
||||
"code_action": "c"
|
||||
}
|
||||
]
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_actions_on_format_migration_with_existing_formatter_object() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_16::restore_code_actions_on_format,
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"formatter": {
|
||||
"code_action": "foo"
|
||||
"formatter": "prettier",
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
},
|
||||
"prettier"
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_actions_on_format_migration_with_existing_formatter_array() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"formatter": ["prettier", {"language_server": "eslint"}],
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true,
|
||||
"source.fixAll": true
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
},
|
||||
{
|
||||
"code_action": "source.fixAll"
|
||||
},
|
||||
"prettier",
|
||||
{
|
||||
"language_server": "eslint"
|
||||
}
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_actions_on_format_migration_in_languages() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"languages": {
|
||||
"JavaScript": {
|
||||
"code_actions_on_format": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
},
|
||||
"Go": {
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"languages": {
|
||||
"JavaScript": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.fixAll.eslint"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Go": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_actions_on_format_migration_in_languages_with_existing_formatter() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"languages": {
|
||||
"JavaScript": {
|
||||
"formatter": "prettier",
|
||||
"code_actions_on_format": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.organizeImports": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"languages": {
|
||||
"JavaScript": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.fixAll.eslint"
|
||||
},
|
||||
"prettier"
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_actions_on_format_migration_mixed_global_and_languages() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"formatter": "prettier",
|
||||
"code_actions_on_format": {
|
||||
"source.fixAll": true
|
||||
},
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": "rust-analyzer",
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"code_actions_on_format": {
|
||||
"bar": true,
|
||||
"baz": false
|
||||
"Python": {
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true,
|
||||
"source.fixAll": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"code_actions_on_format": {
|
||||
"foo": true,
|
||||
"bar": true,
|
||||
"baz": false
|
||||
}
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.fixAll"
|
||||
},
|
||||
"prettier"
|
||||
],
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
},
|
||||
"rust-analyzer"
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_actions_on_format_no_migration_when_not_present() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_16::restore_code_actions_on_format,
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{ "code_action": "foo" },
|
||||
{ "code_action": "qux" },
|
||||
],
|
||||
"code_actions_on_format": {
|
||||
"bar": true,
|
||||
"baz": false
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"code_actions_on_format": {
|
||||
"foo": true,
|
||||
"qux": true,
|
||||
"bar": true,
|
||||
"baz": false
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_16::restore_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"formatter": [],
|
||||
"code_actions_on_format": {
|
||||
"bar": true,
|
||||
"baz": false
|
||||
}
|
||||
"formatter": ["prettier"]
|
||||
}"#
|
||||
.unindent(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_actions_on_format_migration_all_false_values() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"code_actions_on_format": {
|
||||
"a": false,
|
||||
"b": false
|
||||
},
|
||||
"formatter": "prettier"
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": "prettier"
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,4 @@ pub(crate) use settings::{
|
||||
SETTINGS_ASSISTANT_PATTERN, SETTINGS_ASSISTANT_TOOLS_PATTERN,
|
||||
SETTINGS_DUPLICATED_AGENT_PATTERN, SETTINGS_EDIT_PREDICTIONS_ASSISTANT_PATTERN,
|
||||
SETTINGS_LANGUAGES_PATTERN, SETTINGS_NESTED_KEY_VALUE_PATTERN, SETTINGS_ROOT_KEY_VALUE_PATTERN,
|
||||
migrate_language_setting,
|
||||
};
|
||||
|
||||
@@ -108,24 +108,3 @@ pub const SETTINGS_DUPLICATED_AGENT_PATTERN: &str = r#"(document
|
||||
(#eq? @agent1 "agent")
|
||||
(#eq? @agent2 "agent")
|
||||
)"#;
|
||||
|
||||
/// Migrate language settings,
|
||||
/// calls `migrate_fn` with the top level object as well as all language settings under the "languages" key
|
||||
/// Fails early if `migrate_fn` returns an error at any point
|
||||
pub fn migrate_language_setting(
|
||||
value: &mut serde_json::Value,
|
||||
migrate_fn: fn(&mut serde_json::Value, path: &[&str]) -> anyhow::Result<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
migrate_fn(value, &[])?;
|
||||
let languages = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("languages"))
|
||||
.and_then(|languages| languages.as_object_mut());
|
||||
if let Some(languages) = languages {
|
||||
for (language_name, language) in languages.iter_mut() {
|
||||
let path = vec!["languages", language_name];
|
||||
migrate_fn(language, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6165,20 +6165,22 @@ impl MultiBufferSnapshot {
|
||||
) -> SmallVec<[Locator; 1]> {
|
||||
let mut sorted_ids = ids.into_iter().collect::<SmallVec<[_; 1]>>();
|
||||
sorted_ids.sort_unstable();
|
||||
sorted_ids.dedup();
|
||||
let mut locators = SmallVec::new();
|
||||
|
||||
while sorted_ids.last() == Some(&ExcerptId::max()) {
|
||||
sorted_ids.pop();
|
||||
locators.push(Locator::max());
|
||||
if let Some(mapping) = self.excerpt_ids.last() {
|
||||
locators.push(mapping.locator.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut sorted_ids = sorted_ids.into_iter().peekable();
|
||||
locators.extend(
|
||||
sorted_ids
|
||||
.peeking_take_while(|excerpt| *excerpt == ExcerptId::min())
|
||||
.map(|_| Locator::min()),
|
||||
);
|
||||
let mut sorted_ids = sorted_ids.into_iter().dedup().peekable();
|
||||
if sorted_ids.peek() == Some(&ExcerptId::min()) {
|
||||
sorted_ids.next();
|
||||
if let Some(mapping) = self.excerpt_ids.first() {
|
||||
locators.push(mapping.locator.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>(());
|
||||
for id in sorted_ids {
|
||||
|
||||
@@ -577,6 +577,7 @@ fn get_or_npm_install_builtin_agent(
|
||||
package_name: SharedString,
|
||||
entrypoint_path: PathBuf,
|
||||
minimum_version: Option<semver::Version>,
|
||||
channel: &'static str,
|
||||
status_tx: Option<watch::Sender<SharedString>>,
|
||||
new_version_available: Option<watch::Sender<Option<String>>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -648,10 +649,12 @@ fn get_or_npm_install_builtin_agent(
|
||||
let dir = dir.clone();
|
||||
let fs = fs.clone();
|
||||
async move {
|
||||
// TODO remove the filter
|
||||
let latest_version = node_runtime
|
||||
.npm_package_latest_version(&package_name)
|
||||
.await
|
||||
.ok();
|
||||
.ok()
|
||||
.filter(|_| channel == "latest");
|
||||
if let Some(latest_version) = latest_version
|
||||
&& &latest_version != &file_name.to_string_lossy()
|
||||
{
|
||||
@@ -660,6 +663,7 @@ fn get_or_npm_install_builtin_agent(
|
||||
dir.clone(),
|
||||
node_runtime,
|
||||
package_name.clone(),
|
||||
channel,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
@@ -683,6 +687,7 @@ fn get_or_npm_install_builtin_agent(
|
||||
dir.clone(),
|
||||
node_runtime,
|
||||
package_name.clone(),
|
||||
channel,
|
||||
))
|
||||
.await?
|
||||
.into()
|
||||
@@ -731,13 +736,14 @@ async fn download_latest_version(
|
||||
dir: PathBuf,
|
||||
node_runtime: NodeRuntime,
|
||||
package_name: SharedString,
|
||||
channel: &'static str,
|
||||
) -> Result<String> {
|
||||
log::debug!("downloading latest version of {package_name}");
|
||||
|
||||
let tmp_dir = tempfile::tempdir_in(&dir)?;
|
||||
|
||||
node_runtime
|
||||
.npm_install_packages(tmp_dir.path(), &[(&package_name, "latest")])
|
||||
.npm_install_packages(tmp_dir.path(), &[(&package_name, channel)])
|
||||
.await?;
|
||||
|
||||
let version = node_runtime
|
||||
@@ -880,12 +886,17 @@ impl ExternalAgentServer for LocalGemini {
|
||||
GEMINI_NAME.into(),
|
||||
"@google/gemini-cli".into(),
|
||||
"node_modules/@google/gemini-cli/dist/index.js".into(),
|
||||
// TODO remove these windows-specific workarounds once v0.9.0 stable is released
|
||||
if cfg!(windows) {
|
||||
// v0.8.x on Windows has a bug that causes the initialize request to hang forever
|
||||
Some("0.9.0".parse().unwrap())
|
||||
Some("0.9.0-preview.4".parse().unwrap())
|
||||
} else {
|
||||
Some("0.2.1".parse().unwrap())
|
||||
},
|
||||
if cfg!(windows) {
|
||||
"0.9.0-preview.4"
|
||||
} else {
|
||||
"latest"
|
||||
},
|
||||
status_tx,
|
||||
new_version_available_tx,
|
||||
fs,
|
||||
@@ -969,6 +980,7 @@ impl ExternalAgentServer for LocalClaudeCode {
|
||||
"@zed-industries/claude-code-acp".into(),
|
||||
"node_modules/@zed-industries/claude-code-acp/dist/index.js".into(),
|
||||
Some("0.5.2".parse().unwrap()),
|
||||
"latest",
|
||||
status_tx,
|
||||
new_version_available_tx,
|
||||
fs,
|
||||
|
||||
@@ -14,7 +14,6 @@ pub mod json_language_server_ext;
|
||||
pub mod log_store;
|
||||
pub mod lsp_ext_command;
|
||||
pub mod rust_analyzer_ext;
|
||||
pub mod vue_language_server_ext;
|
||||
|
||||
use crate::{
|
||||
CodeAction, ColorPresentation, Completion, CompletionDisplayOptions, CompletionResponse,
|
||||
@@ -988,7 +987,6 @@ impl LocalLspStore {
|
||||
})
|
||||
.detach();
|
||||
|
||||
vue_language_server_ext::register_requests(this.clone(), language_server);
|
||||
json_language_server_ext::register_requests(this.clone(), language_server);
|
||||
rust_analyzer_ext::register_notifications(this.clone(), language_server);
|
||||
clangd_ext::register_notifications(this, language_server, adapter);
|
||||
@@ -1335,32 +1333,6 @@ impl LocalLspStore {
|
||||
})?;
|
||||
}
|
||||
|
||||
// Formatter for `code_actions_on_format` that runs before
|
||||
// the rest of the formatters
|
||||
let mut code_actions_on_format_formatters = None;
|
||||
let should_run_code_actions_on_format = !matches!(
|
||||
(trigger, &settings.format_on_save),
|
||||
(FormatTrigger::Save, &FormatOnSave::Off)
|
||||
);
|
||||
if should_run_code_actions_on_format {
|
||||
let have_code_actions_to_run_on_format = settings
|
||||
.code_actions_on_format
|
||||
.values()
|
||||
.any(|enabled| *enabled);
|
||||
if have_code_actions_to_run_on_format {
|
||||
zlog::trace!(logger => "going to run code actions on format");
|
||||
code_actions_on_format_formatters = Some(
|
||||
settings
|
||||
.code_actions_on_format
|
||||
.iter()
|
||||
.filter_map(|(action, enabled)| enabled.then_some(action))
|
||||
.cloned()
|
||||
.map(Formatter::CodeAction)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let formatters = match (trigger, &settings.format_on_save) {
|
||||
(FormatTrigger::Save, FormatOnSave::Off) => &[],
|
||||
(FormatTrigger::Manual, _) | (FormatTrigger::Save, FormatOnSave::On) => {
|
||||
@@ -1368,11 +1340,6 @@ impl LocalLspStore {
|
||||
}
|
||||
};
|
||||
|
||||
let formatters = code_actions_on_format_formatters
|
||||
.iter()
|
||||
.flatten()
|
||||
.chain(formatters);
|
||||
|
||||
for formatter in formatters {
|
||||
let formatter = if formatter == &Formatter::Auto {
|
||||
if settings.prettier.allowed {
|
||||
@@ -9398,11 +9365,7 @@ impl LspStore {
|
||||
name: symbol.name,
|
||||
kind: symbol.kind,
|
||||
range: symbol.range,
|
||||
label: CodeLabel {
|
||||
text: Default::default(),
|
||||
runs: Default::default(),
|
||||
filter_range: Default::default(),
|
||||
},
|
||||
label: CodeLabel::default(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
@@ -9592,11 +9555,7 @@ impl LspStore {
|
||||
new_text: completion.new_text,
|
||||
source: completion.source,
|
||||
documentation: None,
|
||||
label: CodeLabel {
|
||||
text: Default::default(),
|
||||
runs: Default::default(),
|
||||
filter_range: Default::default(),
|
||||
},
|
||||
label: CodeLabel::default(),
|
||||
insert_text_mode: None,
|
||||
icon_path: None,
|
||||
confirm: None,
|
||||
@@ -12783,7 +12742,6 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
async fn which(&self, command: &OsStr) -> Option<PathBuf> {
|
||||
let mut worktree_abs_path = self.worktree_root_path().to_path_buf();
|
||||
if self.fs.is_file(&worktree_abs_path).await {
|
||||
@@ -12793,14 +12751,6 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
|
||||
which::which_in(command, shell_path.as_ref(), worktree_abs_path).ok()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
async fn which(&self, command: &OsStr) -> Option<PathBuf> {
|
||||
// todo(windows) Getting the shell env variables in a current directory on Windows is more complicated than other platforms
|
||||
// there isn't a 'default shell' necessarily. The closest would be the default profile on the windows terminal
|
||||
// SEE: https://learn.microsoft.com/en-us/windows/terminal/customize-settings/startup
|
||||
which::which(command).ok()
|
||||
}
|
||||
|
||||
async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> {
|
||||
let mut working_dir = self.worktree_root_path().to_path_buf();
|
||||
if self.fs.is_file(&working_dir).await {
|
||||
@@ -13102,19 +13052,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_multi_len_chars_normalization() {
|
||||
let mut label = CodeLabel {
|
||||
text: "myElˇ (parameter) myElˇ: {\n foo: string;\n}".to_string(),
|
||||
runs: vec![(0..6, HighlightId(1))],
|
||||
filter_range: 0..6,
|
||||
};
|
||||
let mut label = CodeLabel::new(
|
||||
"myElˇ (parameter) myElˇ: {\n foo: string;\n}".to_string(),
|
||||
0..6,
|
||||
vec![(0..6, HighlightId(1))],
|
||||
);
|
||||
ensure_uniform_list_compatible_label(&mut label);
|
||||
assert_eq!(
|
||||
label,
|
||||
CodeLabel {
|
||||
text: "myElˇ (parameter) myElˇ: { foo: string; }".to_string(),
|
||||
runs: vec![(0..6, HighlightId(1))],
|
||||
filter_range: 0..6,
|
||||
}
|
||||
CodeLabel::new(
|
||||
"myElˇ (parameter) myElˇ: { foo: string; }".to_string(),
|
||||
0..6,
|
||||
vec![(0..6, HighlightId(1))],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
use anyhow::Context as _;
|
||||
use gpui::{AppContext, WeakEntity};
|
||||
use lsp::{LanguageServer, LanguageServerName};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::LspStore;
|
||||
|
||||
struct VueServerRequest;
|
||||
struct TypescriptServerResponse;
|
||||
|
||||
impl lsp::notification::Notification for VueServerRequest {
|
||||
type Params = Vec<(u64, String, serde_json::Value)>;
|
||||
|
||||
const METHOD: &'static str = "tsserver/request";
|
||||
}
|
||||
|
||||
impl lsp::notification::Notification for TypescriptServerResponse {
|
||||
type Params = Vec<(u64, serde_json::Value)>;
|
||||
|
||||
const METHOD: &'static str = "tsserver/response";
|
||||
}
|
||||
|
||||
const VUE_SERVER_NAME: LanguageServerName = LanguageServerName::new_static("vue-language-server");
|
||||
const VTSLS: LanguageServerName = LanguageServerName::new_static("vtsls");
|
||||
const TS_LS: LanguageServerName = LanguageServerName::new_static("typescript-language-server");
|
||||
|
||||
pub fn register_requests(lsp_store: WeakEntity<LspStore>, language_server: &LanguageServer) {
|
||||
let language_server_name = language_server.name();
|
||||
if language_server_name == VUE_SERVER_NAME {
|
||||
let vue_server_id = language_server.server_id();
|
||||
language_server
|
||||
.on_notification::<VueServerRequest, _>({
|
||||
move |params, cx| {
|
||||
let lsp_store = lsp_store.clone();
|
||||
let Ok(Some(vue_server)) = lsp_store.read_with(cx, |this, _| {
|
||||
this.language_server_for_id(vue_server_id)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let requests = params;
|
||||
let target_server = match lsp_store.read_with(cx, |this, _| {
|
||||
let language_server_id = this
|
||||
.as_local()
|
||||
.and_then(|local| {
|
||||
local.language_server_ids.iter().find_map(|(seed, v)| {
|
||||
[VTSLS, TS_LS].contains(&seed.name).then_some(v.id)
|
||||
})
|
||||
})
|
||||
.context("Could not find language server")?;
|
||||
|
||||
this.language_server_for_id(language_server_id)
|
||||
.context("language server not found")
|
||||
}) {
|
||||
Ok(Ok(server)) => server,
|
||||
other => {
|
||||
log::warn!(
|
||||
"vue-language-server forwarding skipped: {other:?}. \
|
||||
Returning null tsserver responses"
|
||||
);
|
||||
if !requests.is_empty() {
|
||||
let null_responses = requests
|
||||
.into_iter()
|
||||
.map(|(id, _, _)| (id, Value::Null))
|
||||
.collect::<Vec<_>>();
|
||||
let _ = vue_server
|
||||
.notify::<TypescriptServerResponse>(null_responses);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let cx = cx.clone();
|
||||
for (request_id, command, payload) in requests.into_iter() {
|
||||
let target_server = target_server.clone();
|
||||
let vue_server = vue_server.clone();
|
||||
cx.background_spawn(async move {
|
||||
let response = target_server
|
||||
.request::<lsp::request::ExecuteCommand>(
|
||||
lsp::ExecuteCommandParams {
|
||||
command: "typescript.tsserverRequest".to_owned(),
|
||||
arguments: vec![Value::String(command), payload],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let response_body = match response {
|
||||
util::ConnectionResult::Result(Ok(result)) => match result {
|
||||
Some(Value::Object(mut map)) => map
|
||||
.remove("body")
|
||||
.unwrap_or(Value::Object(map)),
|
||||
Some(other) => other,
|
||||
None => Value::Null,
|
||||
},
|
||||
util::ConnectionResult::Result(Err(error)) => {
|
||||
log::warn!(
|
||||
"typescript.tsserverRequest failed: {error:?} for request {request_id}"
|
||||
);
|
||||
Value::Null
|
||||
}
|
||||
other => {
|
||||
log::warn!(
|
||||
"typescript.tsserverRequest did not return a response: {other:?} for request {request_id}"
|
||||
);
|
||||
Value::Null
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = vue_server
|
||||
.notify::<TypescriptServerResponse>(vec![(request_id, response_body)])
|
||||
{
|
||||
log::warn!(
|
||||
"Failed to notify vue-language-server of tsserver response: {err:?}"
|
||||
);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
sync::{Arc, atomic::AtomicUsize},
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::{Fs, copy_recursive};
|
||||
use futures::{
|
||||
@@ -1194,16 +1194,6 @@ impl WorktreeStore {
|
||||
RelPath::from_proto(&envelope.payload.new_path)?,
|
||||
);
|
||||
let (scan_id, entry) = this.update(&mut cx, |this, cx| {
|
||||
let Some((_, project_id)) = this.downstream_client else {
|
||||
bail!("no downstream client")
|
||||
};
|
||||
let Some(entry) = this.entry_for_id(entry_id, cx) else {
|
||||
bail!("no such entry");
|
||||
};
|
||||
if entry.is_private && project_id != REMOTE_SERVER_PROJECT_ID {
|
||||
bail!("entry is private")
|
||||
}
|
||||
|
||||
let new_worktree = this
|
||||
.worktree_for_id(new_worktree_id, cx)
|
||||
.context("no such worktree")?;
|
||||
@@ -1227,15 +1217,6 @@ impl WorktreeStore {
|
||||
) -> Result<proto::ProjectEntryResponse> {
|
||||
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
|
||||
let worktree = this.update(&mut cx, |this, cx| {
|
||||
let Some((_, project_id)) = this.downstream_client else {
|
||||
bail!("no downstream client")
|
||||
};
|
||||
let Some(entry) = this.entry_for_id(entry_id, cx) else {
|
||||
bail!("no entry")
|
||||
};
|
||||
if entry.is_private && project_id != REMOTE_SERVER_PROJECT_ID {
|
||||
bail!("entry is private")
|
||||
}
|
||||
this.worktree_for_entry(entry_id, cx)
|
||||
.context("worktree not found")
|
||||
})??;
|
||||
@@ -1256,18 +1237,6 @@ impl WorktreeStore {
|
||||
let worktree = this
|
||||
.worktree_for_entry(entry_id, cx)
|
||||
.context("no such worktree")?;
|
||||
|
||||
let Some((_, project_id)) = this.downstream_client else {
|
||||
bail!("no downstream client")
|
||||
};
|
||||
let entry = worktree
|
||||
.read(cx)
|
||||
.entry_for_id(entry_id)
|
||||
.ok_or_else(|| anyhow!("missing entry"))?;
|
||||
if entry.is_private && project_id != REMOTE_SERVER_PROJECT_ID {
|
||||
bail!("entry is private")
|
||||
}
|
||||
|
||||
let scan_id = worktree.read(cx).scan_id();
|
||||
anyhow::Ok((
|
||||
scan_id,
|
||||
|
||||
@@ -64,7 +64,7 @@ use workspace::{
|
||||
DraggedSelection, OpenInTerminal, OpenOptions, OpenVisible, PreviewTabsSettings, SelectedEntry,
|
||||
SplitDirection, Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt},
|
||||
notifications::{DetachAndPromptErr, NotifyTaskExt},
|
||||
};
|
||||
use worktree::CreatedEntry;
|
||||
use zed_actions::workspace::OpenWithSystem;
|
||||
@@ -2677,14 +2677,12 @@ impl ProjectPanel {
|
||||
for task in paste_tasks {
|
||||
match task {
|
||||
PasteTask::Rename(task) => {
|
||||
if let Some(CreatedEntry::Included(entry)) =
|
||||
task.await.notify_async_err(cx)
|
||||
{
|
||||
if let Some(CreatedEntry::Included(entry)) = task.await.log_err() {
|
||||
last_succeed = Some(entry);
|
||||
}
|
||||
}
|
||||
PasteTask::Copy(task) => {
|
||||
if let Some(Some(entry)) = task.await.notify_async_err(cx) {
|
||||
if let Some(Some(entry)) = task.await.log_err() {
|
||||
last_succeed = Some(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ impl WslRemoteConnection {
|
||||
this.can_exec = this.detect_can_exec(shell).await?;
|
||||
this.platform = this.detect_platform(shell).await?;
|
||||
this.remote_binary_path = Some(
|
||||
this.ensure_server_binary(&delegate, release_channel, version, commit, shell, cx)
|
||||
this.ensure_server_binary(&delegate, release_channel, version, commit, cx)
|
||||
.await?,
|
||||
);
|
||||
log::debug!("Detected WSL environment: {this:#?}");
|
||||
@@ -163,7 +163,6 @@ impl WslRemoteConnection {
|
||||
release_channel: ReleaseChannel,
|
||||
version: SemanticVersion,
|
||||
commit: Option<AppCommitSha>,
|
||||
shell: ShellKind,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Arc<RelPath>> {
|
||||
let version_str = match release_channel {
|
||||
@@ -185,13 +184,9 @@ impl WslRemoteConnection {
|
||||
paths::remote_wsl_server_dir_relative().join(RelPath::unix(&binary_name).unwrap());
|
||||
|
||||
if let Some(parent) = dst_path.parent() {
|
||||
let parent = parent.display(PathStyle::Posix);
|
||||
if shell == ShellKind::Nushell {
|
||||
self.run_wsl_command("mkdir", &[&parent]).await
|
||||
} else {
|
||||
self.run_wsl_command("mkdir", &["-p", &parent]).await
|
||||
}
|
||||
.map_err(|e| anyhow!("Failed to create directory: {}", e))?;
|
||||
self.run_wsl_command("mkdir", &["-p", &parent.display(PathStyle::Posix)])
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to create directory: {}", e))?;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
@@ -103,9 +103,7 @@ fn init_logging_server(log_file_path: PathBuf) -> Result<Receiver<Vec<u8>>> {
|
||||
buffer: Vec::new(),
|
||||
});
|
||||
|
||||
env_logger::Builder::new()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.parse_default_env()
|
||||
env_logger::Builder::from_default_env()
|
||||
.target(env_logger::Target::Pipe(target))
|
||||
.format(|buf, record| {
|
||||
let mut log_record = LogRecord::new(record);
|
||||
|
||||
@@ -30,6 +30,13 @@ impl Rope {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Checks that `index`-th byte is the first byte in a UTF-8 code point
|
||||
/// sequence or the end of the string.
|
||||
///
|
||||
/// The start and end of the string (when `index == self.len()`) are
|
||||
/// considered to be boundaries.
|
||||
///
|
||||
/// Returns `false` if `index` is greater than `self.len()`.
|
||||
pub fn is_char_boundary(&self, offset: usize) -> bool {
|
||||
if self.chunks.is_empty() {
|
||||
return offset == 0;
|
||||
@@ -673,6 +680,12 @@ impl<'a> Chunks<'a> {
|
||||
chunks.seek(&range.start, Bias::Right);
|
||||
range.start
|
||||
};
|
||||
let chunk_offset = offset - chunks.start();
|
||||
if let Some(chunk) = chunks.item()
|
||||
&& !chunk.text.is_char_boundary(chunk_offset)
|
||||
{
|
||||
panic!("byte index {} is not a char boundary", offset);
|
||||
}
|
||||
Self {
|
||||
chunks,
|
||||
range,
|
||||
|
||||
@@ -56,7 +56,7 @@ impl Into<BaseKeymapContent> for BaseKeymap {
|
||||
impl Display for BaseKeymap {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BaseKeymap::VSCode => write!(f, "VSCode"),
|
||||
BaseKeymap::VSCode => write!(f, "VS Code"),
|
||||
BaseKeymap::JetBrains => write!(f, "JetBrains"),
|
||||
BaseKeymap::SublimeText => write!(f, "Sublime Text"),
|
||||
BaseKeymap::Atom => write!(f, "Atom"),
|
||||
@@ -71,7 +71,7 @@ impl Display for BaseKeymap {
|
||||
impl BaseKeymap {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const OPTIONS: [(&'static str, Self); 7] = [
|
||||
("VSCode (Default)", Self::VSCode),
|
||||
("VS Code (Default)", Self::VSCode),
|
||||
("Atom", Self::Atom),
|
||||
("JetBrains", Self::JetBrains),
|
||||
("Sublime Text", Self::SublimeText),
|
||||
@@ -82,7 +82,7 @@ impl BaseKeymap {
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub const OPTIONS: [(&'static str, Self); 6] = [
|
||||
("VSCode (Default)", Self::VSCode),
|
||||
("VS Code (Default)", Self::VSCode),
|
||||
("Atom", Self::Atom),
|
||||
("JetBrains", Self::JetBrains),
|
||||
("Sublime Text", Self::SublimeText),
|
||||
|
||||
@@ -318,11 +318,6 @@ pub struct LanguageSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub use_on_type_format: Option<bool>,
|
||||
/// Which code actions to run on save after the formatter.
|
||||
/// These are not run if formatting is off.
|
||||
///
|
||||
/// Default: {} (or {"source.organizeImports": true} for Go).
|
||||
pub code_actions_on_format: Option<HashMap<String, bool>>,
|
||||
/// Whether to perform linked edits of associated ranges, if the language server supports it.
|
||||
/// For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well.
|
||||
///
|
||||
|
||||
@@ -254,9 +254,9 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
),
|
||||
metadata: None,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Buffer Font"),
|
||||
SettingsPageItem::SectionHeader("Fonts"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Family",
|
||||
title: "Buffer Font Family",
|
||||
description: "Font family for editor text",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.theme.buffer_font_family,
|
||||
@@ -266,7 +266,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Size",
|
||||
title: "Buffer Font Size",
|
||||
description: "Font size for editor text",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.theme.buffer_font_size,
|
||||
@@ -276,7 +276,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Weight",
|
||||
title: "Buffer Font Weight",
|
||||
description: "Font weight for editor text (100-900)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.theme.buffer_font_weight,
|
||||
@@ -288,7 +288,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
// todo(settings_ui): This needs custom ui
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
files: USER,
|
||||
title: "Line Height",
|
||||
title: "Buffer Line Height",
|
||||
description: "Line height for editor text",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
@@ -303,7 +303,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
files: USER,
|
||||
title: "Font Features",
|
||||
title: "Buffer Font Features",
|
||||
description: "The OpenType features to enable for rendering in text buffers.",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
@@ -318,7 +318,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
files: USER,
|
||||
title: "Font Fallbacks",
|
||||
title: "Buffer Font Fallbacks",
|
||||
description: "The font fallbacks to use for rendering in text buffers.",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
@@ -331,9 +331,8 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
),
|
||||
metadata: None,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("UI Font"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Family",
|
||||
title: "UI Font Family",
|
||||
description: "Font family for UI elements",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.theme.ui_font_family,
|
||||
@@ -343,7 +342,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Size",
|
||||
title: "UI Font Size",
|
||||
description: "Font size for UI elements",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.theme.ui_font_size,
|
||||
@@ -353,7 +352,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Weight",
|
||||
title: "UI Font Weight",
|
||||
description: "Font weight for UI elements (100-900)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.theme.ui_font_weight,
|
||||
@@ -364,7 +363,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
files: USER,
|
||||
title: "Font Features",
|
||||
title: "UI Font Features",
|
||||
description: "The OpenType features to enable for rendering in UI elements.",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
@@ -379,7 +378,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
files: USER,
|
||||
title: "Font Fallbacks",
|
||||
title: "UI Font Fallbacks",
|
||||
description: "The font fallbacks to use for rendering in the UI.",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
@@ -392,9 +391,8 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
),
|
||||
metadata: None,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Agent Panel Font"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "UI Font Size",
|
||||
title: "Agent Panel UI Font Size",
|
||||
description: "Font size for agent response text in the agent panel. Falls back to the regular UI font size.",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
@@ -410,7 +408,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Buffer Font Size",
|
||||
title: "Agent Panel Buffer Font Size",
|
||||
description: "Font size for user messages text in the agent panel",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.theme.agent_buffer_font_size,
|
||||
@@ -421,7 +419,6 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Cursor"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Multi Cursor Modifier",
|
||||
description: "Modifier key for adding multiple cursors",
|
||||
@@ -434,6 +431,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Cursor"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Cursor Blink",
|
||||
description: "Whether the cursor blinks in the editor",
|
||||
@@ -810,9 +808,9 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Hover Popover"),
|
||||
SettingsPageItem::SectionHeader("Hover"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Enabled",
|
||||
title: "Hover Popover Enabled",
|
||||
description: "Show the informational hover box when moving the mouse over symbols in the editor",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.editor.hover_popover_enabled,
|
||||
@@ -825,7 +823,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
}),
|
||||
// todo(settings ui): add units to this number input
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Delay",
|
||||
title: "Hover Popover Delay",
|
||||
description: "Time to wait in milliseconds before showing the informational hover box",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.editor.hover_popover_delay,
|
||||
@@ -836,9 +834,21 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Drag And Drop Selection"),
|
||||
SettingsPageItem::SectionHeader("Code Actions & Selection"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Enabled",
|
||||
title: "Inline Code Actions",
|
||||
description: "Show code action button at start of buffer line",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.editor.inline_code_actions,
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content.editor.inline_code_actions
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Drag And Drop Selection",
|
||||
description: "Enable drag and drop selection",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
@@ -862,7 +872,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Delay",
|
||||
title: "Drag And Drop Selection Delay",
|
||||
description: "Delay in milliseconds before drag and drop selection starts",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
@@ -1004,18 +1014,6 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Inline Code Actions",
|
||||
description: "Show code action button at start of buffer line",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.editor.inline_code_actions,
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content.editor.inline_code_actions
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Scrollbar"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Show",
|
||||
@@ -5416,27 +5414,6 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
|
||||
metadata: None,
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Code Actions On Format",
|
||||
description: "Additional Code Actions To Run When Formatting",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
language_settings_field(settings_content, |language| {
|
||||
&language.code_actions_on_format
|
||||
})
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
language_settings_field_mut(settings_content, |language| {
|
||||
&mut language.code_actions_on_format
|
||||
})
|
||||
},
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Autoclose"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Use Autoclose",
|
||||
|
||||
@@ -15,7 +15,7 @@ use heck::ToTitleCase as _;
|
||||
use project::WorktreeId;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::{Settings, SettingsContent, SettingsStore};
|
||||
use settings::{SettingsContent, SettingsStore};
|
||||
use std::{
|
||||
any::{Any, TypeId, type_name},
|
||||
cell::RefCell,
|
||||
@@ -466,13 +466,6 @@ pub fn open_settings_editor(
|
||||
// We have to defer this to get the workspace off the stack.
|
||||
|
||||
cx.defer(move |cx| {
|
||||
let current_rem_size: f32 = theme::ThemeSettings::get_global(cx).ui_font_size(cx).into();
|
||||
|
||||
let default_bounds = size(px(900.), px(750.)); // 4:3 Aspect Ratio
|
||||
let default_rem_size = 16.0;
|
||||
let scale_factor = current_rem_size / default_rem_size;
|
||||
let scaled_bounds: gpui::Size<Pixels> = default_bounds.map(|axis| axis * scale_factor);
|
||||
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
titlebar: Some(TitlebarOptions {
|
||||
@@ -485,8 +478,8 @@ pub fn open_settings_editor(
|
||||
is_movable: true,
|
||||
kind: gpui::WindowKind::Floating,
|
||||
window_background: cx.theme().window_background_appearance(),
|
||||
window_min_size: Some(scaled_bounds),
|
||||
window_bounds: Some(WindowBounds::centered(scaled_bounds, cx)),
|
||||
window_min_size: Some(size(px(900.), px(750.))), // 4:3 Aspect Ratio
|
||||
window_bounds: Some(WindowBounds::centered(size(px(900.), px(750.)), cx)),
|
||||
..Default::default()
|
||||
},
|
||||
|window, cx| cx.new(|cx| SettingsWindow::new(Some(workspace_handle), window, cx)),
|
||||
@@ -1702,7 +1695,7 @@ impl SettingsWindow {
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.w_56()
|
||||
.w_64()
|
||||
.p_2p5()
|
||||
.when(cfg!(target_os = "macos"), |c| c.pt_10())
|
||||
.h_full()
|
||||
@@ -2137,7 +2130,7 @@ impl SettingsWindow {
|
||||
}
|
||||
|
||||
return v_flex()
|
||||
.flex_1()
|
||||
.size_full()
|
||||
.pt_6()
|
||||
.pb_8()
|
||||
.px_8()
|
||||
|
||||
@@ -2400,7 +2400,9 @@ impl BufferSnapshot {
|
||||
} else if bias == Bias::Right && offset == self.len() {
|
||||
Anchor::MAX
|
||||
} else {
|
||||
if !self.visible_text.is_char_boundary(offset) {
|
||||
if offset > self.visible_text.len() {
|
||||
panic!("offset {} is out of bounds", offset)
|
||||
} else if !self.visible_text.is_char_boundary(offset) {
|
||||
// find the character
|
||||
let char_start = self.visible_text.floor_char_boundary(offset);
|
||||
// `char_start` must be less than len and a char boundary
|
||||
|
||||
@@ -15,16 +15,9 @@ impl HighlightedLabel {
|
||||
/// Constructs a label with the given characters highlighted.
|
||||
/// Characters are identified by UTF-8 byte position.
|
||||
pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
|
||||
let label = label.into();
|
||||
for &run in &highlight_indices {
|
||||
assert!(
|
||||
label.is_char_boundary(run),
|
||||
"highlight index {run} is not a valid UTF-8 boundary"
|
||||
);
|
||||
}
|
||||
Self {
|
||||
base: LabelLike::new(),
|
||||
label,
|
||||
label: label.into(),
|
||||
highlight_indices,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,8 +369,6 @@ impl Vim {
|
||||
|
||||
let mut selections = Vec::new();
|
||||
let mut row = tail.row();
|
||||
let going_up = tail.row() > head.row();
|
||||
let direction = if going_up { -1 } else { 1 };
|
||||
|
||||
loop {
|
||||
let laid_out_line = map.layout_row(row, &text_layout_details);
|
||||
@@ -401,18 +399,13 @@ impl Vim {
|
||||
|
||||
selections.push(selection);
|
||||
}
|
||||
|
||||
// When dealing with soft wrapped lines, it's possible that
|
||||
// `row` ends up being set to a value other than `head.row()` as
|
||||
// `head.row()` might be a `DisplayPoint` mapped to a soft
|
||||
// wrapped line, hence the need for `<=` and `>=` instead of
|
||||
// `==`.
|
||||
if going_up && row <= head.row() || !going_up && row >= head.row() {
|
||||
if row == head.row() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the next or previous buffer row where the `row` should
|
||||
// be moved to, so that wrapped lines are skipped.
|
||||
// Move to the next or previous buffer row, ensuring that
|
||||
// wrapped lines are handled correctly.
|
||||
let direction = if tail.row() > head.row() { -1 } else { 1 };
|
||||
row = start_of_relative_buffer_row(map, DisplayPoint::new(row, 0), direction).row();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition.workspace = true
|
||||
name = "zed"
|
||||
version = "0.209.6"
|
||||
version = "0.210.0"
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
stable
|
||||
dev
|
||||
|
||||
@@ -33,7 +33,7 @@ pub struct EditPrediction {
|
||||
pub snapshot: BufferSnapshot,
|
||||
pub edit_preview: EditPreview,
|
||||
// We keep a reference to the buffer so that we do not need to reload it from disk when applying the prediction.
|
||||
_buffer: Entity<Buffer>,
|
||||
pub buffer: Entity<Buffer>,
|
||||
}
|
||||
|
||||
impl EditPrediction {
|
||||
@@ -108,7 +108,7 @@ impl EditPrediction {
|
||||
edits,
|
||||
snapshot,
|
||||
edit_preview,
|
||||
_buffer: buffer,
|
||||
buffer,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -184,6 +184,10 @@ pub fn interpolate_edits(
|
||||
if edits.is_empty() { None } else { Some(edits) }
|
||||
}
|
||||
|
||||
pub fn line_range_to_point_range(range: Range<predict_edits_v3::Line>) -> Range<language::Point> {
|
||||
language::Point::new(range.start.0, 0)..language::Point::new(range.end.0, 0)
|
||||
}
|
||||
|
||||
fn edits_from_response(
|
||||
edits: &[predict_edits_v3::Edit],
|
||||
snapshot: &TextBufferSnapshot,
|
||||
@@ -191,12 +195,14 @@ fn edits_from_response(
|
||||
edits
|
||||
.iter()
|
||||
.flat_map(|edit| {
|
||||
let old_text = snapshot.text_for_range(edit.range.clone());
|
||||
let point_range = line_range_to_point_range(edit.range.clone());
|
||||
let offset = point_range.to_offset(snapshot).start;
|
||||
let old_text = snapshot.text_for_range(point_range);
|
||||
|
||||
excerpt_edits_from_response(
|
||||
old_text.collect::<Cow<str>>(),
|
||||
&edit.content,
|
||||
edit.range.start,
|
||||
offset,
|
||||
&snapshot,
|
||||
)
|
||||
})
|
||||
@@ -252,6 +258,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use cloud_llm_client::predict_edits_v3;
|
||||
use edit_prediction_context::Line;
|
||||
use gpui::{App, Entity, TestAppContext, prelude::*};
|
||||
use indoc::indoc;
|
||||
use language::{Buffer, ToOffset as _};
|
||||
@@ -278,7 +285,7 @@ mod tests {
|
||||
// TODO cover more cases when multi-file is supported
|
||||
let big_edits = vec![predict_edits_v3::Edit {
|
||||
path: PathBuf::from("test.txt").into(),
|
||||
range: 0..old.len(),
|
||||
range: Line(0)..Line(old.lines().count() as u32),
|
||||
content: new.into(),
|
||||
}];
|
||||
|
||||
@@ -317,7 +324,7 @@ mod tests {
|
||||
edits,
|
||||
snapshot: cx.read(|cx| buffer.read(cx).snapshot()),
|
||||
path: Path::new("test.txt").into(),
|
||||
_buffer: buffer.clone(),
|
||||
buffer: buffer.clone(),
|
||||
edit_preview,
|
||||
};
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ use gpui::{
|
||||
App, Entity, EntityId, Global, SemanticVersion, SharedString, Subscription, Task, WeakEntity,
|
||||
http_client, prelude::*,
|
||||
};
|
||||
use language::BufferSnapshot;
|
||||
use language::{Buffer, DiagnosticSet, LanguageServerId, ToOffset as _, ToPoint};
|
||||
use language::{BufferSnapshot, TextBufferSnapshot};
|
||||
use language_model::{LlmApiToken, RefreshLlmTokenListener};
|
||||
use project::Project;
|
||||
use release_channel::AppVersion;
|
||||
@@ -106,30 +106,40 @@ struct ZetaProject {
|
||||
current_prediction: Option<CurrentEditPrediction>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct CurrentEditPrediction {
|
||||
pub requested_by_buffer_id: EntityId,
|
||||
pub prediction: EditPrediction,
|
||||
}
|
||||
|
||||
impl CurrentEditPrediction {
|
||||
fn should_replace_prediction(
|
||||
&self,
|
||||
old_prediction: &Self,
|
||||
snapshot: &TextBufferSnapshot,
|
||||
) -> bool {
|
||||
if self.requested_by_buffer_id != old_prediction.requested_by_buffer_id {
|
||||
fn should_replace_prediction(&self, old_prediction: &Self, cx: &App) -> bool {
|
||||
let Some(new_edits) = self
|
||||
.prediction
|
||||
.interpolate(&self.prediction.buffer.read(cx))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if self.prediction.buffer != old_prediction.prediction.buffer {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(old_edits) = old_prediction.prediction.interpolate(snapshot) else {
|
||||
let Some(old_edits) = old_prediction
|
||||
.prediction
|
||||
.interpolate(&old_prediction.prediction.buffer.read(cx))
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Some(new_edits) = self.prediction.interpolate(snapshot) else {
|
||||
return false;
|
||||
};
|
||||
if old_edits.len() == 1 && new_edits.len() == 1 {
|
||||
// This reduces the occurrence of UI thrash from replacing edits
|
||||
//
|
||||
// TODO: This is fairly arbitrary - should have a more general heuristic that handles multiple edits.
|
||||
if self.requested_by_buffer_id == self.prediction.buffer.entity_id()
|
||||
&& self.requested_by_buffer_id == old_prediction.prediction.buffer.entity_id()
|
||||
&& old_edits.len() == 1
|
||||
&& new_edits.len() == 1
|
||||
{
|
||||
let (old_range, old_text) = &old_edits[0];
|
||||
let (new_range, new_text) = &new_edits[0];
|
||||
new_range == old_range && new_text.starts_with(old_text)
|
||||
@@ -421,8 +431,7 @@ impl Zeta {
|
||||
.current_prediction
|
||||
.as_ref()
|
||||
.is_none_or(|old_prediction| {
|
||||
new_prediction
|
||||
.should_replace_prediction(&old_prediction, buffer.read(cx))
|
||||
new_prediction.should_replace_prediction(&old_prediction, cx)
|
||||
})
|
||||
{
|
||||
project_state.current_prediction = Some(new_prediction);
|
||||
@@ -926,7 +935,7 @@ fn make_cloud_request(
|
||||
referenced_declarations.push(predict_edits_v3::ReferencedDeclaration {
|
||||
path: path.as_std_path().into(),
|
||||
text: text.into(),
|
||||
range: snippet.declaration.item_range(),
|
||||
range: snippet.declaration.item_line_range(),
|
||||
text_is_truncated,
|
||||
signature_range: snippet.declaration.signature_range_in_item_text(),
|
||||
parent_index,
|
||||
@@ -954,8 +963,12 @@ fn make_cloud_request(
|
||||
predict_edits_v3::PredictEditsRequest {
|
||||
excerpt_path,
|
||||
excerpt: context.excerpt_text.body,
|
||||
excerpt_line_range: context.excerpt.line_range,
|
||||
excerpt_range: context.excerpt.range,
|
||||
cursor_offset: context.cursor_offset_in_excerpt,
|
||||
cursor_point: predict_edits_v3::Point {
|
||||
line: predict_edits_v3::Line(context.cursor_point.row),
|
||||
column: context.cursor_point.column,
|
||||
},
|
||||
referenced_declarations,
|
||||
signatures,
|
||||
excerpt_parent,
|
||||
@@ -992,7 +1005,7 @@ fn add_signature(
|
||||
text: text.into(),
|
||||
text_is_truncated,
|
||||
parent_index,
|
||||
range: parent_declaration.signature_range(),
|
||||
range: parent_declaration.signature_line_range(),
|
||||
});
|
||||
declaration_to_signature_index.insert(declaration_id, signature_index);
|
||||
Some(signature_index)
|
||||
@@ -1007,7 +1020,8 @@ mod tests {
|
||||
|
||||
use client::UserStore;
|
||||
use clock::FakeSystemClock;
|
||||
use cloud_llm_client::predict_edits_v3;
|
||||
use cloud_llm_client::predict_edits_v3::{self, Point};
|
||||
use edit_prediction_context::Line;
|
||||
use futures::{
|
||||
AsyncReadExt, StreamExt,
|
||||
channel::{mpsc, oneshot},
|
||||
@@ -1067,7 +1081,7 @@ mod tests {
|
||||
request_id: Uuid::new_v4(),
|
||||
edits: vec![predict_edits_v3::Edit {
|
||||
path: Path::new(path!("root/1.txt")).into(),
|
||||
range: 0..snapshot1.len(),
|
||||
range: Line(0)..Line(snapshot1.max_point().row + 1),
|
||||
content: "Hello!\nHow are you?\nBye".into(),
|
||||
}],
|
||||
debug_info: None,
|
||||
@@ -1083,7 +1097,6 @@ mod tests {
|
||||
});
|
||||
|
||||
// Prediction for another file
|
||||
|
||||
let prediction_task = zeta.update(cx, |zeta, cx| {
|
||||
zeta.refresh_prediction(&project, &buffer1, position, cx)
|
||||
});
|
||||
@@ -1093,14 +1106,13 @@ mod tests {
|
||||
request_id: Uuid::new_v4(),
|
||||
edits: vec![predict_edits_v3::Edit {
|
||||
path: Path::new(path!("root/2.txt")).into(),
|
||||
range: 0..snapshot1.len(),
|
||||
range: Line(0)..Line(snapshot1.max_point().row + 1),
|
||||
content: "Hola!\nComo estas?\nAdios".into(),
|
||||
}],
|
||||
debug_info: None,
|
||||
})
|
||||
.unwrap();
|
||||
prediction_task.await.unwrap();
|
||||
|
||||
zeta.read_with(cx, |zeta, cx| {
|
||||
let prediction = zeta
|
||||
.current_prediction_for_buffer(&buffer1, &project, cx)
|
||||
@@ -1159,14 +1171,20 @@ mod tests {
|
||||
request.excerpt_path.as_ref(),
|
||||
Path::new(path!("root/foo.md"))
|
||||
);
|
||||
assert_eq!(request.cursor_offset, 10);
|
||||
assert_eq!(
|
||||
request.cursor_point,
|
||||
Point {
|
||||
line: Line(1),
|
||||
column: 3
|
||||
}
|
||||
);
|
||||
|
||||
respond_tx
|
||||
.send(predict_edits_v3::PredictEditsResponse {
|
||||
request_id: Uuid::new_v4(),
|
||||
edits: vec![predict_edits_v3::Edit {
|
||||
path: Path::new(path!("root/foo.md")).into(),
|
||||
range: 0..snapshot.len(),
|
||||
range: Line(0)..Line(snapshot.max_point().row + 1),
|
||||
content: "Hello!\nHow are you?\nBye".into(),
|
||||
}],
|
||||
debug_info: None,
|
||||
@@ -1244,7 +1262,7 @@ mod tests {
|
||||
request_id: Uuid::new_v4(),
|
||||
edits: vec![predict_edits_v3::Edit {
|
||||
path: Path::new(path!("root/foo.md")).into(),
|
||||
range: 0..snapshot.len(),
|
||||
range: Line(0)..Line(snapshot.max_point().row + 1),
|
||||
content: "Hello!\nHow are you?\nBye".into(),
|
||||
}],
|
||||
debug_info: None,
|
||||
|
||||
@@ -98,10 +98,11 @@ struct Zeta2Args {
|
||||
|
||||
#[derive(clap::ValueEnum, Default, Debug, Clone)]
|
||||
enum PromptFormat {
|
||||
#[default]
|
||||
MarkedExcerpt,
|
||||
LabeledSections,
|
||||
OnlySnippets,
|
||||
#[default]
|
||||
NumberedLines,
|
||||
}
|
||||
|
||||
impl Into<predict_edits_v3::PromptFormat> for PromptFormat {
|
||||
@@ -110,6 +111,7 @@ impl Into<predict_edits_v3::PromptFormat> for PromptFormat {
|
||||
Self::MarkedExcerpt => predict_edits_v3::PromptFormat::MarkedExcerpt,
|
||||
Self::LabeledSections => predict_edits_v3::PromptFormat::LabeledSections,
|
||||
Self::OnlySnippets => predict_edits_v3::PromptFormat::OnlySnippets,
|
||||
Self::NumberedLines => predict_edits_v3::PromptFormat::NumberedLines,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,60 @@ To ensure you're using your billing method of choice, [open a new Claude Code th
|
||||
|
||||
The first time you create a Claude Code thread, Zed will install [@zed-industries/claude-code-acp](https://github.com/zed-industries/claude-code-acp). This installation is only available to Zed and is kept up to date as you use the agent.
|
||||
|
||||
Zed will always use this managed version of Claude Code even if you have it installed globally.
|
||||
Zed will always use this managed version of the Claude Code adapter, which includes a vendored version of the Claude Code CLI, even if you have it installed globally.
|
||||
|
||||
If you want to override the executable used by the adapter, you can set the `CLAUDE_CODE_EXECUTABLE` environment variable in your settings to the path of your preferred executable.
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_servers": {
|
||||
"claude": {
|
||||
"env": {
|
||||
"CLAUDE_CODE_EXECUTABLE": "/path/to/alternate-claude-code-executable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Codex CLI
|
||||
|
||||
You can also run [Codex CLI](https://github.com/openai/codex) directly via Zed's [agent panel](./agent-panel.md).
|
||||
Under the hood, Zed runs Codex CLI and communicates to it over ACP, through [a dedicated adapter](https://github.com/zed-industries/codex-acp).
|
||||
|
||||
### Getting Started
|
||||
|
||||
As of Zed Stable v0.208 you should be able to use Codex directly from Zed. Open the agent panel with {#kb agent::ToggleFocus}, and then use the `+` button in the top right to start a new Codex thread.
|
||||
|
||||
If you'd like to bind this to a keyboard shortcut, you can do so by editing your `keymap.json` file via the `zed: open keymap` command to include:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-alt-c": ["agent::NewExternalAgentThread", { "agent": "codex" }]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
Authentication to Zed's Codex installation is decoupled entirely from Zed's agent. That is to say, an OpenAI API key added via the [Zed Agent's settings](./llm-providers.md#openai) will _not_ be utilized by Codex for authentication and billing.
|
||||
|
||||
To ensure you're using your billing method of choice, [open a new Codex thread](./agent-panel.md#new-thread). The first time you will be prompted to authenticate with one of three methods:
|
||||
|
||||
1. Login with ChatGPT - allows you to use your existing, paid ChatGPT subscription. _Note: This method isn't currently supported in remote projects_
|
||||
2. `CODEX_API_KEY` - uses an API key you have set in your environment under the variable `CODEX_API_KEY`.
|
||||
3. `OPENAI_API_KEY` - uses an API key you have set in your environment under the variable `OPENAI_API_KEY`.
|
||||
|
||||
If you are already logged in and want to change your authentication method, type `/logout` in the thread and authenticate again.
|
||||
|
||||
#### Installation
|
||||
|
||||
The first time you create a Codex thread, Zed will install [codex-acp](https://github.com/zed-industries/codex-acp). This installation is only available to Zed and is kept up to date as you use the agent.
|
||||
|
||||
Zed will always use this managed version of Codex even if you have it installed globally.
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
@@ -40,9 +40,6 @@ We’re working hard to expand the models supported by Zed’s subscription offe
|
||||
| Grok 4 Fast | X.ai | Input | $0.20 | $0.22 |
|
||||
| | X.ai | Output | $0.50 | $0.55 |
|
||||
| | X.ai | Cached Input | $0.05 | $0.055 |
|
||||
| Grok 4 Fast | X.ai | Input | $0.20 | $0.22 |
|
||||
| | X.ai | Output | $0.50 | $0.55 |
|
||||
| | X.ai | Cached Input | $0.05 | $0.055 |
|
||||
| Grok 4 (Non-Reasoning) | X.ai | Input | $0.20 | $0.22 |
|
||||
| | X.ai | Output | $0.50 | $0.55 |
|
||||
| | X.ai | Cached Input | $0.05 | $0.055 |
|
||||
|
||||
@@ -40,7 +40,7 @@ This is mostly a formality on Wednesday's minor update releases, but can be bene
|
||||
1. Check the release assets.
|
||||
|
||||
- Ensure the stable and preview release jobs have finished without error.
|
||||
- Ensure each draft has the proper number of assets—releases currently have 10 assets each.
|
||||
- Ensure each draft has the proper number of assets—releases currently have 11 assets each.
|
||||
- Download the artifacts for each release draft and test that you can run them locally.
|
||||
|
||||
1. Publish the drafts.
|
||||
|
||||
@@ -49,8 +49,8 @@ You can configure Zed to format code using `eslint --fix` by running the ESLint
|
||||
{
|
||||
"languages": {
|
||||
"JavaScript": {
|
||||
"code_actions_on_format": {
|
||||
"source.fixAll.eslint": true
|
||||
"formatter": {
|
||||
"code_action": "source.fixAll.eslint"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,8 @@ You can also only execute a single ESLint rule when using `fixAll`:
|
||||
{
|
||||
"languages": {
|
||||
"JavaScript": {
|
||||
"code_actions_on_format": {
|
||||
"source.fixAll.eslint": true
|
||||
"formatter": {
|
||||
"code_action": "source.fixAll.eslint"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -92,8 +92,8 @@ the formatter:
|
||||
{
|
||||
"languages": {
|
||||
"JavaScript": {
|
||||
"code_actions_on_format": {
|
||||
"source.fixAll.eslint": true
|
||||
"formatter": {
|
||||
"code_action": "source.fixAll.eslint"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user