Merge branch 'main' into mention-more
This commit is contained in:
commit
26befa1ec6
54 changed files with 1269 additions and 291 deletions
35
.github/ISSUE_TEMPLATE/07_bug_windows_alpha.yml
vendored
Normal file
35
.github/ISSUE_TEMPLATE/07_bug_windows_alpha.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: Bug Report (Windows)
|
||||
description: Zed Windows-Related Bugs
|
||||
type: "Bug"
|
||||
labels: ["windows"]
|
||||
title: "Windows: <a short description of the Windows bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected Behavior**:
|
||||
**Actual Behavior**:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: copy system specs into clipboard"
|
||||
validations:
|
||||
required: true
|
163
.github/actions/run_tests_windows/action.yml
vendored
163
.github/actions/run_tests_windows/action.yml
vendored
|
@ -20,7 +20,168 @@ runs:
|
|||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Configure crash dumps
|
||||
shell: powershell
|
||||
run: |
|
||||
# Record the start time for this CI run
|
||||
$runStartTime = Get-Date
|
||||
$runStartTimeStr = $runStartTime.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
Write-Host "CI run started at: $runStartTimeStr"
|
||||
|
||||
# Save the timestamp for later use
|
||||
echo "CI_RUN_START_TIME=$($runStartTime.Ticks)" >> $env:GITHUB_ENV
|
||||
|
||||
# Create crash dump directory in workspace (non-persistent)
|
||||
$dumpPath = "$env:GITHUB_WORKSPACE\crash_dumps"
|
||||
New-Item -ItemType Directory -Force -Path $dumpPath | Out-Null
|
||||
|
||||
Write-Host "Setting up crash dump detection..."
|
||||
Write-Host "Workspace dump path: $dumpPath"
|
||||
|
||||
# Note: We're NOT modifying registry on stateful runners
|
||||
# Instead, we'll check default Windows crash locations after tests
|
||||
|
||||
- name: Run tests
|
||||
shell: powershell
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
run: |
|
||||
$env:RUST_BACKTRACE = "full"
|
||||
|
||||
# Enable Windows debugging features
|
||||
$env:_NT_SYMBOL_PATH = "srv*https://msdl.microsoft.com/download/symbols"
|
||||
|
||||
# .NET crash dump environment variables (ephemeral)
|
||||
$env:COMPlus_DbgEnableMiniDump = "1"
|
||||
$env:COMPlus_DbgMiniDumpType = "4"
|
||||
$env:COMPlus_CreateDumpDiagnostics = "1"
|
||||
|
||||
cargo nextest run --workspace --no-fail-fast
|
||||
continue-on-error: true
|
||||
|
||||
- name: Analyze crash dumps
|
||||
if: always()
|
||||
shell: powershell
|
||||
run: |
|
||||
Write-Host "Checking for crash dumps..."
|
||||
|
||||
# Get the CI run start time from the environment
|
||||
$runStartTime = [DateTime]::new([long]$env:CI_RUN_START_TIME)
|
||||
Write-Host "Only analyzing dumps created after: $($runStartTime.ToString('yyyy-MM-dd HH:mm:ss'))"
|
||||
|
||||
# Check all possible crash dump locations
|
||||
$searchPaths = @(
|
||||
"$env:GITHUB_WORKSPACE\crash_dumps",
|
||||
"$env:LOCALAPPDATA\CrashDumps",
|
||||
"$env:TEMP",
|
||||
"$env:GITHUB_WORKSPACE",
|
||||
"$env:USERPROFILE\AppData\Local\CrashDumps",
|
||||
"C:\Windows\System32\config\systemprofile\AppData\Local\CrashDumps"
|
||||
)
|
||||
|
||||
$dumps = @()
|
||||
foreach ($path in $searchPaths) {
|
||||
if (Test-Path $path) {
|
||||
Write-Host "Searching in: $path"
|
||||
$found = Get-ChildItem "$path\*.dmp" -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.CreationTime -gt $runStartTime
|
||||
}
|
||||
if ($found) {
|
||||
$dumps += $found
|
||||
Write-Host " Found $($found.Count) dump(s) from this CI run"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($dumps) {
|
||||
Write-Host "Found $($dumps.Count) crash dump(s)"
|
||||
|
||||
# Install debugging tools if not present
|
||||
$cdbPath = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"
|
||||
if (-not (Test-Path $cdbPath)) {
|
||||
Write-Host "Installing Windows Debugging Tools..."
|
||||
$url = "https://go.microsoft.com/fwlink/?linkid=2237387"
|
||||
Invoke-WebRequest -Uri $url -OutFile winsdksetup.exe
|
||||
Start-Process -Wait winsdksetup.exe -ArgumentList "/features OptionId.WindowsDesktopDebuggers /quiet"
|
||||
}
|
||||
|
||||
foreach ($dump in $dumps) {
|
||||
Write-Host "`n=================================="
|
||||
Write-Host "Analyzing crash dump: $($dump.Name)"
|
||||
Write-Host "Size: $([math]::Round($dump.Length / 1MB, 2)) MB"
|
||||
Write-Host "Time: $($dump.CreationTime)"
|
||||
Write-Host "=================================="
|
||||
|
||||
# Set symbol path
|
||||
$env:_NT_SYMBOL_PATH = "srv*C:\symbols*https://msdl.microsoft.com/download/symbols"
|
||||
|
||||
# Run analysis
|
||||
$analysisOutput = & $cdbPath -z $dump.FullName -c "!analyze -v; ~*k; lm; q" 2>&1 | Out-String
|
||||
|
||||
# Extract key information
|
||||
if ($analysisOutput -match "ExceptionCode:\s*([\w]+)") {
|
||||
Write-Host "Exception Code: $($Matches[1])"
|
||||
if ($Matches[1] -eq "c0000005") {
|
||||
Write-Host "Exception Type: ACCESS VIOLATION"
|
||||
}
|
||||
}
|
||||
|
||||
if ($analysisOutput -match "EXCEPTION_RECORD:\s*(.+)") {
|
||||
Write-Host "Exception Record: $($Matches[1])"
|
||||
}
|
||||
|
||||
if ($analysisOutput -match "FAULTING_IP:\s*\n(.+)") {
|
||||
Write-Host "Faulting Instruction: $($Matches[1])"
|
||||
}
|
||||
|
||||
# Save full analysis
|
||||
$analysisFile = "$($dump.FullName).analysis.txt"
|
||||
$analysisOutput | Out-File -FilePath $analysisFile
|
||||
Write-Host "`nFull analysis saved to: $analysisFile"
|
||||
|
||||
# Print stack trace section
|
||||
Write-Host "`n--- Stack Trace Preview ---"
|
||||
$stackSection = $analysisOutput -split "STACK_TEXT:" | Select-Object -Last 1
|
||||
$stackLines = $stackSection -split "`n" | Select-Object -First 20
|
||||
$stackLines | ForEach-Object { Write-Host $_ }
|
||||
Write-Host "--- End Stack Trace Preview ---"
|
||||
}
|
||||
|
||||
Write-Host "`n⚠️ Crash dumps detected! Download the 'crash-dumps' artifact for detailed analysis."
|
||||
|
||||
# Copy dumps to workspace for artifact upload
|
||||
$artifactPath = "$env:GITHUB_WORKSPACE\crash_dumps_collected"
|
||||
New-Item -ItemType Directory -Force -Path $artifactPath | Out-Null
|
||||
|
||||
foreach ($dump in $dumps) {
|
||||
$destName = "$($dump.Directory.Name)_$($dump.Name)"
|
||||
Copy-Item $dump.FullName -Destination "$artifactPath\$destName"
|
||||
if (Test-Path "$($dump.FullName).analysis.txt") {
|
||||
Copy-Item "$($dump.FullName).analysis.txt" -Destination "$artifactPath\$destName.analysis.txt"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Copied $($dumps.Count) dump(s) to artifact directory"
|
||||
} else {
|
||||
Write-Host "No crash dumps from this CI run found"
|
||||
}
|
||||
|
||||
- name: Upload crash dumps
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crash-dumps-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: |
|
||||
crash_dumps_collected/*.dmp
|
||||
crash_dumps_collected/*.txt
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
- name: Check test results
|
||||
shell: powershell
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
# Re-check test results to fail the job if tests failed
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Tests failed with exit code: $LASTEXITCODE"
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -205,6 +205,8 @@ dependencies = [
|
|||
"gpui",
|
||||
"gpui_tokio",
|
||||
"handlebars 4.5.0",
|
||||
"html_to_markdown",
|
||||
"http_client",
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
|
|
|
@ -174,6 +174,10 @@ impl Diff {
|
|||
buffer_text
|
||||
)
|
||||
}
|
||||
|
||||
pub fn has_revealed_range(&self, cx: &App) -> bool {
|
||||
self.multibuffer().read(cx).excerpt_paths().next().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PendingDiff {
|
||||
|
|
|
@ -29,8 +29,14 @@ impl Terminal {
|
|||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
command: cx
|
||||
.new(|cx| Markdown::new(command.into(), Some(language_registry.clone()), None, cx)),
|
||||
command: cx.new(|cx| {
|
||||
Markdown::new(
|
||||
format!("```\n{}\n```", command).into(),
|
||||
Some(language_registry.clone()),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
working_dir,
|
||||
terminal,
|
||||
started_at: Instant::now(),
|
||||
|
|
|
@ -17,8 +17,6 @@ use util::{
|
|||
pub struct ActionLog {
|
||||
/// Buffers that we want to notify the model about when they change.
|
||||
tracked_buffers: BTreeMap<Entity<Buffer>, TrackedBuffer>,
|
||||
/// Has the model edited a file since it last checked diagnostics?
|
||||
edited_since_project_diagnostics_check: bool,
|
||||
/// The project this action log is associated with
|
||||
project: Entity<Project>,
|
||||
}
|
||||
|
@ -28,7 +26,6 @@ impl ActionLog {
|
|||
pub fn new(project: Entity<Project>) -> Self {
|
||||
Self {
|
||||
tracked_buffers: BTreeMap::default(),
|
||||
edited_since_project_diagnostics_check: false,
|
||||
project,
|
||||
}
|
||||
}
|
||||
|
@ -37,16 +34,6 @@ impl ActionLog {
|
|||
&self.project
|
||||
}
|
||||
|
||||
/// Notifies a diagnostics check
|
||||
pub fn checked_project_diagnostics(&mut self) {
|
||||
self.edited_since_project_diagnostics_check = false;
|
||||
}
|
||||
|
||||
/// Returns true if any files have been edited since the last project diagnostics check
|
||||
pub fn has_edited_files_since_project_diagnostics_check(&self) -> bool {
|
||||
self.edited_since_project_diagnostics_check
|
||||
}
|
||||
|
||||
pub fn latest_snapshot(&self, buffer: &Entity<Buffer>) -> Option<text::BufferSnapshot> {
|
||||
Some(self.tracked_buffers.get(buffer)?.snapshot.clone())
|
||||
}
|
||||
|
@ -543,14 +530,11 @@ impl ActionLog {
|
|||
|
||||
/// Mark a buffer as created by agent, so we can refresh it in the context
|
||||
pub fn buffer_created(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
|
||||
self.edited_since_project_diagnostics_check = true;
|
||||
self.track_buffer_internal(buffer.clone(), true, cx);
|
||||
}
|
||||
|
||||
/// Mark a buffer as edited by agent, so we can refresh it in the context
|
||||
pub fn buffer_edited(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
|
||||
self.edited_since_project_diagnostics_check = true;
|
||||
|
||||
let tracked_buffer = self.track_buffer_internal(buffer.clone(), false, cx);
|
||||
if let TrackedBufferStatus::Deleted = tracked_buffer.status {
|
||||
tracked_buffer.status = TrackedBufferStatus::Modified;
|
||||
|
|
|
@ -27,6 +27,8 @@ fs.workspace = true
|
|||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
handlebars = { workspace = true, features = ["rust-embed"] }
|
||||
html_to_markdown.workspace = true
|
||||
http_client.workspace = true
|
||||
indoc.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::{AgentResponseEvent, Thread, templates::Templates};
|
||||
use crate::{
|
||||
CopyPathTool, CreateDirectoryTool, EditFileTool, FindPathTool, GrepTool, ListDirectoryTool,
|
||||
MessageContent, MovePathTool, NowTool, OpenTool, ReadFileTool, TerminalTool, ThinkingTool,
|
||||
ToolCallAuthorization, WebSearchTool,
|
||||
CopyPathTool, CreateDirectoryTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool,
|
||||
GrepTool, ListDirectoryTool, MessageContent, MovePathTool, NowTool, OpenTool, ReadFileTool,
|
||||
TerminalTool, ThinkingTool, ToolCallAuthorization, WebSearchTool,
|
||||
};
|
||||
use acp_thread::ModelSelector;
|
||||
use agent_client_protocol as acp;
|
||||
|
@ -420,11 +420,13 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
|
|||
let mut thread = Thread::new(project.clone(), agent.project_context.clone(), action_log.clone(), agent.templates.clone(), default_model);
|
||||
thread.add_tool(CreateDirectoryTool::new(project.clone()));
|
||||
thread.add_tool(CopyPathTool::new(project.clone()));
|
||||
thread.add_tool(DiagnosticsTool::new(project.clone()));
|
||||
thread.add_tool(MovePathTool::new(project.clone()));
|
||||
thread.add_tool(ListDirectoryTool::new(project.clone()));
|
||||
thread.add_tool(OpenTool::new(project.clone()));
|
||||
thread.add_tool(ThinkingTool);
|
||||
thread.add_tool(FindPathTool::new(project.clone()));
|
||||
thread.add_tool(FetchTool::new(project.read(cx).client().http_client()));
|
||||
thread.add_tool(GrepTool::new(project.clone()));
|
||||
thread.add_tool(ReadFileTool::new(project.clone(), action_log));
|
||||
thread.add_tool(EditFileTool::new(cx.entity()));
|
||||
|
|
|
@ -5,9 +5,11 @@ use action_log::ActionLog;
|
|||
use agent_client_protocol::{self as acp};
|
||||
use anyhow::Result;
|
||||
use client::{Client, UserStore};
|
||||
use fs::FakeFs;
|
||||
use fs::{FakeFs, Fs};
|
||||
use futures::channel::mpsc::UnboundedReceiver;
|
||||
use gpui::{AppContext, Entity, Task, TestAppContext, http_client::FakeHttpClient};
|
||||
use gpui::{
|
||||
App, AppContext, Entity, Task, TestAppContext, UpdateGlobal, http_client::FakeHttpClient,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId,
|
||||
|
@ -20,6 +22,7 @@ use reqwest_client::ReqwestClient;
|
|||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc, time::Duration};
|
||||
use util::path;
|
||||
|
@ -283,6 +286,63 @@ async fn test_tool_authorization(cx: &mut TestAppContext) {
|
|||
})
|
||||
]
|
||||
);
|
||||
|
||||
// Simulate yet another tool call.
|
||||
fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: "tool_id_3".into(),
|
||||
name: ToolRequiringPermission.name().into(),
|
||||
raw_input: "{}".into(),
|
||||
input: json!({}),
|
||||
is_input_complete: true,
|
||||
},
|
||||
));
|
||||
fake_model.end_last_completion_stream();
|
||||
|
||||
// Respond by always allowing tools.
|
||||
let tool_call_auth_3 = next_tool_call_authorization(&mut events).await;
|
||||
tool_call_auth_3
|
||||
.response
|
||||
.send(tool_call_auth_3.options[0].id.clone())
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
let completion = fake_model.pending_completions().pop().unwrap();
|
||||
let message = completion.messages.last().unwrap();
|
||||
assert_eq!(
|
||||
message.content,
|
||||
vec![MessageContent::ToolResult(LanguageModelToolResult {
|
||||
tool_use_id: tool_call_auth_3.tool_call.id.0.to_string().into(),
|
||||
tool_name: ToolRequiringPermission.name().into(),
|
||||
is_error: false,
|
||||
content: "Allowed".into(),
|
||||
output: Some("Allowed".into())
|
||||
})]
|
||||
);
|
||||
|
||||
// Simulate a final tool call, ensuring we don't trigger authorization.
|
||||
fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: "tool_id_4".into(),
|
||||
name: ToolRequiringPermission.name().into(),
|
||||
raw_input: "{}".into(),
|
||||
input: json!({}),
|
||||
is_input_complete: true,
|
||||
},
|
||||
));
|
||||
fake_model.end_last_completion_stream();
|
||||
cx.run_until_parked();
|
||||
let completion = fake_model.pending_completions().pop().unwrap();
|
||||
let message = completion.messages.last().unwrap();
|
||||
assert_eq!(
|
||||
message.content,
|
||||
vec![MessageContent::ToolResult(LanguageModelToolResult {
|
||||
tool_use_id: "tool_id_4".into(),
|
||||
tool_name: ToolRequiringPermission.name().into(),
|
||||
is_error: false,
|
||||
content: "Allowed".into(),
|
||||
output: Some("Allowed".into())
|
||||
})]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -774,13 +834,17 @@ impl TestModel {
|
|||
|
||||
async fn setup(cx: &mut TestAppContext, model: TestModel) -> ThreadTest {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
|
||||
cx.update(|cx| {
|
||||
settings::init(cx);
|
||||
watch_settings(fs.clone(), cx);
|
||||
Project::init_settings(cx);
|
||||
agent_settings::init(cx);
|
||||
});
|
||||
let templates = Templates::new();
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(path!("/test"), json!({})).await;
|
||||
let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
|
||||
|
||||
|
@ -842,3 +906,26 @@ fn init_logger() {
|
|||
env_logger::init();
|
||||
}
|
||||
}
|
||||
|
||||
fn watch_settings(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
let fs = fs.clone();
|
||||
cx.spawn({
|
||||
async move |cx| {
|
||||
let mut new_settings_content_rx = settings::watch_config_file(
|
||||
cx.background_executor(),
|
||||
fs,
|
||||
paths::settings_file().clone(),
|
||||
);
|
||||
|
||||
while let Some(new_settings_content) = new_settings_content_rx.next().await {
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |settings, cx| {
|
||||
settings.set_user_settings(&new_settings_content, cx)
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
|
|
@ -110,9 +110,9 @@ impl AgentTool for ToolRequiringPermission {
|
|||
event_stream: ToolCallEventStream,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
let auth_check = event_stream.authorize("Authorize?".into());
|
||||
let authorize = event_stream.authorize("Authorize?", cx);
|
||||
cx.foreground_executor().spawn(async move {
|
||||
auth_check.await?;
|
||||
authorize.await?;
|
||||
Ok("Allowed".to_string())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ use crate::{SystemPromptTemplate, Template, Templates};
|
|||
use acp_thread::MentionUri;
|
||||
use action_log::ActionLog;
|
||||
use agent_client_protocol as acp;
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::adapt_schema_to_format;
|
||||
use cloud_llm_client::{CompletionIntent, CompletionMode};
|
||||
use collections::HashMap;
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
stream::FuturesUnordered,
|
||||
|
@ -22,9 +24,10 @@ use project::Project;
|
|||
use prompt_store::ProjectContext;
|
||||
use schemars::{JsonSchema, Schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use smol::stream::StreamExt;
|
||||
use std::fmt::Write;
|
||||
use std::{cell::RefCell, collections::BTreeMap, future::Future, path::Path, rc::Rc, sync::Arc};
|
||||
use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc, sync::Arc};
|
||||
use util::{ResultExt, markdown::MarkdownCodeBlock};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -529,8 +532,9 @@ impl Thread {
|
|||
}));
|
||||
};
|
||||
|
||||
let fs = self.project.read(cx).fs().clone();
|
||||
let tool_event_stream =
|
||||
ToolCallEventStream::new(&tool_use, tool.kind(), event_stream.clone());
|
||||
ToolCallEventStream::new(&tool_use, tool.kind(), event_stream.clone(), Some(fs));
|
||||
tool_event_stream.update_fields(acp::ToolCallUpdateFields {
|
||||
status: Some(acp::ToolCallStatus::InProgress),
|
||||
..Default::default()
|
||||
|
@ -917,6 +921,7 @@ pub struct ToolCallEventStream {
|
|||
kind: acp::ToolKind,
|
||||
input: serde_json::Value,
|
||||
stream: AgentResponseEventStream,
|
||||
fs: Option<Arc<dyn Fs>>,
|
||||
}
|
||||
|
||||
impl ToolCallEventStream {
|
||||
|
@ -935,6 +940,7 @@ impl ToolCallEventStream {
|
|||
},
|
||||
acp::ToolKind::Other,
|
||||
AgentResponseEventStream(events_tx),
|
||||
None,
|
||||
);
|
||||
|
||||
(stream, ToolCallEventStreamReceiver(events_rx))
|
||||
|
@ -944,12 +950,14 @@ impl ToolCallEventStream {
|
|||
tool_use: &LanguageModelToolUse,
|
||||
kind: acp::ToolKind,
|
||||
stream: AgentResponseEventStream,
|
||||
fs: Option<Arc<dyn Fs>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tool_use_id: tool_use.id.clone(),
|
||||
kind,
|
||||
input: tool_use.input.clone(),
|
||||
stream,
|
||||
fs,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -984,7 +992,11 @@ impl ToolCallEventStream {
|
|||
.ok();
|
||||
}
|
||||
|
||||
pub fn authorize(&self, title: String) -> impl use<> + Future<Output = Result<()>> {
|
||||
pub fn authorize(&self, title: impl Into<String>, cx: &mut App) -> Task<Result<()>> {
|
||||
if agent_settings::AgentSettings::get_global(cx).always_allow_tool_actions {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
self.stream
|
||||
.0
|
||||
|
@ -992,7 +1004,7 @@ impl ToolCallEventStream {
|
|||
ToolCallAuthorization {
|
||||
tool_call: AgentResponseEventStream::initial_tool_call(
|
||||
&self.tool_use_id,
|
||||
title,
|
||||
title.into(),
|
||||
self.kind.clone(),
|
||||
self.input.clone(),
|
||||
),
|
||||
|
@ -1017,12 +1029,22 @@ impl ToolCallEventStream {
|
|||
},
|
||||
)))
|
||||
.ok();
|
||||
async move {
|
||||
match response_rx.await?.0.as_ref() {
|
||||
"allow" | "always_allow" => Ok(()),
|
||||
_ => Err(anyhow!("Permission to run tool denied by user")),
|
||||
let fs = self.fs.clone();
|
||||
cx.spawn(async move |cx| match response_rx.await?.0.as_ref() {
|
||||
"always_allow" => {
|
||||
if let Some(fs) = fs.clone() {
|
||||
cx.update(|cx| {
|
||||
update_settings_file::<AgentSettings>(fs, cx, |settings, _| {
|
||||
settings.set_always_allow_tool_actions(true);
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
"allow" => Ok(()),
|
||||
_ => Err(anyhow!("Permission to run tool denied by user")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
mod copy_path_tool;
|
||||
mod create_directory_tool;
|
||||
mod delete_path_tool;
|
||||
mod diagnostics_tool;
|
||||
mod edit_file_tool;
|
||||
mod fetch_tool;
|
||||
mod find_path_tool;
|
||||
mod grep_tool;
|
||||
mod list_directory_tool;
|
||||
|
@ -16,7 +18,9 @@ mod web_search_tool;
|
|||
pub use copy_path_tool::*;
|
||||
pub use create_directory_tool::*;
|
||||
pub use delete_path_tool::*;
|
||||
pub use diagnostics_tool::*;
|
||||
pub use edit_file_tool::*;
|
||||
pub use fetch_tool::*;
|
||||
pub use find_path_tool::*;
|
||||
pub use grep_tool::*;
|
||||
pub use list_directory_tool::*;
|
||||
|
|
177
crates/agent2/src/tools/diagnostics_tool.rs
Normal file
177
crates/agent2/src/tools/diagnostics_tool.rs
Normal file
|
@ -0,0 +1,177 @@
|
|||
use crate::{AgentTool, ToolCallEventStream};
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Result, anyhow};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::{DiagnosticSeverity, OffsetRangeExt};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Write, path::Path, sync::Arc};
|
||||
use ui::SharedString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
/// Get errors and warnings for the project or a specific file.
|
||||
///
|
||||
/// This tool can be invoked after a series of edits to determine if further edits are necessary, or if the user asks to fix errors or warnings in their codebase.
|
||||
///
|
||||
/// When a path is provided, shows all diagnostics for that specific file.
|
||||
/// When no path is provided, shows a summary of error and warning counts for all files in the project.
|
||||
///
|
||||
/// <example>
|
||||
/// To get diagnostics for a specific file:
|
||||
/// {
|
||||
/// "path": "src/main.rs"
|
||||
/// }
|
||||
///
|
||||
/// To get a project-wide diagnostic summary:
|
||||
/// {}
|
||||
/// </example>
|
||||
///
|
||||
/// <guidelines>
|
||||
/// - If you think you can fix a diagnostic, make 1-2 attempts and then give up.
|
||||
/// - Don't remove code you've generated just because you can't fix an error. The user can help you fix it.
|
||||
/// </guidelines>
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DiagnosticsToolInput {
|
||||
/// The path to get diagnostics for. If not provided, returns a project-wide summary.
|
||||
///
|
||||
/// This path should never be absolute, and the first component
|
||||
/// of the path should always be a root directory in a project.
|
||||
///
|
||||
/// <example>
|
||||
/// If the project has the following root directories:
|
||||
///
|
||||
/// - lorem
|
||||
/// - ipsum
|
||||
///
|
||||
/// If you wanna access diagnostics for `dolor.txt` in `ipsum`, you should use the path `ipsum/dolor.txt`.
|
||||
/// </example>
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
pub struct DiagnosticsTool {
|
||||
project: Entity<Project>,
|
||||
}
|
||||
|
||||
impl DiagnosticsTool {
|
||||
pub fn new(project: Entity<Project>) -> Self {
|
||||
Self { project }
|
||||
}
|
||||
}
|
||||
|
||||
impl AgentTool for DiagnosticsTool {
|
||||
type Input = DiagnosticsToolInput;
|
||||
type Output = String;
|
||||
|
||||
fn name(&self) -> SharedString {
|
||||
"diagnostics".into()
|
||||
}
|
||||
|
||||
fn kind(&self) -> acp::ToolKind {
|
||||
acp::ToolKind::Read
|
||||
}
|
||||
|
||||
fn initial_title(&self, input: Result<Self::Input, serde_json::Value>) -> SharedString {
|
||||
if let Some(path) = input.ok().and_then(|input| match input.path {
|
||||
Some(path) if !path.is_empty() => Some(path),
|
||||
_ => None,
|
||||
}) {
|
||||
format!("Check diagnostics for {}", MarkdownInlineCode(&path)).into()
|
||||
} else {
|
||||
"Check project diagnostics".into()
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
input: Self::Input,
|
||||
event_stream: ToolCallEventStream,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Self::Output>> {
|
||||
match input.path {
|
||||
Some(path) if !path.is_empty() => {
|
||||
let Some(project_path) = self.project.read(cx).find_project_path(&path, cx) else {
|
||||
return Task::ready(Err(anyhow!("Could not find path {path} in project",)));
|
||||
};
|
||||
|
||||
let buffer = self
|
||||
.project
|
||||
.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut output = String::new();
|
||||
let buffer = buffer.await?;
|
||||
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
|
||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
||||
let entry = &group.entries[group.primary_ix];
|
||||
let range = entry.range.to_point(&snapshot);
|
||||
let severity = match entry.diagnostic.severity {
|
||||
DiagnosticSeverity::ERROR => "error",
|
||||
DiagnosticSeverity::WARNING => "warning",
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
writeln!(
|
||||
output,
|
||||
"{} at line {}: {}",
|
||||
severity,
|
||||
range.start.row + 1,
|
||||
entry.diagnostic.message
|
||||
)?;
|
||||
|
||||
event_stream.update_fields(acp::ToolCallUpdateFields {
|
||||
content: Some(vec![output.clone().into()]),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
if output.is_empty() {
|
||||
Ok("File doesn't have errors or warnings!".to_string())
|
||||
} else {
|
||||
Ok(output)
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let project = self.project.read(cx);
|
||||
let mut output = String::new();
|
||||
let mut has_diagnostics = false;
|
||||
|
||||
for (project_path, _, summary) in project.diagnostic_summaries(true, cx) {
|
||||
if summary.error_count > 0 || summary.warning_count > 0 {
|
||||
let Some(worktree) = project.worktree_for_id(project_path.worktree_id, cx)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
has_diagnostics = true;
|
||||
output.push_str(&format!(
|
||||
"{}: {} error(s), {} warning(s)\n",
|
||||
Path::new(worktree.read(cx).root_name())
|
||||
.join(project_path.path)
|
||||
.display(),
|
||||
summary.error_count,
|
||||
summary.warning_count
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if has_diagnostics {
|
||||
event_stream.update_fields(acp::ToolCallUpdateFields {
|
||||
content: Some(vec![output.clone().into()]),
|
||||
..Default::default()
|
||||
});
|
||||
Task::ready(Ok(output))
|
||||
} else {
|
||||
let text = "No errors or warnings found in the project.";
|
||||
event_stream.update_fields(acp::ToolCallUpdateFields {
|
||||
content: Some(vec![text.into()]),
|
||||
..Default::default()
|
||||
});
|
||||
Task::ready(Ok(text.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -133,7 +133,7 @@ impl EditFileTool {
|
|||
&self,
|
||||
input: &EditFileToolInput,
|
||||
event_stream: &ToolCallEventStream,
|
||||
cx: &App,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<()>> {
|
||||
if agent_settings::AgentSettings::get_global(cx).always_allow_tool_actions {
|
||||
return Task::ready(Ok(()));
|
||||
|
@ -147,8 +147,9 @@ impl EditFileTool {
|
|||
.components()
|
||||
.any(|component| component.as_os_str() == local_settings_folder.as_os_str())
|
||||
{
|
||||
return cx.foreground_executor().spawn(
|
||||
event_stream.authorize(format!("{} (local settings)", input.display_description)),
|
||||
return event_stream.authorize(
|
||||
format!("{} (local settings)", input.display_description),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -156,9 +157,9 @@ impl EditFileTool {
|
|||
// so check for that edge case too.
|
||||
if let Ok(canonical_path) = std::fs::canonicalize(&input.path) {
|
||||
if canonical_path.starts_with(paths::config_dir()) {
|
||||
return cx.foreground_executor().spawn(
|
||||
event_stream
|
||||
.authorize(format!("{} (global settings)", input.display_description)),
|
||||
return event_stream.authorize(
|
||||
format!("{} (global settings)", input.display_description),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -173,8 +174,7 @@ impl EditFileTool {
|
|||
if project_path.is_some() {
|
||||
Task::ready(Ok(()))
|
||||
} else {
|
||||
cx.foreground_executor()
|
||||
.spawn(event_stream.authorize(input.display_description.clone()))
|
||||
event_stream.authorize(&input.display_description, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
161
crates/agent2/src/tools/fetch_tool.rs
Normal file
161
crates/agent2/src/tools/fetch_tool.rs
Normal file
|
@ -0,0 +1,161 @@
|
|||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, cell::RefCell};
|
||||
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{App, AppContext as _, Task};
|
||||
use html_to_markdown::{TagHandler, convert_html_to_markdown, markdown};
|
||||
use http_client::{AsyncBody, HttpClientWithUrl};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ui::SharedString;
|
||||
use util::markdown::MarkdownEscaped;
|
||||
|
||||
use crate::{AgentTool, ToolCallEventStream};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
enum ContentType {
|
||||
Html,
|
||||
Plaintext,
|
||||
Json,
|
||||
}
|
||||
|
||||
/// Fetches a URL and returns the content as Markdown.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct FetchToolInput {
|
||||
/// The URL to fetch.
|
||||
url: String,
|
||||
}
|
||||
|
||||
pub struct FetchTool {
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
}
|
||||
|
||||
impl FetchTool {
|
||||
pub fn new(http_client: Arc<HttpClientWithUrl>) -> Self {
|
||||
Self { http_client }
|
||||
}
|
||||
|
||||
async fn build_message(http_client: Arc<HttpClientWithUrl>, url: &str) -> Result<String> {
|
||||
let url = if !url.starts_with("https://") && !url.starts_with("http://") {
|
||||
Cow::Owned(format!("https://{url}"))
|
||||
} else {
|
||||
Cow::Borrowed(url)
|
||||
};
|
||||
|
||||
let mut response = http_client.get(&url, AsyncBody::default(), true).await?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("error reading response body")?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
let text = String::from_utf8_lossy(body.as_slice());
|
||||
bail!(
|
||||
"status error {}, response: {text:?}",
|
||||
response.status().as_u16()
|
||||
);
|
||||
}
|
||||
|
||||
let Some(content_type) = response.headers().get("content-type") else {
|
||||
bail!("missing Content-Type header");
|
||||
};
|
||||
let content_type = content_type
|
||||
.to_str()
|
||||
.context("invalid Content-Type header")?;
|
||||
|
||||
let content_type = if content_type.starts_with("text/plain") {
|
||||
ContentType::Plaintext
|
||||
} else if content_type.starts_with("application/json") {
|
||||
ContentType::Json
|
||||
} else {
|
||||
ContentType::Html
|
||||
};
|
||||
|
||||
match content_type {
|
||||
ContentType::Html => {
|
||||
let mut handlers: Vec<TagHandler> = vec![
|
||||
Rc::new(RefCell::new(markdown::WebpageChromeRemover)),
|
||||
Rc::new(RefCell::new(markdown::ParagraphHandler)),
|
||||
Rc::new(RefCell::new(markdown::HeadingHandler)),
|
||||
Rc::new(RefCell::new(markdown::ListHandler)),
|
||||
Rc::new(RefCell::new(markdown::TableHandler::new())),
|
||||
Rc::new(RefCell::new(markdown::StyledTextHandler)),
|
||||
];
|
||||
if url.contains("wikipedia.org") {
|
||||
use html_to_markdown::structure::wikipedia;
|
||||
|
||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaChromeRemover)));
|
||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaInfoboxHandler)));
|
||||
handlers.push(Rc::new(
|
||||
RefCell::new(wikipedia::WikipediaCodeHandler::new()),
|
||||
));
|
||||
} else {
|
||||
handlers.push(Rc::new(RefCell::new(markdown::CodeHandler)));
|
||||
}
|
||||
|
||||
convert_html_to_markdown(&body[..], &mut handlers)
|
||||
}
|
||||
ContentType::Plaintext => Ok(std::str::from_utf8(&body)?.to_owned()),
|
||||
ContentType::Json => {
|
||||
let json: serde_json::Value = serde_json::from_slice(&body)?;
|
||||
|
||||
Ok(format!(
|
||||
"```json\n{}\n```",
|
||||
serde_json::to_string_pretty(&json)?
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AgentTool for FetchTool {
|
||||
type Input = FetchToolInput;
|
||||
type Output = String;
|
||||
|
||||
fn name(&self) -> SharedString {
|
||||
"fetch".into()
|
||||
}
|
||||
|
||||
fn kind(&self) -> acp::ToolKind {
|
||||
acp::ToolKind::Fetch
|
||||
}
|
||||
|
||||
fn initial_title(&self, input: Result<Self::Input, serde_json::Value>) -> SharedString {
|
||||
match input {
|
||||
Ok(input) => format!("Fetch {}", MarkdownEscaped(&input.url)).into(),
|
||||
Err(_) => "Fetch URL".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
input: Self::Input,
|
||||
event_stream: ToolCallEventStream,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Self::Output>> {
|
||||
let text = cx.background_spawn({
|
||||
let http_client = self.http_client.clone();
|
||||
async move { Self::build_message(http_client, &input.url).await }
|
||||
});
|
||||
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let text = text.await?;
|
||||
if text.trim().is_empty() {
|
||||
bail!("no textual content found");
|
||||
}
|
||||
|
||||
event_stream.update_fields(acp::ToolCallUpdateFields {
|
||||
content: Some(vec![text.clone().into()]),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
Ok(text)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -65,7 +65,7 @@ impl AgentTool for OpenTool {
|
|||
) -> Task<Result<Self::Output>> {
|
||||
// If path_or_url turns out to be a path in the project, make it absolute.
|
||||
let abs_path = to_absolute_path(&input.path_or_url, self.project.clone(), cx);
|
||||
let authorize = event_stream.authorize(self.initial_title(Ok(input.clone())).to_string());
|
||||
let authorize = event_stream.authorize(self.initial_title(Ok(input.clone())), cx);
|
||||
cx.background_spawn(async move {
|
||||
authorize.await?;
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ use gpui::{App, AppContext, Entity, SharedString, Task};
|
|||
use project::{Project, terminals::TerminalKind};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
|
@ -61,21 +60,6 @@ impl TerminalTool {
|
|||
determine_shell: determine_shell.shared(),
|
||||
}
|
||||
}
|
||||
|
||||
fn authorize(
|
||||
&self,
|
||||
input: &TerminalToolInput,
|
||||
event_stream: &ToolCallEventStream,
|
||||
cx: &App,
|
||||
) -> Task<Result<()>> {
|
||||
if agent_settings::AgentSettings::get_global(cx).always_allow_tool_actions {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
// TODO: do we want to have a special title here?
|
||||
cx.foreground_executor()
|
||||
.spawn(event_stream.authorize(self.initial_title(Ok(input.clone())).to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AgentTool for TerminalTool {
|
||||
|
@ -152,7 +136,7 @@ impl AgentTool for TerminalTool {
|
|||
env
|
||||
});
|
||||
|
||||
let authorize = self.authorize(&input, &event_stream, cx);
|
||||
let authorize = event_stream.authorize(self.initial_title(Ok(input.clone())), cx);
|
||||
|
||||
cx.spawn({
|
||||
async move |cx| {
|
||||
|
|
|
@ -39,7 +39,7 @@ use theme::ThemeSettings;
|
|||
use ui::{
|
||||
Disclosure, Divider, DividerColor, KeyBinding, Scrollbar, ScrollbarState, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
|
||||
use workspace::{CollaboratorId, Workspace};
|
||||
use zed_actions::agent::{Chat, NextHistoryMessage, PreviousHistoryMessage};
|
||||
|
||||
|
@ -76,6 +76,7 @@ pub struct AcpThreadView {
|
|||
edits_expanded: bool,
|
||||
plan_expanded: bool,
|
||||
editor_expanded: bool,
|
||||
terminal_expanded: bool,
|
||||
message_history: Rc<RefCell<MessageHistory<Vec<acp::ContentBlock>>>>,
|
||||
_cancel_task: Option<Task<()>>,
|
||||
_subscriptions: [Subscription; 1],
|
||||
|
@ -201,6 +202,7 @@ impl AcpThreadView {
|
|||
edits_expanded: false,
|
||||
plan_expanded: false,
|
||||
editor_expanded: false,
|
||||
terminal_expanded: true,
|
||||
message_history,
|
||||
_subscriptions: [subscription],
|
||||
_cancel_task: None,
|
||||
|
@ -796,7 +798,7 @@ impl AcpThreadView {
|
|||
window,
|
||||
cx,
|
||||
);
|
||||
view.set_embedded_mode(None, cx);
|
||||
view.set_embedded_mode(Some(1000), cx);
|
||||
view
|
||||
});
|
||||
|
||||
|
@ -942,17 +944,26 @@ impl AcpThreadView {
|
|||
.child(message_body)
|
||||
.into_any()
|
||||
}
|
||||
AgentThreadEntry::ToolCall(tool_call) => div()
|
||||
.w_full()
|
||||
.py_1p5()
|
||||
.px_5()
|
||||
.child(self.render_tool_call(index, tool_call, window, cx))
|
||||
.into_any(),
|
||||
AgentThreadEntry::ToolCall(tool_call) => {
|
||||
let has_terminals = tool_call.terminals().next().is_some();
|
||||
|
||||
div().w_full().py_1p5().px_5().map(|this| {
|
||||
if has_terminals {
|
||||
this.children(tool_call.terminals().map(|terminal| {
|
||||
self.render_terminal_tool_call(terminal, tool_call, window, cx)
|
||||
}))
|
||||
} else {
|
||||
this.child(self.render_tool_call(index, tool_call, window, cx))
|
||||
}
|
||||
})
|
||||
}
|
||||
.into_any(),
|
||||
};
|
||||
|
||||
let Some(thread) = self.thread() else {
|
||||
return primary;
|
||||
};
|
||||
|
||||
let is_generating = matches!(thread.read(cx).status(), ThreadStatus::Generating);
|
||||
if index == total_entries - 1 && !is_generating {
|
||||
v_flex()
|
||||
|
@ -1181,19 +1192,27 @@ impl AcpThreadView {
|
|||
),
|
||||
};
|
||||
|
||||
let needs_confirmation = match &tool_call.status {
|
||||
ToolCallStatus::WaitingForConfirmation { .. } => true,
|
||||
_ => tool_call
|
||||
.content
|
||||
.iter()
|
||||
.any(|content| matches!(content, ToolCallContent::Diff(_))),
|
||||
};
|
||||
let needs_confirmation = matches!(
|
||||
tool_call.status,
|
||||
ToolCallStatus::WaitingForConfirmation { .. }
|
||||
);
|
||||
let is_edit = matches!(tool_call.kind, acp::ToolKind::Edit);
|
||||
let has_diff = tool_call
|
||||
.content
|
||||
.iter()
|
||||
.any(|content| matches!(content, ToolCallContent::Diff { .. }));
|
||||
let has_nonempty_diff = tool_call.content.iter().any(|content| match content {
|
||||
ToolCallContent::Diff(diff) => diff.read(cx).has_revealed_range(cx),
|
||||
_ => false,
|
||||
});
|
||||
let is_collapsible =
|
||||
!tool_call.content.is_empty() && !needs_confirmation && !is_edit && !has_diff;
|
||||
let is_open = tool_call.content.is_empty()
|
||||
|| needs_confirmation
|
||||
|| has_nonempty_diff
|
||||
|| self.expanded_tool_calls.contains(&tool_call.id);
|
||||
|
||||
let is_collapsible = !tool_call.content.is_empty() && !needs_confirmation;
|
||||
let is_open = !is_collapsible || self.expanded_tool_calls.contains(&tool_call.id);
|
||||
|
||||
let gradient_color = cx.theme().colors().panel_background;
|
||||
let gradient_overlay = {
|
||||
let gradient_overlay = |color: Hsla| {
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
|
@ -1202,13 +1221,13 @@ impl AcpThreadView {
|
|||
.h_full()
|
||||
.bg(linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(gradient_color, 1.),
|
||||
linear_color_stop(gradient_color.opacity(0.2), 0.),
|
||||
linear_color_stop(color, 1.),
|
||||
linear_color_stop(color.opacity(0.2), 0.),
|
||||
))
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.when(needs_confirmation, |this| {
|
||||
.when(needs_confirmation || is_edit || has_diff, |this| {
|
||||
this.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
|
@ -1222,7 +1241,7 @@ impl AcpThreadView {
|
|||
.gap_1()
|
||||
.justify_between()
|
||||
.map(|this| {
|
||||
if needs_confirmation {
|
||||
if needs_confirmation || is_edit || has_diff {
|
||||
this.pl_2()
|
||||
.pr_1()
|
||||
.py_1()
|
||||
|
@ -1299,13 +1318,23 @@ impl AcpThreadView {
|
|||
.child(self.render_markdown(
|
||||
tool_call.label.clone(),
|
||||
default_markdown_style(
|
||||
needs_confirmation,
|
||||
needs_confirmation || is_edit || has_diff,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
)),
|
||||
)
|
||||
.child(gradient_overlay)
|
||||
.map(|this| {
|
||||
if needs_confirmation {
|
||||
this.child(gradient_overlay(
|
||||
self.tool_card_header_bg(cx),
|
||||
))
|
||||
} else {
|
||||
this.child(gradient_overlay(
|
||||
cx.theme().colors().panel_background,
|
||||
))
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener({
|
||||
let id = tool_call.id.clone();
|
||||
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
|
||||
|
@ -1340,11 +1369,9 @@ impl AcpThreadView {
|
|||
.children(tool_call.content.iter().map(|content| {
|
||||
div()
|
||||
.py_1p5()
|
||||
.child(
|
||||
self.render_tool_call_content(
|
||||
content, window, cx,
|
||||
),
|
||||
)
|
||||
.child(self.render_tool_call_content(
|
||||
content, tool_call, window, cx,
|
||||
))
|
||||
.into_any_element()
|
||||
}))
|
||||
.child(self.render_permission_buttons(
|
||||
|
@ -1358,11 +1385,9 @@ impl AcpThreadView {
|
|||
this.children(tool_call.content.iter().map(|content| {
|
||||
div()
|
||||
.py_1p5()
|
||||
.child(
|
||||
self.render_tool_call_content(
|
||||
content, window, cx,
|
||||
),
|
||||
)
|
||||
.child(self.render_tool_call_content(
|
||||
content, tool_call, window, cx,
|
||||
))
|
||||
.into_any_element()
|
||||
}))
|
||||
}
|
||||
|
@ -1379,6 +1404,7 @@ impl AcpThreadView {
|
|||
fn render_tool_call_content(
|
||||
&self,
|
||||
content: &ToolCallContent,
|
||||
tool_call: &ToolCall,
|
||||
window: &Window,
|
||||
cx: &Context<Self>,
|
||||
) -> AnyElement {
|
||||
|
@ -1399,7 +1425,9 @@ impl AcpThreadView {
|
|||
}
|
||||
}
|
||||
ToolCallContent::Diff(diff) => self.render_diff_editor(&diff.read(cx).multibuffer()),
|
||||
ToolCallContent::Terminal(terminal) => self.render_terminal(terminal),
|
||||
ToolCallContent::Terminal(terminal) => {
|
||||
self.render_terminal_tool_call(terminal, tool_call, window, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1412,14 +1440,22 @@ impl AcpThreadView {
|
|||
cx: &Context<Self>,
|
||||
) -> Div {
|
||||
h_flex()
|
||||
.p_1p5()
|
||||
.py_1()
|
||||
.pl_2()
|
||||
.pr_1()
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.justify_between()
|
||||
.flex_wrap()
|
||||
.when(!empty_content, |this| {
|
||||
this.border_t_1()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
})
|
||||
.children(options.iter().map(|option| {
|
||||
.child(
|
||||
div()
|
||||
.min_w(rems_from_px(145.))
|
||||
.child(LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small)),
|
||||
)
|
||||
.child(h_flex().gap_0p5().children(options.iter().map(|option| {
|
||||
let option_id = SharedString::from(option.id.0.clone());
|
||||
Button::new((option_id, entry_ix), option.name.clone())
|
||||
.map(|this| match option.kind {
|
||||
|
@ -1452,7 +1488,7 @@ impl AcpThreadView {
|
|||
);
|
||||
}
|
||||
}))
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
fn render_diff_editor(&self, multibuffer: &Entity<MultiBuffer>) -> AnyElement {
|
||||
|
@ -1468,18 +1504,242 @@ impl AcpThreadView {
|
|||
.into_any()
|
||||
}
|
||||
|
||||
fn render_terminal(&self, terminal: &Entity<acp_thread::Terminal>) -> AnyElement {
|
||||
v_flex()
|
||||
.h_72()
|
||||
fn render_terminal_tool_call(
|
||||
&self,
|
||||
terminal: &Entity<acp_thread::Terminal>,
|
||||
tool_call: &ToolCall,
|
||||
window: &Window,
|
||||
cx: &Context<Self>,
|
||||
) -> AnyElement {
|
||||
let terminal_data = terminal.read(cx);
|
||||
let working_dir = terminal_data.working_dir();
|
||||
let command = terminal_data.command();
|
||||
let started_at = terminal_data.started_at();
|
||||
|
||||
let tool_failed = matches!(
|
||||
&tool_call.status,
|
||||
ToolCallStatus::Rejected
|
||||
| ToolCallStatus::Canceled
|
||||
| ToolCallStatus::Allowed {
|
||||
status: acp::ToolCallStatus::Failed,
|
||||
..
|
||||
}
|
||||
);
|
||||
|
||||
let output = terminal_data.output();
|
||||
let command_finished = output.is_some();
|
||||
let truncated_output = output.is_some_and(|output| output.was_content_truncated);
|
||||
let output_line_count = output.map(|output| output.content_line_count).unwrap_or(0);
|
||||
|
||||
let command_failed = command_finished
|
||||
&& output.is_some_and(|o| o.exit_status.is_none_or(|status| !status.success()));
|
||||
|
||||
let time_elapsed = if let Some(output) = output {
|
||||
output.ended_at.duration_since(started_at)
|
||||
} else {
|
||||
started_at.elapsed()
|
||||
};
|
||||
|
||||
let header_bg = cx
|
||||
.theme()
|
||||
.colors()
|
||||
.element_background
|
||||
.blend(cx.theme().colors().editor_foreground.opacity(0.025));
|
||||
let border_color = cx.theme().colors().border.opacity(0.6);
|
||||
|
||||
let working_dir = working_dir
|
||||
.as_ref()
|
||||
.map(|path| format!("{}", path.display()))
|
||||
.unwrap_or_else(|| "current directory".to_string());
|
||||
|
||||
let header = h_flex()
|
||||
.id(SharedString::from(format!(
|
||||
"terminal-tool-header-{}",
|
||||
terminal.entity_id()
|
||||
)))
|
||||
.flex_none()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.rounded_t_md()
|
||||
.child(
|
||||
if let Some(terminal_view) = self.terminal_views.get(&terminal.entity_id()) {
|
||||
// TODO: terminal has all the state we need to reproduce
|
||||
// what we had in the terminal card.
|
||||
terminal_view.clone().into_any_element()
|
||||
} else {
|
||||
Empty.into_any()
|
||||
},
|
||||
div()
|
||||
.id(("command-target-path", terminal.entity_id()))
|
||||
.w_full()
|
||||
.max_w_full()
|
||||
.overflow_x_scroll()
|
||||
.child(
|
||||
Label::new(working_dir)
|
||||
.buffer_font(cx)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.when(!command_finished, |header| {
|
||||
header
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Button::new(
|
||||
SharedString::from(format!("stop-terminal-{}", terminal.entity_id())),
|
||||
"Stop",
|
||||
)
|
||||
.icon(IconName::Stop)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Error)
|
||||
.label_size(LabelSize::Small)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Stop This Command",
|
||||
None,
|
||||
"Also possible by placing your cursor inside the terminal and using regular terminal bindings.",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let terminal = terminal.clone();
|
||||
cx.listener(move |_this, _event, _window, cx| {
|
||||
let inner_terminal = terminal.read(cx).inner().clone();
|
||||
inner_terminal.update(cx, |inner_terminal, _cx| {
|
||||
inner_terminal.kill_active_task();
|
||||
});
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(tool_failed || command_failed, |header| {
|
||||
header.child(
|
||||
div()
|
||||
.id(("terminal-tool-error-code-indicator", terminal.entity_id()))
|
||||
.child(
|
||||
Icon::new(IconName::Close)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.when_some(output.and_then(|o| o.exit_status), |this, status| {
|
||||
this.tooltip(Tooltip::text(format!(
|
||||
"Exited with code {}",
|
||||
status.code().unwrap_or(-1),
|
||||
)))
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(truncated_output, |header| {
|
||||
let tooltip = if let Some(output) = output {
|
||||
if output_line_count + 10 > terminal::MAX_SCROLL_HISTORY_LINES {
|
||||
"Output exceeded terminal max lines and was \
|
||||
truncated, the model received the first 16 KB."
|
||||
.to_string()
|
||||
} else {
|
||||
format!(
|
||||
"Output is {} long—to avoid unexpected token usage, \
|
||||
only 16 KB was sent back to the model.",
|
||||
format_file_size(output.original_content_len as u64, true),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
"Output was truncated".to_string()
|
||||
};
|
||||
|
||||
header.child(
|
||||
h_flex()
|
||||
.id(("terminal-tool-truncated-label", terminal.entity_id()))
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Info)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Ignored),
|
||||
)
|
||||
.child(
|
||||
Label::new("Truncated")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
.tooltip(Tooltip::text(tooltip)),
|
||||
)
|
||||
})
|
||||
.when(time_elapsed > Duration::from_secs(10), |header| {
|
||||
header.child(
|
||||
Label::new(format!("({})", duration_alt_display(time_elapsed)))
|
||||
.buffer_font(cx)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Disclosure::new(
|
||||
SharedString::from(format!(
|
||||
"terminal-tool-disclosure-{}",
|
||||
terminal.entity_id()
|
||||
)),
|
||||
self.terminal_expanded,
|
||||
)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown)
|
||||
.on_click(cx.listener(move |this, _event, _window, _cx| {
|
||||
this.terminal_expanded = !this.terminal_expanded;
|
||||
})),
|
||||
);
|
||||
|
||||
let show_output =
|
||||
self.terminal_expanded && self.terminal_views.contains_key(&terminal.entity_id());
|
||||
|
||||
v_flex()
|
||||
.mb_2()
|
||||
.border_1()
|
||||
.when(tool_failed || command_failed, |card| card.border_dashed())
|
||||
.border_color(border_color)
|
||||
.rounded_lg()
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
v_flex()
|
||||
.p_2()
|
||||
.gap_0p5()
|
||||
.bg(header_bg)
|
||||
.text_xs()
|
||||
.child(header)
|
||||
.child(
|
||||
MarkdownElement::new(
|
||||
command.clone(),
|
||||
terminal_command_markdown_style(window, cx),
|
||||
)
|
||||
.code_block_renderer(
|
||||
markdown::CodeBlockRenderer::Default {
|
||||
copy_button: false,
|
||||
copy_button_on_hover: true,
|
||||
border: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.when(show_output, |this| {
|
||||
let terminal_view = self.terminal_views.get(&terminal.entity_id()).unwrap();
|
||||
|
||||
this.child(
|
||||
div()
|
||||
.pt_2()
|
||||
.border_t_1()
|
||||
.when(tool_failed || command_failed, |card| card.border_dashed())
|
||||
.border_color(border_color)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.rounded_b_md()
|
||||
.text_ui_sm(cx)
|
||||
.child(terminal_view.clone()),
|
||||
)
|
||||
})
|
||||
.into_any()
|
||||
}
|
||||
|
||||
|
@ -3056,6 +3316,18 @@ fn diff_editor_text_style_refinement(cx: &mut App) -> TextStyleRefinement {
|
|||
}
|
||||
}
|
||||
|
||||
fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
let default_md_style = default_markdown_style(true, window, cx);
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: TextStyle {
|
||||
..default_md_style.base_text_style
|
||||
},
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use agent_client_protocol::SessionId;
|
||||
|
|
|
@ -86,7 +86,7 @@ impl Tool for DiagnosticsTool {
|
|||
input: serde_json::Value,
|
||||
_request: Arc<LanguageModelRequest>,
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_model: Arc<dyn LanguageModel>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
|
@ -159,10 +159,6 @@ impl Tool for DiagnosticsTool {
|
|||
}
|
||||
}
|
||||
|
||||
action_log.update(cx, |action_log, _cx| {
|
||||
action_log.checked_project_diagnostics();
|
||||
});
|
||||
|
||||
if has_diagnostics {
|
||||
Task::ready(Ok(output.into())).into()
|
||||
} else {
|
||||
|
|
|
@ -152,6 +152,9 @@ impl PythonDebugAdapter {
|
|||
maybe!(async move {
|
||||
let response = latest_release.filter(|response| response.status().is_success())?;
|
||||
|
||||
let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME);
|
||||
std::fs::create_dir_all(&download_dir).ok()?;
|
||||
|
||||
let mut output = String::new();
|
||||
response
|
||||
.into_body()
|
||||
|
|
|
@ -2172,6 +2172,9 @@ impl Fs for FakeFs {
|
|||
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
|
||||
self.simulate_random_delay().await;
|
||||
let path = normalize_path(path.as_path());
|
||||
if let Some(path) = path.parent() {
|
||||
self.create_dir(path).await?;
|
||||
}
|
||||
self.write_file_internal(path, data.into_bytes(), true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use gpui::{
|
||||
App, Application, Context, Menu, MenuItem, Window, WindowOptions, actions, div, prelude::*, rgb,
|
||||
App, Application, Context, Menu, MenuItem, SystemMenuType, Window, WindowOptions, actions, div,
|
||||
prelude::*, rgb,
|
||||
};
|
||||
|
||||
struct SetMenus;
|
||||
|
@ -27,7 +28,11 @@ fn main() {
|
|||
// Add menu items
|
||||
cx.set_menus(vec![Menu {
|
||||
name: "set_menus".into(),
|
||||
items: vec![MenuItem::action("Quit", Quit)],
|
||||
items: vec![
|
||||
MenuItem::os_submenu("Services", SystemMenuType::Services),
|
||||
MenuItem::separator(),
|
||||
MenuItem::action("Quit", Quit),
|
||||
],
|
||||
}]);
|
||||
cx.open_window(WindowOptions::default(), |_, cx| cx.new(|_| SetMenus {}))
|
||||
.unwrap();
|
||||
|
|
|
@ -20,6 +20,34 @@ impl Menu {
|
|||
}
|
||||
}
|
||||
|
||||
/// OS menus are menus that are recognized by the operating system
|
||||
/// This allows the operating system to provide specialized items for
|
||||
/// these menus
|
||||
pub struct OsMenu {
|
||||
/// The name of the menu
|
||||
pub name: SharedString,
|
||||
|
||||
/// The type of menu
|
||||
pub menu_type: SystemMenuType,
|
||||
}
|
||||
|
||||
impl OsMenu {
|
||||
/// Create an OwnedOsMenu from this OsMenu
|
||||
pub fn owned(self) -> OwnedOsMenu {
|
||||
OwnedOsMenu {
|
||||
name: self.name.to_string().into(),
|
||||
menu_type: self.menu_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of system menu
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum SystemMenuType {
|
||||
/// The 'Services' menu in the Application menu on macOS
|
||||
Services,
|
||||
}
|
||||
|
||||
/// The different kinds of items that can be in a menu
|
||||
pub enum MenuItem {
|
||||
/// A separator between items
|
||||
|
@ -28,6 +56,9 @@ pub enum MenuItem {
|
|||
/// A submenu
|
||||
Submenu(Menu),
|
||||
|
||||
/// A menu, managed by the system (for example, the Services menu on macOS)
|
||||
SystemMenu(OsMenu),
|
||||
|
||||
/// An action that can be performed
|
||||
Action {
|
||||
/// The name of this menu item
|
||||
|
@ -53,6 +84,14 @@ impl MenuItem {
|
|||
Self::Submenu(menu)
|
||||
}
|
||||
|
||||
/// Creates a new submenu that is populated by the OS
|
||||
pub fn os_submenu(name: impl Into<SharedString>, menu_type: SystemMenuType) -> Self {
|
||||
Self::SystemMenu(OsMenu {
|
||||
name: name.into(),
|
||||
menu_type,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new menu item that invokes an action
|
||||
pub fn action(name: impl Into<SharedString>, action: impl Action) -> Self {
|
||||
Self::Action {
|
||||
|
@ -89,10 +128,23 @@ impl MenuItem {
|
|||
action,
|
||||
os_action,
|
||||
},
|
||||
MenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// OS menus are menus that are recognized by the operating system
|
||||
/// This allows the operating system to provide specialized items for
|
||||
/// these menus
|
||||
#[derive(Clone)]
|
||||
pub struct OwnedOsMenu {
|
||||
/// The name of the menu
|
||||
pub name: SharedString,
|
||||
|
||||
/// The type of menu
|
||||
pub menu_type: SystemMenuType,
|
||||
}
|
||||
|
||||
/// A menu of the application, either a main menu or a submenu
|
||||
#[derive(Clone)]
|
||||
pub struct OwnedMenu {
|
||||
|
@ -111,6 +163,9 @@ pub enum OwnedMenuItem {
|
|||
/// A submenu
|
||||
Submenu(OwnedMenu),
|
||||
|
||||
/// A menu, managed by the system (for example, the Services menu on macOS)
|
||||
SystemMenu(OwnedOsMenu),
|
||||
|
||||
/// An action that can be performed
|
||||
Action {
|
||||
/// The name of this menu item
|
||||
|
@ -139,6 +194,7 @@ impl Clone for OwnedMenuItem {
|
|||
action: action.boxed_clone(),
|
||||
os_action: *os_action,
|
||||
},
|
||||
OwnedMenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ use super::{
|
|||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString,
|
||||
CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher,
|
||||
MacDisplay, MacWindow, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay,
|
||||
PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task,
|
||||
WindowAppearance, WindowParams, hash,
|
||||
MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform,
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result,
|
||||
SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams, hash,
|
||||
};
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use block::ConcreteBlock;
|
||||
|
@ -413,9 +413,20 @@ impl MacPlatform {
|
|||
}
|
||||
item.setSubmenu_(submenu);
|
||||
item.setTitle_(ns_string(&name));
|
||||
if name == "Services" {
|
||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||
app.setServicesMenu_(item);
|
||||
item
|
||||
}
|
||||
MenuItem::SystemMenu(OsMenu { name, menu_type }) => {
|
||||
let item = NSMenuItem::new(nil).autorelease();
|
||||
let submenu = NSMenu::new(nil).autorelease();
|
||||
submenu.setDelegate_(delegate);
|
||||
item.setSubmenu_(submenu);
|
||||
item.setTitle_(ns_string(&name));
|
||||
|
||||
match menu_type {
|
||||
SystemMenuType::Services => {
|
||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||
app.setServicesMenu_(item);
|
||||
}
|
||||
}
|
||||
|
||||
item
|
||||
|
|
|
@ -476,7 +476,7 @@ pub(crate) struct Underline {
|
|||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub color: Hsla,
|
||||
pub thickness: ScaledPixels,
|
||||
pub wavy: bool,
|
||||
pub wavy: u32,
|
||||
}
|
||||
|
||||
impl From<Underline> for Primitive {
|
||||
|
|
|
@ -2814,7 +2814,7 @@ impl Window {
|
|||
content_mask: content_mask.scale(scale_factor),
|
||||
color: style.color.unwrap_or_default().opacity(element_opacity),
|
||||
thickness: style.thickness.scale(scale_factor),
|
||||
wavy: style.wavy,
|
||||
wavy: if style.wavy { 1 } else { 0 },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2845,7 +2845,7 @@ impl Window {
|
|||
content_mask: content_mask.scale(scale_factor),
|
||||
thickness: style.thickness.scale(scale_factor),
|
||||
color: style.color.unwrap_or_default().opacity(opacity),
|
||||
wavy: false,
|
||||
wavy: 0,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -121,8 +121,16 @@ impl ApplicationMenu {
|
|||
menu.action(name, action)
|
||||
}
|
||||
OwnedMenuItem::Submenu(_) => menu,
|
||||
OwnedMenuItem::SystemMenu(_) => {
|
||||
// A system menu doesn't make sense in this context, so ignore it
|
||||
menu
|
||||
}
|
||||
})
|
||||
}
|
||||
OwnedMenuItem::SystemMenu(_) => {
|
||||
// A system menu doesn't make sense in this context, so ignore it
|
||||
menu
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -420,7 +420,7 @@ pub struct Switch {
|
|||
id: ElementId,
|
||||
toggle_state: ToggleState,
|
||||
disabled: bool,
|
||||
on_click: Option<Box<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>>,
|
||||
on_click: Option<Rc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>>,
|
||||
label: Option<SharedString>,
|
||||
key_binding: Option<KeyBinding>,
|
||||
color: SwitchColor,
|
||||
|
@ -459,7 +459,7 @@ impl Switch {
|
|||
mut self,
|
||||
handler: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self.on_click = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -513,10 +513,16 @@ impl RenderOnce for Switch {
|
|||
.when_some(
|
||||
self.tab_index.filter(|_| !self.disabled),
|
||||
|this, tab_index| {
|
||||
this.tab_index(tab_index).focus(|mut style| {
|
||||
style.border_color = Some(cx.theme().colors().border_focused);
|
||||
style
|
||||
})
|
||||
this.tab_index(tab_index)
|
||||
.focus(|mut style| {
|
||||
style.border_color = Some(cx.theme().colors().border_focused);
|
||||
style
|
||||
})
|
||||
.when_some(self.on_click.clone(), |this, on_click| {
|
||||
this.on_click(move |_, window, cx| {
|
||||
on_click(&self.toggle_state.inverse(), window, cx)
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
.child(
|
||||
|
|
|
@ -31,7 +31,7 @@ impl Vim {
|
|||
) {
|
||||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
if let Some(selections) = editor
|
||||
.change_list
|
||||
.next_change(count, direction)
|
||||
|
@ -49,7 +49,7 @@ impl Vim {
|
|||
}
|
||||
|
||||
pub(crate) fn push_to_change_list(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some((new_positions, buffer)) = self.update_editor(window, cx, |vim, editor, _, cx| {
|
||||
let Some((new_positions, buffer)) = self.update_editor(cx, |vim, editor, cx| {
|
||||
let (map, selections) = editor.selections.all_adjusted_display(cx);
|
||||
let buffer = editor.buffer().clone();
|
||||
|
||||
|
|
|
@ -241,9 +241,9 @@ impl Deref for WrappedAction {
|
|||
|
||||
pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||
// Vim::action(editor, cx, |vim, action: &StartOfLine, window, cx| {
|
||||
Vim::action(editor, cx, |vim, action: &VimSet, window, cx| {
|
||||
Vim::action(editor, cx, |vim, action: &VimSet, _, cx| {
|
||||
for option in action.options.iter() {
|
||||
vim.update_editor(window, cx, |_, editor, _, cx| match option {
|
||||
vim.update_editor(cx, |_, editor, cx| match option {
|
||||
VimOption::Wrap(true) => {
|
||||
editor
|
||||
.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
|
@ -298,7 +298,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
});
|
||||
|
||||
Vim::action(editor, cx, |vim, action: &VimSave, window, cx| {
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
let Some(project) = editor.project.clone() else {
|
||||
return;
|
||||
};
|
||||
|
@ -375,7 +375,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
cx,
|
||||
);
|
||||
}
|
||||
vim.update_editor(window, cx, |vim, editor, window, cx| match action {
|
||||
vim.update_editor(cx, |vim, editor, cx| match action {
|
||||
DeleteMarks::Marks(s) => {
|
||||
if s.starts_with('-') || s.ends_with('-') || s.contains(['\'', '`']) {
|
||||
err(s.clone(), window, cx);
|
||||
|
@ -432,7 +432,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
});
|
||||
|
||||
Vim::action(editor, cx, |vim, action: &VimEdit, window, cx| {
|
||||
vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
vim.update_editor(cx, |vim, editor, cx| {
|
||||
let Some(workspace) = vim.workspace(window) else {
|
||||
return;
|
||||
};
|
||||
|
@ -462,11 +462,10 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
.map(|c| Keystroke::parse(&c.to_string()).unwrap())
|
||||
.collect();
|
||||
vim.switch_mode(Mode::Normal, true, window, cx);
|
||||
let initial_selections = vim.update_editor(window, cx, |_, editor, _, _| {
|
||||
editor.selections.disjoint_anchors()
|
||||
});
|
||||
let initial_selections =
|
||||
vim.update_editor(cx, |_, editor, _| editor.selections.disjoint_anchors());
|
||||
if let Some(range) = &action.range {
|
||||
let result = vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
let result = vim.update_editor(cx, |vim, editor, cx| {
|
||||
let range = range.buffer_range(vim, editor, window, cx)?;
|
||||
editor.change_selections(
|
||||
SelectionEffects::no_scroll().nav_history(false),
|
||||
|
@ -498,7 +497,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
cx.spawn_in(window, async move |vim, cx| {
|
||||
task.await;
|
||||
vim.update_in(cx, |vim, window, cx| {
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
if had_range {
|
||||
editor.change_selections(SelectionEffects::default(), window, cx, |s| {
|
||||
s.select_anchor_ranges([s.newest_anchor().range()]);
|
||||
|
@ -510,7 +509,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
} else {
|
||||
vim.switch_mode(Mode::Normal, true, window, cx);
|
||||
}
|
||||
vim.update_editor(window, cx, |_, editor, _, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
if let Some(first_sel) = initial_selections {
|
||||
if let Some(tx_id) = editor
|
||||
.buffer()
|
||||
|
@ -548,7 +547,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
|
||||
Vim::action(editor, cx, |vim, action: &GoToLine, window, cx| {
|
||||
vim.switch_mode(Mode::Normal, false, window, cx);
|
||||
let result = vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
let result = vim.update_editor(cx, |vim, editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let buffer_row = action.range.head().buffer_row(vim, editor, window, cx)?;
|
||||
let current = editor.selections.newest::<Point>(cx);
|
||||
|
@ -573,7 +572,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
});
|
||||
|
||||
Vim::action(editor, cx, |vim, action: &YankCommand, window, cx| {
|
||||
vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
vim.update_editor(cx, |vim, editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
if let Ok(range) = action.range.buffer_range(vim, editor, window, cx) {
|
||||
let end = if range.end < snapshot.buffer_snapshot.max_row() {
|
||||
|
@ -600,7 +599,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
});
|
||||
|
||||
Vim::action(editor, cx, |vim, action: &WithRange, window, cx| {
|
||||
let result = vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
let result = vim.update_editor(cx, |vim, editor, cx| {
|
||||
action.range.buffer_range(vim, editor, window, cx)
|
||||
});
|
||||
|
||||
|
@ -619,7 +618,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
};
|
||||
|
||||
let previous_selections = vim
|
||||
.update_editor(window, cx, |_, editor, window, cx| {
|
||||
.update_editor(cx, |_, editor, cx| {
|
||||
let selections = action.restore_selection.then(|| {
|
||||
editor
|
||||
.selections
|
||||
|
@ -635,7 +634,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
.flatten();
|
||||
window.dispatch_action(action.action.boxed_clone(), cx);
|
||||
cx.defer_in(window, move |vim, window, cx| {
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
if let Some(previous_selections) = previous_selections {
|
||||
s.select_ranges(previous_selections);
|
||||
|
@ -1536,7 +1535,7 @@ impl OnMatchingLines {
|
|||
}
|
||||
|
||||
pub fn run(&self, vim: &mut Vim, window: &mut Window, cx: &mut Context<Vim>) {
|
||||
let result = vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
let result = vim.update_editor(cx, |vim, editor, cx| {
|
||||
self.range.buffer_range(vim, editor, window, cx)
|
||||
});
|
||||
|
||||
|
@ -1600,7 +1599,7 @@ impl OnMatchingLines {
|
|||
});
|
||||
};
|
||||
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let mut row = range.start.0;
|
||||
|
||||
|
@ -1680,7 +1679,7 @@ pub struct ShellExec {
|
|||
impl Vim {
|
||||
pub fn cancel_running_command(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.running_command.take().is_some() {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, _window, _cx| {
|
||||
editor.clear_row_highlights::<ShellExec>();
|
||||
})
|
||||
|
@ -1691,7 +1690,7 @@ impl Vim {
|
|||
fn prepare_shell_command(
|
||||
&mut self,
|
||||
command: &str,
|
||||
window: &mut Window,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> String {
|
||||
let mut ret = String::new();
|
||||
|
@ -1711,7 +1710,7 @@ impl Vim {
|
|||
}
|
||||
match c {
|
||||
'%' => {
|
||||
self.update_editor(window, cx, |_, editor, _window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
|
||||
if let Some(file) = buffer.read(cx).file() {
|
||||
if let Some(local) = file.as_local() {
|
||||
|
@ -1747,7 +1746,7 @@ impl Vim {
|
|||
let Some(workspace) = self.workspace(window) else {
|
||||
return;
|
||||
};
|
||||
let command = self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
let command = self.update_editor(cx, |_, editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let start = editor.selections.newest_display(cx);
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
|
@ -1794,7 +1793,7 @@ impl Vim {
|
|||
let Some(workspace) = self.workspace(window) else {
|
||||
return;
|
||||
};
|
||||
let command = self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
let command = self.update_editor(cx, |_, editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let start = editor.selections.newest_display(cx);
|
||||
let range = object
|
||||
|
@ -1896,7 +1895,7 @@ impl ShellExec {
|
|||
let mut input_snapshot = None;
|
||||
let mut input_range = None;
|
||||
let mut needs_newline_prefix = false;
|
||||
vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
vim.update_editor(cx, |vim, editor, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let range = if let Some(range) = self.range.clone() {
|
||||
let Some(range) = range.buffer_range(vim, editor, window, cx).log_err() else {
|
||||
|
@ -1990,7 +1989,7 @@ impl ShellExec {
|
|||
}
|
||||
|
||||
vim.update_in(cx, |vim, window, cx| {
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.edit([(range.clone(), text)], cx);
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
|
|
@ -56,9 +56,7 @@ impl Vim {
|
|||
|
||||
self.pop_operator(window, cx);
|
||||
if self.editor_input_enabled() {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
editor.insert(&text, window, cx)
|
||||
});
|
||||
self.update_editor(cx, |_, editor, cx| editor.insert(&text, window, cx));
|
||||
} else {
|
||||
self.input_ignored(text, window, cx);
|
||||
}
|
||||
|
@ -214,9 +212,7 @@ impl Vim {
|
|||
text.push_str(suffix);
|
||||
|
||||
if self.editor_input_enabled() {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
editor.insert(&text, window, cx)
|
||||
});
|
||||
self.update_editor(cx, |_, editor, cx| editor.insert(&text, window, cx));
|
||||
} else {
|
||||
self.input_ignored(text.into(), window, cx);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,
|
||||
) {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
let times = times.unwrap_or(1);
|
||||
|
@ -115,7 +115,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,
|
||||
) {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
let times = times.unwrap_or(1);
|
||||
|
@ -175,7 +175,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
|
@ -253,7 +253,7 @@ impl Vim {
|
|||
})
|
||||
}
|
||||
Motion::FindForward { .. } => {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
|
@ -280,7 +280,7 @@ impl Vim {
|
|||
});
|
||||
}
|
||||
Motion::FindBackward { .. } => {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
|
@ -312,7 +312,7 @@ impl Vim {
|
|||
|
||||
fn helix_insert(&mut self, _: &HelixInsert, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.start_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|_map, selection| {
|
||||
// In helix normal mode, move cursor to start of selection and collapse
|
||||
|
@ -328,7 +328,7 @@ impl Vim {
|
|||
fn helix_append(&mut self, _: &HelixAppend, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.start_recording(cx);
|
||||
self.switch_mode(Mode::Insert, false, window, cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
let point = if selection.is_empty() {
|
||||
|
@ -343,7 +343,7 @@ impl Vim {
|
|||
}
|
||||
|
||||
pub fn helix_replace(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let (map, selections) = editor.selections.all_display(cx);
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
vim.store_visual_marks(window, cx);
|
||||
vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
vim.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let original_positions = vim.save_selection_starts(editor, cx);
|
||||
for _ in 0..count {
|
||||
|
@ -50,7 +50,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
vim.store_visual_marks(window, cx);
|
||||
vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
vim.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let original_positions = vim.save_selection_starts(editor, cx);
|
||||
for _ in 0..count {
|
||||
|
@ -69,7 +69,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
vim.store_visual_marks(window, cx);
|
||||
vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
vim.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let original_positions = vim.save_selection_starts(editor, cx);
|
||||
for _ in 0..count {
|
||||
|
@ -95,7 +95,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let mut selection_starts: HashMap<_, _> = Default::default();
|
||||
|
@ -137,7 +137,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let mut original_positions: HashMap<_, _> = Default::default();
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
|
|
|
@ -38,7 +38,7 @@ impl Vim {
|
|||
if count <= 1 || Vim::globals(cx).dot_replaying {
|
||||
self.create_mark("^".into(), window, cx);
|
||||
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.dismiss_menus_and_popups(false, window, cx);
|
||||
|
||||
if !HelixModeSetting::get_global(cx).0 {
|
||||
|
|
|
@ -679,7 +679,7 @@ impl Vim {
|
|||
match self.mode {
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||
if !prior_selections.is_empty() {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select_ranges(prior_selections.iter().cloned())
|
||||
})
|
||||
|
|
|
@ -132,7 +132,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
|
||||
Vim::action(editor, cx, |vim, _: &HelixDelete, window, cx| {
|
||||
vim.record_current_action(cx);
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if selection.is_empty() {
|
||||
|
@ -146,7 +146,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
});
|
||||
|
||||
Vim::action(editor, cx, |vim, _: &HelixCollapseSelection, window, cx| {
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
let mut point = selection.head();
|
||||
|
@ -198,7 +198,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
Vim::action(editor, cx, |vim, _: &Undo, window, cx| {
|
||||
let times = Vim::take_count(cx);
|
||||
Vim::take_forced_motion(cx);
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
for _ in 0..times.unwrap_or(1) {
|
||||
editor.undo(&editor::actions::Undo, window, cx);
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
Vim::action(editor, cx, |vim, _: &Redo, window, cx| {
|
||||
let times = Vim::take_count(cx);
|
||||
Vim::take_forced_motion(cx);
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
for _ in 0..times.unwrap_or(1) {
|
||||
editor.redo(&editor::actions::Redo, window, cx);
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
});
|
||||
Vim::action(editor, cx, |vim, _: &UndoLastLine, window, cx| {
|
||||
Vim::take_forced_motion(cx);
|
||||
vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
vim.update_editor(cx, |vim, editor, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let Some(last_change) = editor.change_list.last_before_grouping() else {
|
||||
return;
|
||||
|
@ -526,7 +526,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.change_selections(
|
||||
SelectionEffects::default().nav_history(motion.push_to_jump_list()),
|
||||
|
@ -546,7 +546,7 @@ impl Vim {
|
|||
fn insert_after(&mut self, _: &InsertAfter, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.start_recording(cx);
|
||||
self.switch_mode(Mode::Insert, false, window, cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None));
|
||||
});
|
||||
|
@ -557,7 +557,7 @@ impl Vim {
|
|||
self.start_recording(cx);
|
||||
if self.mode.is_visual() {
|
||||
let current_mode = self.mode;
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if current_mode == Mode::VisualLine {
|
||||
|
@ -581,7 +581,7 @@ impl Vim {
|
|||
) {
|
||||
self.start_recording(cx);
|
||||
self.switch_mode(Mode::Insert, false, window, cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(|map, cursor, _| {
|
||||
(
|
||||
|
@ -601,7 +601,7 @@ impl Vim {
|
|||
) {
|
||||
self.start_recording(cx);
|
||||
self.switch_mode(Mode::Insert, false, window, cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(|map, cursor, _| {
|
||||
(next_line_end(map, cursor, 1), SelectionGoal::None)
|
||||
|
@ -618,7 +618,7 @@ impl Vim {
|
|||
) {
|
||||
self.start_recording(cx);
|
||||
self.switch_mode(Mode::Insert, false, window, cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let Some(Mark::Local(marks)) = vim.get_mark("^", editor, window, cx) else {
|
||||
return;
|
||||
};
|
||||
|
@ -637,7 +637,7 @@ impl Vim {
|
|||
) {
|
||||
self.start_recording(cx);
|
||||
self.switch_mode(Mode::Insert, false, window, cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let selections = editor.selections.all::<Point>(cx);
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
@ -678,7 +678,7 @@ impl Vim {
|
|||
) {
|
||||
self.start_recording(cx);
|
||||
self.switch_mode(Mode::Insert, false, window, cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let selections = editor.selections.all::<Point>(cx);
|
||||
|
@ -725,7 +725,7 @@ impl Vim {
|
|||
self.record_current_action(cx);
|
||||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, _, cx| {
|
||||
let selections = editor.selections.all::<Point>(cx);
|
||||
|
||||
|
@ -754,7 +754,7 @@ impl Vim {
|
|||
self.record_current_action(cx);
|
||||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let selections = editor.selections.all::<Point>(cx);
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
@ -804,7 +804,7 @@ impl Vim {
|
|||
times -= 1;
|
||||
}
|
||||
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
for _ in 0..times {
|
||||
editor.join_lines_impl(insert_whitespace, window, cx)
|
||||
|
@ -828,10 +828,10 @@ impl Vim {
|
|||
)
|
||||
}
|
||||
|
||||
fn show_location(&mut self, _: &ShowLocation, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn show_location(&mut self, _: &ShowLocation, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let count = Vim::take_count(cx);
|
||||
Vim::take_forced_motion(cx);
|
||||
self.update_editor(window, cx, |vim, editor, _window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let selection = editor.selections.newest_anchor();
|
||||
let Some((buffer, point, _)) = editor
|
||||
.buffer()
|
||||
|
@ -875,7 +875,7 @@ impl Vim {
|
|||
fn toggle_comments(&mut self, _: &ToggleComments, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.record_current_action(cx);
|
||||
self.store_visual_marks(window, cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let original_positions = vim.save_selection_starts(editor, cx);
|
||||
editor.toggle_comments(&Default::default(), window, cx);
|
||||
|
@ -897,7 +897,7 @@ impl Vim {
|
|||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let (map, display_selections) = editor.selections.all_display(cx);
|
||||
|
|
|
@ -34,7 +34,7 @@ impl Vim {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
||||
|
@ -111,7 +111,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut objects_found = false;
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
|
|
|
@ -31,7 +31,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
|
@ -87,7 +87,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let mut original_positions: HashMap<_, _> = Default::default();
|
||||
|
@ -195,7 +195,7 @@ impl Vim {
|
|||
let count = Vim::take_count(cx).unwrap_or(1) as u32;
|
||||
Vim::take_forced_motion(cx);
|
||||
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let mut ranges = Vec::new();
|
||||
let mut cursor_positions = Vec::new();
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
|
|
@ -22,7 +22,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
|
@ -96,7 +96,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
// Emulates behavior in vim where if we expanded backwards to include a newline
|
||||
|
|
|
@ -53,7 +53,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.store_visual_marks(window, cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let mut edits = Vec::new();
|
||||
let mut new_anchors = Vec::new();
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
|||
|
||||
impl Vim {
|
||||
pub fn create_mark(&mut self, text: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let anchors = editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
|
@ -49,7 +49,7 @@ impl Vim {
|
|||
let mut ends = vec![];
|
||||
let mut reversed = vec![];
|
||||
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let (map, selections) = editor.selections.all_display(cx);
|
||||
for selection in selections {
|
||||
let end = movement::saturating_left(&map, selection.end);
|
||||
|
@ -190,7 +190,7 @@ impl Vim {
|
|||
self.pop_operator(window, cx);
|
||||
}
|
||||
let mark = self
|
||||
.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
.update_editor(cx, |vim, editor, cx| {
|
||||
vim.get_mark(&text, editor, window, cx)
|
||||
})
|
||||
.flatten();
|
||||
|
@ -209,7 +209,7 @@ impl Vim {
|
|||
|
||||
let Some(mut anchors) = anchors else { return };
|
||||
|
||||
self.update_editor(window, cx, |_, editor, _, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.create_nav_history_entry(cx);
|
||||
});
|
||||
let is_active_operator = self.active_operator().is_some();
|
||||
|
@ -231,7 +231,7 @@ impl Vim {
|
|||
|| self.mode == Mode::VisualLine
|
||||
|| self.mode == Mode::VisualBlock;
|
||||
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let map = editor.snapshot(window, cx);
|
||||
let mut ranges: Vec<Range<Anchor>> = Vec::new();
|
||||
for mut anchor in anchors {
|
||||
|
|
|
@ -32,7 +32,7 @@ impl Vim {
|
|||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
|
@ -236,7 +236,7 @@ impl Vim {
|
|||
) {
|
||||
self.stop_recording(cx);
|
||||
let selected_register = self.selected_register.take();
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
|
@ -273,7 +273,7 @@ impl Vim {
|
|||
) {
|
||||
self.stop_recording(cx);
|
||||
let selected_register = self.selected_register.take();
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
|
|
|
@ -97,7 +97,7 @@ impl Vim {
|
|||
let amount = by(Vim::take_count(cx).map(|c| c as f32));
|
||||
Vim::take_forced_motion(cx);
|
||||
self.exit_temporary_normal(window, cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
scroll_editor(editor, move_cursor, &amount, window, cx)
|
||||
});
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ impl Vim {
|
|||
|
||||
// If the active editor has changed during a search, don't panic.
|
||||
if prior_selections.iter().any(|s| {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
!s.start
|
||||
.is_valid(&editor.snapshot(window, cx).buffer_snapshot)
|
||||
})
|
||||
|
@ -457,7 +457,7 @@ impl Vim {
|
|||
else {
|
||||
return;
|
||||
};
|
||||
if let Some(result) = self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
if let Some(result) = self.update_editor(cx, |vim, editor, cx| {
|
||||
let range = action.range.buffer_range(vim, editor, window, cx)?;
|
||||
let snapshot = &editor.snapshot(window, cx).buffer_snapshot;
|
||||
let end_point = Point::new(range.end.0, snapshot.line_len(range.end));
|
||||
|
|
|
@ -45,7 +45,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.store_visual_marks(window, cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
|
|
|
@ -14,7 +14,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let mut selection_starts: HashMap<_, _> = Default::default();
|
||||
|
@ -51,7 +51,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let mut original_positions: HashMap<_, _> = Default::default();
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
|
|
|
@ -25,7 +25,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
|
@ -70,7 +70,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let mut start_positions: HashMap<_, _> = Default::default();
|
||||
|
|
|
@ -49,7 +49,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let map = editor.snapshot(window, cx);
|
||||
|
@ -94,7 +94,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let map = editor.snapshot(window, cx);
|
||||
|
@ -148,7 +148,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let mut selection = editor.selections.newest_display(cx);
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
|
@ -167,7 +167,7 @@ impl Vim {
|
|||
|
||||
pub fn exchange_visual(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let selection = editor.selections.newest_anchor();
|
||||
let new_range = selection.start..selection.end;
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
|
@ -178,7 +178,7 @@ impl Vim {
|
|||
|
||||
pub fn clear_exchange(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, _, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.clear_background_highlights::<VimExchange>(cx);
|
||||
});
|
||||
self.clear_operator(window, cx);
|
||||
|
@ -193,7 +193,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
let mut selection = editor.selections.newest_display(cx);
|
||||
|
|
|
@ -18,7 +18,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
Vim::take_count(cx);
|
||||
Vim::take_forced_motion(cx);
|
||||
vim.store_visual_marks(window, cx);
|
||||
vim.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
vim.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let mut positions = vim.save_selection_starts(editor, cx);
|
||||
editor.rewrap_impl(
|
||||
|
@ -55,7 +55,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let mut selection_starts: HashMap<_, _> = Default::default();
|
||||
|
@ -100,7 +100,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let mut original_positions: HashMap<_, _> = Default::default();
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
|
|
|
@ -29,7 +29,7 @@ impl Vim {
|
|||
let count = Vim::take_count(cx);
|
||||
let forced_motion = Vim::take_forced_motion(cx);
|
||||
let mode = self.mode;
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
|
@ -140,7 +140,7 @@ impl Vim {
|
|||
};
|
||||
let surround = pair.end != *text;
|
||||
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
|
||||
|
@ -228,7 +228,7 @@ impl Vim {
|
|||
) {
|
||||
if let Some(will_replace_pair) = object_to_bracket_pair(target) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
|
||||
|
@ -344,7 +344,7 @@ impl Vim {
|
|||
) -> bool {
|
||||
let mut valid = false;
|
||||
if let Some(pair) = object_to_bracket_pair(object) {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
let (display_map, selections) = editor.selections.all_adjusted_display(cx);
|
||||
|
|
|
@ -748,7 +748,7 @@ impl Vim {
|
|||
editor,
|
||||
cx,
|
||||
|vim, action: &editor::actions::AcceptEditPrediction, window, cx| {
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.accept_edit_prediction(action, window, cx);
|
||||
});
|
||||
// In non-insertion modes, predictions will be hidden and instead a jump will be
|
||||
|
@ -847,7 +847,7 @@ impl Vim {
|
|||
if let Some(action) = keystroke_event.action.as_ref() {
|
||||
// Keystroke is handled by the vim system, so continue forward
|
||||
if action.name().starts_with("vim::") {
|
||||
self.update_editor(window, cx, |_, editor, _, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx)
|
||||
});
|
||||
return;
|
||||
|
@ -909,7 +909,7 @@ impl Vim {
|
|||
anchor,
|
||||
is_deactivate,
|
||||
} => {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let mark = if *is_deactivate {
|
||||
"\"".to_string()
|
||||
} else {
|
||||
|
@ -972,7 +972,7 @@ impl Vim {
|
|||
if mode == Mode::Normal || mode != last_mode {
|
||||
self.current_tx.take();
|
||||
self.current_anchor.take();
|
||||
self.update_editor(window, cx, |_, editor, _, _| {
|
||||
self.update_editor(cx, |_, editor, _| {
|
||||
editor.clear_selection_drag_state();
|
||||
});
|
||||
}
|
||||
|
@ -988,7 +988,7 @@ impl Vim {
|
|||
&& self.mode != self.last_mode
|
||||
&& (self.mode == Mode::Insert || self.last_mode == Mode::Insert)
|
||||
{
|
||||
self.update_editor(window, cx, |vim, editor, _, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let is_relative = vim.mode != Mode::Insert;
|
||||
editor.set_relative_line_number(Some(is_relative), cx)
|
||||
});
|
||||
|
@ -1003,7 +1003,7 @@ impl Vim {
|
|||
}
|
||||
|
||||
// Adjust selections
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
|
||||
{
|
||||
vim.visual_block_motion(true, editor, window, cx, |_, point, goal| {
|
||||
|
@ -1214,7 +1214,7 @@ impl Vim {
|
|||
if preserve_selection {
|
||||
self.switch_mode(Mode::Visual, true, window, cx);
|
||||
} else {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(|_, selection| {
|
||||
|
@ -1232,18 +1232,18 @@ impl Vim {
|
|||
if let Some(old_vim) = Vim::globals(cx).focused_vim() {
|
||||
if old_vim.entity_id() != cx.entity().entity_id() {
|
||||
old_vim.update(cx, |vim, cx| {
|
||||
vim.update_editor(window, cx, |_, editor, _, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.set_relative_line_number(None, cx)
|
||||
});
|
||||
});
|
||||
|
||||
self.update_editor(window, cx, |vim, editor, _, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let is_relative = vim.mode != Mode::Insert;
|
||||
editor.set_relative_line_number(Some(is_relative), cx)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self.update_editor(window, cx, |vim, editor, _, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let is_relative = vim.mode != Mode::Insert;
|
||||
editor.set_relative_line_number(Some(is_relative), cx)
|
||||
});
|
||||
|
@ -1256,35 +1256,30 @@ impl Vim {
|
|||
self.stop_recording_immediately(NormalBefore.boxed_clone(), cx);
|
||||
self.store_visual_marks(window, cx);
|
||||
self.clear_operator(window, cx);
|
||||
self.update_editor(window, cx, |vim, editor, _, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
if vim.cursor_shape(cx) == CursorShape::Block {
|
||||
editor.set_cursor_shape(CursorShape::Hollow, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn cursor_shape_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.update_editor(window, cx, |vim, editor, _, cx| {
|
||||
fn cursor_shape_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.set_cursor_shape(vim.cursor_shape(cx), cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn update_editor<S>(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
update: impl FnOnce(&mut Self, &mut Editor, &mut Window, &mut Context<Editor>) -> S,
|
||||
update: impl FnOnce(&mut Self, &mut Editor, &mut Context<Editor>) -> S,
|
||||
) -> Option<S> {
|
||||
let editor = self.editor.upgrade()?;
|
||||
Some(editor.update(cx, |editor, cx| update(self, editor, window, cx)))
|
||||
Some(editor.update(cx, |editor, cx| update(self, editor, cx)))
|
||||
}
|
||||
|
||||
fn editor_selections(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Vec<Range<Anchor>> {
|
||||
self.update_editor(window, cx, |_, editor, _, _| {
|
||||
fn editor_selections(&mut self, _: &mut Window, cx: &mut Context<Self>) -> Vec<Range<Anchor>> {
|
||||
self.update_editor(cx, |_, editor, _| {
|
||||
editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
|
@ -1300,7 +1295,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<String> {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
|
||||
let snapshot = &editor.snapshot(window, cx).buffer_snapshot;
|
||||
|
@ -1489,7 +1484,7 @@ impl Vim {
|
|||
) {
|
||||
match self.mode {
|
||||
Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let original_mode = vim.undo_modes.get(transaction_id);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
match original_mode {
|
||||
|
@ -1520,7 +1515,7 @@ impl Vim {
|
|||
self.switch_mode(Mode::Normal, true, window, cx)
|
||||
}
|
||||
Mode::Normal => {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
selection
|
||||
|
@ -1547,7 +1542,7 @@ impl Vim {
|
|||
self.current_anchor = Some(newest);
|
||||
} else if self.current_anchor.as_ref().unwrap() != &newest {
|
||||
if let Some(tx_id) = self.current_tx.take() {
|
||||
self.update_editor(window, cx, |_, editor, _, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.group_until_transaction(tx_id, cx)
|
||||
});
|
||||
}
|
||||
|
@ -1694,7 +1689,7 @@ impl Vim {
|
|||
}
|
||||
Some(Operator::Register) => match self.mode {
|
||||
Mode::Insert => {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
if let Some(register) = Vim::update_globals(cx, |globals, cx| {
|
||||
globals.read_register(text.chars().next(), Some(editor), cx)
|
||||
}) {
|
||||
|
@ -1720,7 +1715,7 @@ impl Vim {
|
|||
}
|
||||
|
||||
if self.mode == Mode::Normal {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.accept_edit_prediction(
|
||||
&editor::actions::AcceptEditPrediction {},
|
||||
window,
|
||||
|
@ -1733,7 +1728,7 @@ impl Vim {
|
|||
}
|
||||
|
||||
fn sync_vim_settings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
editor.set_cursor_shape(vim.cursor_shape(cx), cx);
|
||||
editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
|
||||
editor.set_collapse_matches(true);
|
||||
|
|
|
@ -104,7 +104,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
for _ in 0..count {
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.select_larger_syntax_node(&Default::default(), window, cx);
|
||||
});
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
Vim::take_forced_motion(cx);
|
||||
for _ in 0..count {
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.select_smaller_syntax_node(&Default::default(), window, cx);
|
||||
});
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
return;
|
||||
};
|
||||
let marks = vim
|
||||
.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
.update_editor(cx, |vim, editor, cx| {
|
||||
vim.get_mark("<", editor, window, cx)
|
||||
.zip(vim.get_mark(">", editor, window, cx))
|
||||
})
|
||||
|
@ -148,7 +148,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
|||
vim.create_visual_marks(vim.mode, window, cx);
|
||||
}
|
||||
|
||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
let map = s.display_map();
|
||||
|
@ -189,7 +189,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(window);
|
||||
if vim.mode == Mode::VisualBlock
|
||||
&& !matches!(
|
||||
|
@ -397,7 +397,7 @@ impl Vim {
|
|||
self.switch_mode(target_mode, true, window, cx);
|
||||
}
|
||||
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
let mut mut_selection = selection.clone();
|
||||
|
@ -475,7 +475,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.split_selection_into_lines(&Default::default(), window, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(|map, cursor, _| {
|
||||
|
@ -493,7 +493,7 @@ impl Vim {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.split_selection_into_lines(&Default::default(), window, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_cursors_with(|map, cursor, _| {
|
||||
|
@ -517,7 +517,7 @@ impl Vim {
|
|||
}
|
||||
|
||||
pub fn other_end(&mut self, _: &OtherEnd, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|_, selection| {
|
||||
selection.reversed = !selection.reversed;
|
||||
|
@ -533,7 +533,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mode = self.mode;
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|_, selection| {
|
||||
selection.reversed = !selection.reversed;
|
||||
|
@ -547,7 +547,7 @@ impl Vim {
|
|||
|
||||
pub fn visual_delete(&mut self, line_mode: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.store_visual_marks(window, cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let mut original_columns: HashMap<_, _> = Default::default();
|
||||
let line_mode = line_mode || editor.selections.line_mode;
|
||||
editor.selections.line_mode = false;
|
||||
|
@ -631,7 +631,7 @@ impl Vim {
|
|||
|
||||
pub fn visual_yank(&mut self, line_mode: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.store_visual_marks(window, cx);
|
||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
||||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let line_mode = line_mode || editor.selections.line_mode;
|
||||
|
||||
// For visual line mode, adjust selections to avoid yanking the next line when on \n
|
||||
|
@ -679,7 +679,7 @@ impl Vim {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.stop_recording(cx);
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
let (display_map, selections) = editor.selections.all_adjusted_display(cx);
|
||||
|
||||
|
@ -722,7 +722,7 @@ impl Vim {
|
|||
Vim::take_forced_motion(cx);
|
||||
let count =
|
||||
Vim::take_count(cx).unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
for _ in 0..count {
|
||||
if editor
|
||||
|
@ -745,7 +745,7 @@ impl Vim {
|
|||
Vim::take_forced_motion(cx);
|
||||
let count =
|
||||
Vim::take_count(cx).unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
for _ in 0..count {
|
||||
if editor
|
||||
.select_previous(&Default::default(), window, cx)
|
||||
|
@ -773,7 +773,7 @@ impl Vim {
|
|||
let mut start_selection = 0usize;
|
||||
let mut end_selection = 0usize;
|
||||
|
||||
self.update_editor(window, cx, |_, editor, _, _| {
|
||||
self.update_editor(cx, |_, editor, _| {
|
||||
editor.set_collapse_matches(false);
|
||||
});
|
||||
if vim_is_normal {
|
||||
|
@ -791,7 +791,7 @@ impl Vim {
|
|||
}
|
||||
});
|
||||
}
|
||||
self.update_editor(window, cx, |_, editor, _, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let latest = editor.selections.newest::<usize>(cx);
|
||||
start_selection = latest.start;
|
||||
end_selection = latest.end;
|
||||
|
@ -812,7 +812,7 @@ impl Vim {
|
|||
self.stop_replaying(cx);
|
||||
return;
|
||||
}
|
||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let latest = editor.selections.newest::<usize>(cx);
|
||||
if vim_is_normal {
|
||||
start_selection = latest.start;
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 0 B |
|
@ -35,10 +35,7 @@ pub fn app_menus() -> Vec<Menu> {
|
|||
],
|
||||
}),
|
||||
MenuItem::separator(),
|
||||
MenuItem::submenu(Menu {
|
||||
name: "Services".into(),
|
||||
items: vec![],
|
||||
}),
|
||||
MenuItem::os_submenu("Services", gpui::SystemMenuType::Services),
|
||||
MenuItem::separator(),
|
||||
MenuItem::action("Extensions", zed_actions::Extensions::default()),
|
||||
MenuItem::action("Install CLI", install_cli::Install),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue