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:
|
with:
|
||||||
node-version: "18"
|
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
|
- name: Run tests
|
||||||
shell: powershell
|
shell: powershell
|
||||||
working-directory: ${{ inputs.working-directory }}
|
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",
|
||||||
"gpui_tokio",
|
"gpui_tokio",
|
||||||
"handlebars 4.5.0",
|
"handlebars 4.5.0",
|
||||||
|
"html_to_markdown",
|
||||||
|
"http_client",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"language",
|
"language",
|
||||||
|
|
|
@ -174,6 +174,10 @@ impl Diff {
|
||||||
buffer_text
|
buffer_text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_revealed_range(&self, cx: &App) -> bool {
|
||||||
|
self.multibuffer().read(cx).excerpt_paths().next().is_some()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PendingDiff {
|
pub struct PendingDiff {
|
||||||
|
|
|
@ -29,8 +29,14 @@ impl Terminal {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
command: cx
|
command: cx.new(|cx| {
|
||||||
.new(|cx| Markdown::new(command.into(), Some(language_registry.clone()), None, cx)),
|
Markdown::new(
|
||||||
|
format!("```\n{}\n```", command).into(),
|
||||||
|
Some(language_registry.clone()),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
working_dir,
|
working_dir,
|
||||||
terminal,
|
terminal,
|
||||||
started_at: Instant::now(),
|
started_at: Instant::now(),
|
||||||
|
|
|
@ -17,8 +17,6 @@ use util::{
|
||||||
pub struct ActionLog {
|
pub struct ActionLog {
|
||||||
/// Buffers that we want to notify the model about when they change.
|
/// Buffers that we want to notify the model about when they change.
|
||||||
tracked_buffers: BTreeMap<Entity<Buffer>, TrackedBuffer>,
|
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
|
/// The project this action log is associated with
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
}
|
}
|
||||||
|
@ -28,7 +26,6 @@ impl ActionLog {
|
||||||
pub fn new(project: Entity<Project>) -> Self {
|
pub fn new(project: Entity<Project>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tracked_buffers: BTreeMap::default(),
|
tracked_buffers: BTreeMap::default(),
|
||||||
edited_since_project_diagnostics_check: false,
|
|
||||||
project,
|
project,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,16 +34,6 @@ impl ActionLog {
|
||||||
&self.project
|
&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> {
|
pub fn latest_snapshot(&self, buffer: &Entity<Buffer>) -> Option<text::BufferSnapshot> {
|
||||||
Some(self.tracked_buffers.get(buffer)?.snapshot.clone())
|
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
|
/// 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>) {
|
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);
|
self.track_buffer_internal(buffer.clone(), true, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark a buffer as edited by agent, so we can refresh it in the context
|
/// 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>) {
|
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);
|
let tracked_buffer = self.track_buffer_internal(buffer.clone(), false, cx);
|
||||||
if let TrackedBufferStatus::Deleted = tracked_buffer.status {
|
if let TrackedBufferStatus::Deleted = tracked_buffer.status {
|
||||||
tracked_buffer.status = TrackedBufferStatus::Modified;
|
tracked_buffer.status = TrackedBufferStatus::Modified;
|
||||||
|
|
|
@ -27,6 +27,8 @@ fs.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
handlebars = { workspace = true, features = ["rust-embed"] }
|
handlebars = { workspace = true, features = ["rust-embed"] }
|
||||||
|
html_to_markdown.workspace = true
|
||||||
|
http_client.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{AgentResponseEvent, Thread, templates::Templates};
|
use crate::{AgentResponseEvent, Thread, templates::Templates};
|
||||||
use crate::{
|
use crate::{
|
||||||
CopyPathTool, CreateDirectoryTool, EditFileTool, FindPathTool, GrepTool, ListDirectoryTool,
|
CopyPathTool, CreateDirectoryTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool,
|
||||||
MessageContent, MovePathTool, NowTool, OpenTool, ReadFileTool, TerminalTool, ThinkingTool,
|
GrepTool, ListDirectoryTool, MessageContent, MovePathTool, NowTool, OpenTool, ReadFileTool,
|
||||||
ToolCallAuthorization, WebSearchTool,
|
TerminalTool, ThinkingTool, ToolCallAuthorization, WebSearchTool,
|
||||||
};
|
};
|
||||||
use acp_thread::ModelSelector;
|
use acp_thread::ModelSelector;
|
||||||
use agent_client_protocol as acp;
|
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);
|
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(CreateDirectoryTool::new(project.clone()));
|
||||||
thread.add_tool(CopyPathTool::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(MovePathTool::new(project.clone()));
|
||||||
thread.add_tool(ListDirectoryTool::new(project.clone()));
|
thread.add_tool(ListDirectoryTool::new(project.clone()));
|
||||||
thread.add_tool(OpenTool::new(project.clone()));
|
thread.add_tool(OpenTool::new(project.clone()));
|
||||||
thread.add_tool(ThinkingTool);
|
thread.add_tool(ThinkingTool);
|
||||||
thread.add_tool(FindPathTool::new(project.clone()));
|
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(GrepTool::new(project.clone()));
|
||||||
thread.add_tool(ReadFileTool::new(project.clone(), action_log));
|
thread.add_tool(ReadFileTool::new(project.clone(), action_log));
|
||||||
thread.add_tool(EditFileTool::new(cx.entity()));
|
thread.add_tool(EditFileTool::new(cx.entity()));
|
||||||
|
|
|
@ -5,9 +5,11 @@ use action_log::ActionLog;
|
||||||
use agent_client_protocol::{self as acp};
|
use agent_client_protocol::{self as acp};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use client::{Client, UserStore};
|
use client::{Client, UserStore};
|
||||||
use fs::FakeFs;
|
use fs::{FakeFs, Fs};
|
||||||
use futures::channel::mpsc::UnboundedReceiver;
|
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 indoc::indoc;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId,
|
LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId,
|
||||||
|
@ -20,6 +22,7 @@ use reqwest_client::ReqwestClient;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use settings::SettingsStore;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc, time::Duration};
|
use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc, time::Duration};
|
||||||
use util::path;
|
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]
|
#[gpui::test]
|
||||||
|
@ -774,13 +834,17 @@ impl TestModel {
|
||||||
|
|
||||||
async fn setup(cx: &mut TestAppContext, model: TestModel) -> ThreadTest {
|
async fn setup(cx: &mut TestAppContext, model: TestModel) -> ThreadTest {
|
||||||
cx.executor().allow_parking();
|
cx.executor().allow_parking();
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.background_executor.clone());
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
settings::init(cx);
|
settings::init(cx);
|
||||||
|
watch_settings(fs.clone(), cx);
|
||||||
Project::init_settings(cx);
|
Project::init_settings(cx);
|
||||||
|
agent_settings::init(cx);
|
||||||
});
|
});
|
||||||
let templates = Templates::new();
|
let templates = Templates::new();
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background_executor.clone());
|
|
||||||
fs.insert_tree(path!("/test"), json!({})).await;
|
fs.insert_tree(path!("/test"), json!({})).await;
|
||||||
let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
|
let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
|
||||||
|
|
||||||
|
@ -842,3 +906,26 @@ fn init_logger() {
|
||||||
env_logger::init();
|
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,
|
event_stream: ToolCallEventStream,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<String>> {
|
) -> Task<Result<String>> {
|
||||||
let auth_check = event_stream.authorize("Authorize?".into());
|
let authorize = event_stream.authorize("Authorize?", cx);
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
auth_check.await?;
|
authorize.await?;
|
||||||
Ok("Allowed".to_string())
|
Ok("Allowed".to_string())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,12 @@ use crate::{SystemPromptTemplate, Template, Templates};
|
||||||
use acp_thread::MentionUri;
|
use acp_thread::MentionUri;
|
||||||
use action_log::ActionLog;
|
use action_log::ActionLog;
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
|
use agent_settings::AgentSettings;
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_tool::adapt_schema_to_format;
|
use assistant_tool::adapt_schema_to_format;
|
||||||
use cloud_llm_client::{CompletionIntent, CompletionMode};
|
use cloud_llm_client::{CompletionIntent, CompletionMode};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
use fs::Fs;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::{mpsc, oneshot},
|
channel::{mpsc, oneshot},
|
||||||
stream::FuturesUnordered,
|
stream::FuturesUnordered,
|
||||||
|
@ -22,9 +24,10 @@ use project::Project;
|
||||||
use prompt_store::ProjectContext;
|
use prompt_store::ProjectContext;
|
||||||
use schemars::{JsonSchema, Schema};
|
use schemars::{JsonSchema, Schema};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use settings::{Settings, update_settings_file};
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::fmt::Write;
|
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};
|
use util::{ResultExt, markdown::MarkdownCodeBlock};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -529,8 +532,9 @@ impl Thread {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fs = self.project.read(cx).fs().clone();
|
||||||
let tool_event_stream =
|
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 {
|
tool_event_stream.update_fields(acp::ToolCallUpdateFields {
|
||||||
status: Some(acp::ToolCallStatus::InProgress),
|
status: Some(acp::ToolCallStatus::InProgress),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -917,6 +921,7 @@ pub struct ToolCallEventStream {
|
||||||
kind: acp::ToolKind,
|
kind: acp::ToolKind,
|
||||||
input: serde_json::Value,
|
input: serde_json::Value,
|
||||||
stream: AgentResponseEventStream,
|
stream: AgentResponseEventStream,
|
||||||
|
fs: Option<Arc<dyn Fs>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolCallEventStream {
|
impl ToolCallEventStream {
|
||||||
|
@ -935,6 +940,7 @@ impl ToolCallEventStream {
|
||||||
},
|
},
|
||||||
acp::ToolKind::Other,
|
acp::ToolKind::Other,
|
||||||
AgentResponseEventStream(events_tx),
|
AgentResponseEventStream(events_tx),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
(stream, ToolCallEventStreamReceiver(events_rx))
|
(stream, ToolCallEventStreamReceiver(events_rx))
|
||||||
|
@ -944,12 +950,14 @@ impl ToolCallEventStream {
|
||||||
tool_use: &LanguageModelToolUse,
|
tool_use: &LanguageModelToolUse,
|
||||||
kind: acp::ToolKind,
|
kind: acp::ToolKind,
|
||||||
stream: AgentResponseEventStream,
|
stream: AgentResponseEventStream,
|
||||||
|
fs: Option<Arc<dyn Fs>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tool_use_id: tool_use.id.clone(),
|
tool_use_id: tool_use.id.clone(),
|
||||||
kind,
|
kind,
|
||||||
input: tool_use.input.clone(),
|
input: tool_use.input.clone(),
|
||||||
stream,
|
stream,
|
||||||
|
fs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -984,7 +992,11 @@ impl ToolCallEventStream {
|
||||||
.ok();
|
.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();
|
let (response_tx, response_rx) = oneshot::channel();
|
||||||
self.stream
|
self.stream
|
||||||
.0
|
.0
|
||||||
|
@ -992,7 +1004,7 @@ impl ToolCallEventStream {
|
||||||
ToolCallAuthorization {
|
ToolCallAuthorization {
|
||||||
tool_call: AgentResponseEventStream::initial_tool_call(
|
tool_call: AgentResponseEventStream::initial_tool_call(
|
||||||
&self.tool_use_id,
|
&self.tool_use_id,
|
||||||
title,
|
title.into(),
|
||||||
self.kind.clone(),
|
self.kind.clone(),
|
||||||
self.input.clone(),
|
self.input.clone(),
|
||||||
),
|
),
|
||||||
|
@ -1017,12 +1029,22 @@ impl ToolCallEventStream {
|
||||||
},
|
},
|
||||||
)))
|
)))
|
||||||
.ok();
|
.ok();
|
||||||
async move {
|
let fs = self.fs.clone();
|
||||||
match response_rx.await?.0.as_ref() {
|
cx.spawn(async move |cx| match response_rx.await?.0.as_ref() {
|
||||||
"allow" | "always_allow" => Ok(()),
|
"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")),
|
_ => Err(anyhow!("Permission to run tool denied by user")),
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
mod copy_path_tool;
|
mod copy_path_tool;
|
||||||
mod create_directory_tool;
|
mod create_directory_tool;
|
||||||
mod delete_path_tool;
|
mod delete_path_tool;
|
||||||
|
mod diagnostics_tool;
|
||||||
mod edit_file_tool;
|
mod edit_file_tool;
|
||||||
|
mod fetch_tool;
|
||||||
mod find_path_tool;
|
mod find_path_tool;
|
||||||
mod grep_tool;
|
mod grep_tool;
|
||||||
mod list_directory_tool;
|
mod list_directory_tool;
|
||||||
|
@ -16,7 +18,9 @@ mod web_search_tool;
|
||||||
pub use copy_path_tool::*;
|
pub use copy_path_tool::*;
|
||||||
pub use create_directory_tool::*;
|
pub use create_directory_tool::*;
|
||||||
pub use delete_path_tool::*;
|
pub use delete_path_tool::*;
|
||||||
|
pub use diagnostics_tool::*;
|
||||||
pub use edit_file_tool::*;
|
pub use edit_file_tool::*;
|
||||||
|
pub use fetch_tool::*;
|
||||||
pub use find_path_tool::*;
|
pub use find_path_tool::*;
|
||||||
pub use grep_tool::*;
|
pub use grep_tool::*;
|
||||||
pub use list_directory_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,
|
&self,
|
||||||
input: &EditFileToolInput,
|
input: &EditFileToolInput,
|
||||||
event_stream: &ToolCallEventStream,
|
event_stream: &ToolCallEventStream,
|
||||||
cx: &App,
|
cx: &mut App,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
if agent_settings::AgentSettings::get_global(cx).always_allow_tool_actions {
|
if agent_settings::AgentSettings::get_global(cx).always_allow_tool_actions {
|
||||||
return Task::ready(Ok(()));
|
return Task::ready(Ok(()));
|
||||||
|
@ -147,8 +147,9 @@ impl EditFileTool {
|
||||||
.components()
|
.components()
|
||||||
.any(|component| component.as_os_str() == local_settings_folder.as_os_str())
|
.any(|component| component.as_os_str() == local_settings_folder.as_os_str())
|
||||||
{
|
{
|
||||||
return cx.foreground_executor().spawn(
|
return event_stream.authorize(
|
||||||
event_stream.authorize(format!("{} (local settings)", input.display_description)),
|
format!("{} (local settings)", input.display_description),
|
||||||
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,9 +157,9 @@ impl EditFileTool {
|
||||||
// so check for that edge case too.
|
// so check for that edge case too.
|
||||||
if let Ok(canonical_path) = std::fs::canonicalize(&input.path) {
|
if let Ok(canonical_path) = std::fs::canonicalize(&input.path) {
|
||||||
if canonical_path.starts_with(paths::config_dir()) {
|
if canonical_path.starts_with(paths::config_dir()) {
|
||||||
return cx.foreground_executor().spawn(
|
return event_stream.authorize(
|
||||||
event_stream
|
format!("{} (global settings)", input.display_description),
|
||||||
.authorize(format!("{} (global settings)", input.display_description)),
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,8 +174,7 @@ impl EditFileTool {
|
||||||
if project_path.is_some() {
|
if project_path.is_some() {
|
||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
} else {
|
} else {
|
||||||
cx.foreground_executor()
|
event_stream.authorize(&input.display_description, cx)
|
||||||
.spawn(event_stream.authorize(input.display_description.clone()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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>> {
|
) -> Task<Result<Self::Output>> {
|
||||||
// If path_or_url turns out to be a path in the project, make it absolute.
|
// 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 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 {
|
cx.background_spawn(async move {
|
||||||
authorize.await?;
|
authorize.await?;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ use gpui::{App, AppContext, Entity, SharedString, Task};
|
||||||
use project::{Project, terminals::TerminalKind};
|
use project::{Project, terminals::TerminalKind};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -61,21 +60,6 @@ impl TerminalTool {
|
||||||
determine_shell: determine_shell.shared(),
|
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 {
|
impl AgentTool for TerminalTool {
|
||||||
|
@ -152,7 +136,7 @@ impl AgentTool for TerminalTool {
|
||||||
env
|
env
|
||||||
});
|
});
|
||||||
|
|
||||||
let authorize = self.authorize(&input, &event_stream, cx);
|
let authorize = event_stream.authorize(self.initial_title(Ok(input.clone())), cx);
|
||||||
|
|
||||||
cx.spawn({
|
cx.spawn({
|
||||||
async move |cx| {
|
async move |cx| {
|
||||||
|
|
|
@ -39,7 +39,7 @@ use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
Disclosure, Divider, DividerColor, KeyBinding, Scrollbar, ScrollbarState, Tooltip, prelude::*,
|
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 workspace::{CollaboratorId, Workspace};
|
||||||
use zed_actions::agent::{Chat, NextHistoryMessage, PreviousHistoryMessage};
|
use zed_actions::agent::{Chat, NextHistoryMessage, PreviousHistoryMessage};
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ pub struct AcpThreadView {
|
||||||
edits_expanded: bool,
|
edits_expanded: bool,
|
||||||
plan_expanded: bool,
|
plan_expanded: bool,
|
||||||
editor_expanded: bool,
|
editor_expanded: bool,
|
||||||
|
terminal_expanded: bool,
|
||||||
message_history: Rc<RefCell<MessageHistory<Vec<acp::ContentBlock>>>>,
|
message_history: Rc<RefCell<MessageHistory<Vec<acp::ContentBlock>>>>,
|
||||||
_cancel_task: Option<Task<()>>,
|
_cancel_task: Option<Task<()>>,
|
||||||
_subscriptions: [Subscription; 1],
|
_subscriptions: [Subscription; 1],
|
||||||
|
@ -201,6 +202,7 @@ impl AcpThreadView {
|
||||||
edits_expanded: false,
|
edits_expanded: false,
|
||||||
plan_expanded: false,
|
plan_expanded: false,
|
||||||
editor_expanded: false,
|
editor_expanded: false,
|
||||||
|
terminal_expanded: true,
|
||||||
message_history,
|
message_history,
|
||||||
_subscriptions: [subscription],
|
_subscriptions: [subscription],
|
||||||
_cancel_task: None,
|
_cancel_task: None,
|
||||||
|
@ -796,7 +798,7 @@ impl AcpThreadView {
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
view.set_embedded_mode(None, cx);
|
view.set_embedded_mode(Some(1000), cx);
|
||||||
view
|
view
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -942,17 +944,26 @@ impl AcpThreadView {
|
||||||
.child(message_body)
|
.child(message_body)
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
AgentThreadEntry::ToolCall(tool_call) => div()
|
AgentThreadEntry::ToolCall(tool_call) => {
|
||||||
.w_full()
|
let has_terminals = tool_call.terminals().next().is_some();
|
||||||
.py_1p5()
|
|
||||||
.px_5()
|
div().w_full().py_1p5().px_5().map(|this| {
|
||||||
.child(self.render_tool_call(index, tool_call, window, cx))
|
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(),
|
.into_any(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(thread) = self.thread() else {
|
let Some(thread) = self.thread() else {
|
||||||
return primary;
|
return primary;
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_generating = matches!(thread.read(cx).status(), ThreadStatus::Generating);
|
let is_generating = matches!(thread.read(cx).status(), ThreadStatus::Generating);
|
||||||
if index == total_entries - 1 && !is_generating {
|
if index == total_entries - 1 && !is_generating {
|
||||||
v_flex()
|
v_flex()
|
||||||
|
@ -1181,19 +1192,27 @@ impl AcpThreadView {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let needs_confirmation = match &tool_call.status {
|
let needs_confirmation = matches!(
|
||||||
ToolCallStatus::WaitingForConfirmation { .. } => true,
|
tool_call.status,
|
||||||
_ => tool_call
|
ToolCallStatus::WaitingForConfirmation { .. }
|
||||||
|
);
|
||||||
|
let is_edit = matches!(tool_call.kind, acp::ToolKind::Edit);
|
||||||
|
let has_diff = tool_call
|
||||||
.content
|
.content
|
||||||
.iter()
|
.iter()
|
||||||
.any(|content| matches!(content, ToolCallContent::Diff(_))),
|
.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 gradient_overlay = |color: Hsla| {
|
||||||
let is_open = !is_collapsible || self.expanded_tool_calls.contains(&tool_call.id);
|
|
||||||
|
|
||||||
let gradient_color = cx.theme().colors().panel_background;
|
|
||||||
let gradient_overlay = {
|
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.top_0()
|
.top_0()
|
||||||
|
@ -1202,13 +1221,13 @@ impl AcpThreadView {
|
||||||
.h_full()
|
.h_full()
|
||||||
.bg(linear_gradient(
|
.bg(linear_gradient(
|
||||||
90.,
|
90.,
|
||||||
linear_color_stop(gradient_color, 1.),
|
linear_color_stop(color, 1.),
|
||||||
linear_color_stop(gradient_color.opacity(0.2), 0.),
|
linear_color_stop(color.opacity(0.2), 0.),
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.when(needs_confirmation, |this| {
|
.when(needs_confirmation || is_edit || has_diff, |this| {
|
||||||
this.rounded_lg()
|
this.rounded_lg()
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(self.tool_card_border_color(cx))
|
.border_color(self.tool_card_border_color(cx))
|
||||||
|
@ -1222,7 +1241,7 @@ impl AcpThreadView {
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if needs_confirmation {
|
if needs_confirmation || is_edit || has_diff {
|
||||||
this.pl_2()
|
this.pl_2()
|
||||||
.pr_1()
|
.pr_1()
|
||||||
.py_1()
|
.py_1()
|
||||||
|
@ -1299,13 +1318,23 @@ impl AcpThreadView {
|
||||||
.child(self.render_markdown(
|
.child(self.render_markdown(
|
||||||
tool_call.label.clone(),
|
tool_call.label.clone(),
|
||||||
default_markdown_style(
|
default_markdown_style(
|
||||||
needs_confirmation,
|
needs_confirmation || is_edit || has_diff,
|
||||||
window,
|
window,
|
||||||
cx,
|
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({
|
.on_click(cx.listener({
|
||||||
let id = tool_call.id.clone();
|
let id = tool_call.id.clone();
|
||||||
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
|
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
|
||||||
|
@ -1340,11 +1369,9 @@ impl AcpThreadView {
|
||||||
.children(tool_call.content.iter().map(|content| {
|
.children(tool_call.content.iter().map(|content| {
|
||||||
div()
|
div()
|
||||||
.py_1p5()
|
.py_1p5()
|
||||||
.child(
|
.child(self.render_tool_call_content(
|
||||||
self.render_tool_call_content(
|
content, tool_call, window, cx,
|
||||||
content, window, cx,
|
))
|
||||||
),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}))
|
}))
|
||||||
.child(self.render_permission_buttons(
|
.child(self.render_permission_buttons(
|
||||||
|
@ -1358,11 +1385,9 @@ impl AcpThreadView {
|
||||||
this.children(tool_call.content.iter().map(|content| {
|
this.children(tool_call.content.iter().map(|content| {
|
||||||
div()
|
div()
|
||||||
.py_1p5()
|
.py_1p5()
|
||||||
.child(
|
.child(self.render_tool_call_content(
|
||||||
self.render_tool_call_content(
|
content, tool_call, window, cx,
|
||||||
content, window, cx,
|
))
|
||||||
),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -1379,6 +1404,7 @@ impl AcpThreadView {
|
||||||
fn render_tool_call_content(
|
fn render_tool_call_content(
|
||||||
&self,
|
&self,
|
||||||
content: &ToolCallContent,
|
content: &ToolCallContent,
|
||||||
|
tool_call: &ToolCall,
|
||||||
window: &Window,
|
window: &Window,
|
||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
|
@ -1399,7 +1425,9 @@ impl AcpThreadView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolCallContent::Diff(diff) => self.render_diff_editor(&diff.read(cx).multibuffer()),
|
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>,
|
cx: &Context<Self>,
|
||||||
) -> Div {
|
) -> Div {
|
||||||
h_flex()
|
h_flex()
|
||||||
.p_1p5()
|
.py_1()
|
||||||
|
.pl_2()
|
||||||
|
.pr_1()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.justify_end()
|
.justify_between()
|
||||||
|
.flex_wrap()
|
||||||
.when(!empty_content, |this| {
|
.when(!empty_content, |this| {
|
||||||
this.border_t_1()
|
this.border_t_1()
|
||||||
.border_color(self.tool_card_border_color(cx))
|
.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());
|
let option_id = SharedString::from(option.id.0.clone());
|
||||||
Button::new((option_id, entry_ix), option.name.clone())
|
Button::new((option_id, entry_ix), option.name.clone())
|
||||||
.map(|this| match option.kind {
|
.map(|this| match option.kind {
|
||||||
|
@ -1452,7 +1488,7 @@ impl AcpThreadView {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_diff_editor(&self, multibuffer: &Entity<MultiBuffer>) -> AnyElement {
|
fn render_diff_editor(&self, multibuffer: &Entity<MultiBuffer>) -> AnyElement {
|
||||||
|
@ -1468,18 +1504,242 @@ impl AcpThreadView {
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_terminal(&self, terminal: &Entity<acp_thread::Terminal>) -> AnyElement {
|
fn render_terminal_tool_call(
|
||||||
v_flex()
|
&self,
|
||||||
.h_72()
|
terminal: &Entity<acp_thread::Terminal>,
|
||||||
.child(
|
tool_call: &ToolCall,
|
||||||
if let Some(terminal_view) = self.terminal_views.get(&terminal.entity_id()) {
|
window: &Window,
|
||||||
// TODO: terminal has all the state we need to reproduce
|
cx: &Context<Self>,
|
||||||
// what we had in the terminal card.
|
) -> AnyElement {
|
||||||
terminal_view.clone().into_any_element()
|
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 {
|
} else {
|
||||||
Empty.into_any()
|
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(
|
||||||
|
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()
|
.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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use agent_client_protocol::SessionId;
|
use agent_client_protocol::SessionId;
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl Tool for DiagnosticsTool {
|
||||||
input: serde_json::Value,
|
input: serde_json::Value,
|
||||||
_request: Arc<LanguageModelRequest>,
|
_request: Arc<LanguageModelRequest>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
action_log: Entity<ActionLog>,
|
_action_log: Entity<ActionLog>,
|
||||||
_model: Arc<dyn LanguageModel>,
|
_model: Arc<dyn LanguageModel>,
|
||||||
_window: Option<AnyWindowHandle>,
|
_window: Option<AnyWindowHandle>,
|
||||||
cx: &mut App,
|
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 {
|
if has_diagnostics {
|
||||||
Task::ready(Ok(output.into())).into()
|
Task::ready(Ok(output.into())).into()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -152,6 +152,9 @@ impl PythonDebugAdapter {
|
||||||
maybe!(async move {
|
maybe!(async move {
|
||||||
let response = latest_release.filter(|response| response.status().is_success())?;
|
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();
|
let mut output = String::new();
|
||||||
response
|
response
|
||||||
.into_body()
|
.into_body()
|
||||||
|
|
|
@ -2172,6 +2172,9 @@ impl Fs for FakeFs {
|
||||||
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
|
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
|
||||||
self.simulate_random_delay().await;
|
self.simulate_random_delay().await;
|
||||||
let path = normalize_path(path.as_path());
|
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)?;
|
self.write_file_internal(path, data.into_bytes(), true)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use gpui::{
|
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;
|
struct SetMenus;
|
||||||
|
@ -27,7 +28,11 @@ fn main() {
|
||||||
// Add menu items
|
// Add menu items
|
||||||
cx.set_menus(vec![Menu {
|
cx.set_menus(vec![Menu {
|
||||||
name: "set_menus".into(),
|
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 {}))
|
cx.open_window(WindowOptions::default(), |_, cx| cx.new(|_| SetMenus {}))
|
||||||
.unwrap();
|
.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
|
/// The different kinds of items that can be in a menu
|
||||||
pub enum MenuItem {
|
pub enum MenuItem {
|
||||||
/// A separator between items
|
/// A separator between items
|
||||||
|
@ -28,6 +56,9 @@ pub enum MenuItem {
|
||||||
/// A submenu
|
/// A submenu
|
||||||
Submenu(Menu),
|
Submenu(Menu),
|
||||||
|
|
||||||
|
/// A menu, managed by the system (for example, the Services menu on macOS)
|
||||||
|
SystemMenu(OsMenu),
|
||||||
|
|
||||||
/// An action that can be performed
|
/// An action that can be performed
|
||||||
Action {
|
Action {
|
||||||
/// The name of this menu item
|
/// The name of this menu item
|
||||||
|
@ -53,6 +84,14 @@ impl MenuItem {
|
||||||
Self::Submenu(menu)
|
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
|
/// Creates a new menu item that invokes an action
|
||||||
pub fn action(name: impl Into<SharedString>, action: impl Action) -> Self {
|
pub fn action(name: impl Into<SharedString>, action: impl Action) -> Self {
|
||||||
Self::Action {
|
Self::Action {
|
||||||
|
@ -89,10 +128,23 @@ impl MenuItem {
|
||||||
action,
|
action,
|
||||||
os_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
|
/// A menu of the application, either a main menu or a submenu
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OwnedMenu {
|
pub struct OwnedMenu {
|
||||||
|
@ -111,6 +163,9 @@ pub enum OwnedMenuItem {
|
||||||
/// A submenu
|
/// A submenu
|
||||||
Submenu(OwnedMenu),
|
Submenu(OwnedMenu),
|
||||||
|
|
||||||
|
/// A menu, managed by the system (for example, the Services menu on macOS)
|
||||||
|
SystemMenu(OwnedOsMenu),
|
||||||
|
|
||||||
/// An action that can be performed
|
/// An action that can be performed
|
||||||
Action {
|
Action {
|
||||||
/// The name of this menu item
|
/// The name of this menu item
|
||||||
|
@ -139,6 +194,7 @@ impl Clone for OwnedMenuItem {
|
||||||
action: action.boxed_clone(),
|
action: action.boxed_clone(),
|
||||||
os_action: *os_action,
|
os_action: *os_action,
|
||||||
},
|
},
|
||||||
|
OwnedMenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ use super::{
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString,
|
Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString,
|
||||||
CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher,
|
CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher,
|
||||||
MacDisplay, MacWindow, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay,
|
MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform,
|
||||||
PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task,
|
PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result,
|
||||||
WindowAppearance, WindowParams, hash,
|
SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams, hash,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, anyhow};
|
use anyhow::{Context as _, anyhow};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
|
@ -413,10 +413,21 @@ impl MacPlatform {
|
||||||
}
|
}
|
||||||
item.setSubmenu_(submenu);
|
item.setSubmenu_(submenu);
|
||||||
item.setTitle_(ns_string(&name));
|
item.setTitle_(ns_string(&name));
|
||||||
if name == "Services" {
|
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];
|
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||||
app.setServicesMenu_(item);
|
app.setServicesMenu_(item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
|
|
@ -476,7 +476,7 @@ pub(crate) struct Underline {
|
||||||
pub content_mask: ContentMask<ScaledPixels>,
|
pub content_mask: ContentMask<ScaledPixels>,
|
||||||
pub color: Hsla,
|
pub color: Hsla,
|
||||||
pub thickness: ScaledPixels,
|
pub thickness: ScaledPixels,
|
||||||
pub wavy: bool,
|
pub wavy: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Underline> for Primitive {
|
impl From<Underline> for Primitive {
|
||||||
|
|
|
@ -2814,7 +2814,7 @@ impl Window {
|
||||||
content_mask: content_mask.scale(scale_factor),
|
content_mask: content_mask.scale(scale_factor),
|
||||||
color: style.color.unwrap_or_default().opacity(element_opacity),
|
color: style.color.unwrap_or_default().opacity(element_opacity),
|
||||||
thickness: style.thickness.scale(scale_factor),
|
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),
|
content_mask: content_mask.scale(scale_factor),
|
||||||
thickness: style.thickness.scale(scale_factor),
|
thickness: style.thickness.scale(scale_factor),
|
||||||
color: style.color.unwrap_or_default().opacity(opacity),
|
color: style.color.unwrap_or_default().opacity(opacity),
|
||||||
wavy: false,
|
wavy: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,8 +121,16 @@ impl ApplicationMenu {
|
||||||
menu.action(name, action)
|
menu.action(name, action)
|
||||||
}
|
}
|
||||||
OwnedMenuItem::Submenu(_) => menu,
|
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,
|
id: ElementId,
|
||||||
toggle_state: ToggleState,
|
toggle_state: ToggleState,
|
||||||
disabled: bool,
|
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>,
|
label: Option<SharedString>,
|
||||||
key_binding: Option<KeyBinding>,
|
key_binding: Option<KeyBinding>,
|
||||||
color: SwitchColor,
|
color: SwitchColor,
|
||||||
|
@ -459,7 +459,7 @@ impl Switch {
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
|
handler: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.on_click = Some(Box::new(handler));
|
self.on_click = Some(Rc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,10 +513,16 @@ impl RenderOnce for Switch {
|
||||||
.when_some(
|
.when_some(
|
||||||
self.tab_index.filter(|_| !self.disabled),
|
self.tab_index.filter(|_| !self.disabled),
|
||||||
|this, tab_index| {
|
|this, tab_index| {
|
||||||
this.tab_index(tab_index).focus(|mut style| {
|
this.tab_index(tab_index)
|
||||||
|
.focus(|mut style| {
|
||||||
style.border_color = Some(cx.theme().colors().border_focused);
|
style.border_color = Some(cx.theme().colors().border_focused);
|
||||||
style
|
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(
|
.child(
|
||||||
|
|
|
@ -31,7 +31,7 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
let count = Vim::take_count(cx).unwrap_or(1);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
if let Some(selections) = editor
|
if let Some(selections) = editor
|
||||||
.change_list
|
.change_list
|
||||||
.next_change(count, direction)
|
.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>) {
|
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 (map, selections) = editor.selections.all_adjusted_display(cx);
|
||||||
let buffer = editor.buffer().clone();
|
let buffer = editor.buffer().clone();
|
||||||
|
|
||||||
|
|
|
@ -241,9 +241,9 @@ impl Deref for WrappedAction {
|
||||||
|
|
||||||
pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
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: &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() {
|
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) => {
|
VimOption::Wrap(true) => {
|
||||||
editor
|
editor
|
||||||
.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
.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::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 {
|
let Some(project) = editor.project.clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -375,7 +375,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
vim.update_editor(window, cx, |vim, editor, window, cx| match action {
|
vim.update_editor(cx, |vim, editor, cx| match action {
|
||||||
DeleteMarks::Marks(s) => {
|
DeleteMarks::Marks(s) => {
|
||||||
if s.starts_with('-') || s.ends_with('-') || s.contains(['\'', '`']) {
|
if s.starts_with('-') || s.ends_with('-') || s.contains(['\'', '`']) {
|
||||||
err(s.clone(), window, cx);
|
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::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 {
|
let Some(workspace) = vim.workspace(window) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -462,11 +462,10 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||||
.map(|c| Keystroke::parse(&c.to_string()).unwrap())
|
.map(|c| Keystroke::parse(&c.to_string()).unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
vim.switch_mode(Mode::Normal, true, window, cx);
|
vim.switch_mode(Mode::Normal, true, window, cx);
|
||||||
let initial_selections = vim.update_editor(window, cx, |_, editor, _, _| {
|
let initial_selections =
|
||||||
editor.selections.disjoint_anchors()
|
vim.update_editor(cx, |_, editor, _| editor.selections.disjoint_anchors());
|
||||||
});
|
|
||||||
if let Some(range) = &action.range {
|
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)?;
|
let range = range.buffer_range(vim, editor, window, cx)?;
|
||||||
editor.change_selections(
|
editor.change_selections(
|
||||||
SelectionEffects::no_scroll().nav_history(false),
|
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| {
|
cx.spawn_in(window, async move |vim, cx| {
|
||||||
task.await;
|
task.await;
|
||||||
vim.update_in(cx, |vim, window, cx| {
|
vim.update_in(cx, |vim, window, cx| {
|
||||||
vim.update_editor(window, cx, |_, editor, window, cx| {
|
vim.update_editor(cx, |_, editor, cx| {
|
||||||
if had_range {
|
if had_range {
|
||||||
editor.change_selections(SelectionEffects::default(), window, cx, |s| {
|
editor.change_selections(SelectionEffects::default(), window, cx, |s| {
|
||||||
s.select_anchor_ranges([s.newest_anchor().range()]);
|
s.select_anchor_ranges([s.newest_anchor().range()]);
|
||||||
|
@ -510,7 +509,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||||
} else {
|
} else {
|
||||||
vim.switch_mode(Mode::Normal, true, window, cx);
|
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(first_sel) = initial_selections {
|
||||||
if let Some(tx_id) = editor
|
if let Some(tx_id) = editor
|
||||||
.buffer()
|
.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::action(editor, cx, |vim, action: &GoToLine, window, cx| {
|
||||||
vim.switch_mode(Mode::Normal, false, 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 snapshot = editor.snapshot(window, cx);
|
||||||
let buffer_row = action.range.head().buffer_row(vim, editor, window, cx)?;
|
let buffer_row = action.range.head().buffer_row(vim, editor, window, cx)?;
|
||||||
let current = editor.selections.newest::<Point>(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::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);
|
let snapshot = editor.snapshot(window, cx);
|
||||||
if let Ok(range) = action.range.buffer_range(vim, editor, window, cx) {
|
if let Ok(range) = action.range.buffer_range(vim, editor, window, cx) {
|
||||||
let end = if range.end < snapshot.buffer_snapshot.max_row() {
|
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| {
|
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)
|
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
|
let previous_selections = vim
|
||||||
.update_editor(window, cx, |_, editor, window, cx| {
|
.update_editor(cx, |_, editor, cx| {
|
||||||
let selections = action.restore_selection.then(|| {
|
let selections = action.restore_selection.then(|| {
|
||||||
editor
|
editor
|
||||||
.selections
|
.selections
|
||||||
|
@ -635,7 +634,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||||
.flatten();
|
.flatten();
|
||||||
window.dispatch_action(action.action.boxed_clone(), cx);
|
window.dispatch_action(action.action.boxed_clone(), cx);
|
||||||
cx.defer_in(window, move |vim, window, 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| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
if let Some(previous_selections) = previous_selections {
|
if let Some(previous_selections) = previous_selections {
|
||||||
s.select_ranges(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>) {
|
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)
|
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 snapshot = editor.snapshot(window, cx);
|
||||||
let mut row = range.start.0;
|
let mut row = range.start.0;
|
||||||
|
|
||||||
|
@ -1680,7 +1679,7 @@ pub struct ShellExec {
|
||||||
impl Vim {
|
impl Vim {
|
||||||
pub fn cancel_running_command(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn cancel_running_command(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if self.running_command.take().is_some() {
|
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.transact(window, cx, |editor, _window, _cx| {
|
||||||
editor.clear_row_highlights::<ShellExec>();
|
editor.clear_row_highlights::<ShellExec>();
|
||||||
})
|
})
|
||||||
|
@ -1691,7 +1690,7 @@ impl Vim {
|
||||||
fn prepare_shell_command(
|
fn prepare_shell_command(
|
||||||
&mut self,
|
&mut self,
|
||||||
command: &str,
|
command: &str,
|
||||||
window: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
|
@ -1711,7 +1710,7 @@ impl Vim {
|
||||||
}
|
}
|
||||||
match c {
|
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((_, buffer, _)) = editor.active_excerpt(cx) {
|
||||||
if let Some(file) = buffer.read(cx).file() {
|
if let Some(file) = buffer.read(cx).file() {
|
||||||
if let Some(local) = file.as_local() {
|
if let Some(local) = file.as_local() {
|
||||||
|
@ -1747,7 +1746,7 @@ impl Vim {
|
||||||
let Some(workspace) = self.workspace(window) else {
|
let Some(workspace) = self.workspace(window) else {
|
||||||
return;
|
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 snapshot = editor.snapshot(window, cx);
|
||||||
let start = editor.selections.newest_display(cx);
|
let start = editor.selections.newest_display(cx);
|
||||||
let text_layout_details = editor.text_layout_details(window);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
|
@ -1794,7 +1793,7 @@ impl Vim {
|
||||||
let Some(workspace) = self.workspace(window) else {
|
let Some(workspace) = self.workspace(window) else {
|
||||||
return;
|
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 snapshot = editor.snapshot(window, cx);
|
||||||
let start = editor.selections.newest_display(cx);
|
let start = editor.selections.newest_display(cx);
|
||||||
let range = object
|
let range = object
|
||||||
|
@ -1896,7 +1895,7 @@ impl ShellExec {
|
||||||
let mut input_snapshot = None;
|
let mut input_snapshot = None;
|
||||||
let mut input_range = None;
|
let mut input_range = None;
|
||||||
let mut needs_newline_prefix = false;
|
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 snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
let range = if let Some(range) = self.range.clone() {
|
let range = if let Some(range) = self.range.clone() {
|
||||||
let Some(range) = range.buffer_range(vim, editor, window, cx).log_err() else {
|
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_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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.edit([(range.clone(), text)], cx);
|
editor.edit([(range.clone(), text)], cx);
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
|
|
@ -56,9 +56,7 @@ impl Vim {
|
||||||
|
|
||||||
self.pop_operator(window, cx);
|
self.pop_operator(window, cx);
|
||||||
if self.editor_input_enabled() {
|
if self.editor_input_enabled() {
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(cx, |_, editor, cx| editor.insert(&text, window, cx));
|
||||||
editor.insert(&text, window, cx)
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
self.input_ignored(text, window, cx);
|
self.input_ignored(text, window, cx);
|
||||||
}
|
}
|
||||||
|
@ -214,9 +212,7 @@ impl Vim {
|
||||||
text.push_str(suffix);
|
text.push_str(suffix);
|
||||||
|
|
||||||
if self.editor_input_enabled() {
|
if self.editor_input_enabled() {
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(cx, |_, editor, cx| editor.insert(&text, window, cx));
|
||||||
editor.insert(&text, window, cx)
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
self.input_ignored(text.into(), window, cx);
|
self.input_ignored(text.into(), window, cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,
|
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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let times = times.unwrap_or(1);
|
let times = times.unwrap_or(1);
|
||||||
|
@ -115,7 +115,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,
|
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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let times = times.unwrap_or(1);
|
let times = times.unwrap_or(1);
|
||||||
|
@ -175,7 +175,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.change_selections(Default::default(), window, cx, |s| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
|
@ -253,7 +253,7 @@ impl Vim {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Motion::FindForward { .. } => {
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.change_selections(Default::default(), window, cx, |s| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
|
@ -280,7 +280,7 @@ impl Vim {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Motion::FindBackward { .. } => {
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.change_selections(Default::default(), window, cx, |s| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
|
@ -312,7 +312,7 @@ impl Vim {
|
||||||
|
|
||||||
fn helix_insert(&mut self, _: &HelixInsert, window: &mut Window, cx: &mut Context<Self>) {
|
fn helix_insert(&mut self, _: &HelixInsert, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.start_recording(cx);
|
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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|_map, selection| {
|
s.move_with(|_map, selection| {
|
||||||
// In helix normal mode, move cursor to start of selection and collapse
|
// 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>) {
|
fn helix_append(&mut self, _: &HelixAppend, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.start_recording(cx);
|
self.start_recording(cx);
|
||||||
self.switch_mode(Mode::Insert, false, window, 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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let point = if selection.is_empty() {
|
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>) {
|
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| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let (map, selections) = editor.selections.all_display(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);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
vim.store_visual_marks(window, 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| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let original_positions = vim.save_selection_starts(editor, cx);
|
let original_positions = vim.save_selection_starts(editor, cx);
|
||||||
for _ in 0..count {
|
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);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
vim.store_visual_marks(window, 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| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let original_positions = vim.save_selection_starts(editor, cx);
|
let original_positions = vim.save_selection_starts(editor, cx);
|
||||||
for _ in 0..count {
|
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);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
vim.store_visual_marks(window, 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| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let original_positions = vim.save_selection_starts(editor, cx);
|
let original_positions = vim.save_selection_starts(editor, cx);
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
|
@ -95,7 +95,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let mut selection_starts: HashMap<_, _> = Default::default();
|
let mut selection_starts: HashMap<_, _> = Default::default();
|
||||||
|
@ -137,7 +137,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(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.transact(window, cx, |editor, window, cx| {
|
||||||
let mut original_positions: HashMap<_, _> = Default::default();
|
let mut original_positions: HashMap<_, _> = Default::default();
|
||||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
|
|
|
@ -38,7 +38,7 @@ impl Vim {
|
||||||
if count <= 1 || Vim::globals(cx).dot_replaying {
|
if count <= 1 || Vim::globals(cx).dot_replaying {
|
||||||
self.create_mark("^".into(), window, cx);
|
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);
|
editor.dismiss_menus_and_popups(false, window, cx);
|
||||||
|
|
||||||
if !HelixModeSetting::get_global(cx).0 {
|
if !HelixModeSetting::get_global(cx).0 {
|
||||||
|
|
|
@ -679,7 +679,7 @@ impl Vim {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||||
if !prior_selections.is_empty() {
|
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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.select_ranges(prior_selections.iter().cloned())
|
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::action(editor, cx, |vim, _: &HelixDelete, window, cx| {
|
||||||
vim.record_current_action(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| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if selection.is_empty() {
|
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::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| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let mut point = selection.head();
|
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| {
|
Vim::action(editor, cx, |vim, _: &Undo, window, cx| {
|
||||||
let times = Vim::take_count(cx);
|
let times = Vim::take_count(cx);
|
||||||
Vim::take_forced_motion(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) {
|
for _ in 0..times.unwrap_or(1) {
|
||||||
editor.undo(&editor::actions::Undo, window, cx);
|
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| {
|
Vim::action(editor, cx, |vim, _: &Redo, window, cx| {
|
||||||
let times = Vim::take_count(cx);
|
let times = Vim::take_count(cx);
|
||||||
Vim::take_forced_motion(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) {
|
for _ in 0..times.unwrap_or(1) {
|
||||||
editor.redo(&editor::actions::Redo, window, cx);
|
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::action(editor, cx, |vim, _: &UndoLastLine, window, cx| {
|
||||||
Vim::take_forced_motion(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 snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
let Some(last_change) = editor.change_list.last_before_grouping() else {
|
let Some(last_change) = editor.change_list.last_before_grouping() else {
|
||||||
return;
|
return;
|
||||||
|
@ -526,7 +526,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.change_selections(
|
editor.change_selections(
|
||||||
SelectionEffects::default().nav_history(motion.push_to_jump_list()),
|
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>) {
|
fn insert_after(&mut self, _: &InsertAfter, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.start_recording(cx);
|
self.start_recording(cx);
|
||||||
self.switch_mode(Mode::Insert, false, window, 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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None));
|
s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None));
|
||||||
});
|
});
|
||||||
|
@ -557,7 +557,7 @@ impl Vim {
|
||||||
self.start_recording(cx);
|
self.start_recording(cx);
|
||||||
if self.mode.is_visual() {
|
if self.mode.is_visual() {
|
||||||
let current_mode = self.mode;
|
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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if current_mode == Mode::VisualLine {
|
if current_mode == Mode::VisualLine {
|
||||||
|
@ -581,7 +581,7 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
self.start_recording(cx);
|
self.start_recording(cx);
|
||||||
self.switch_mode(Mode::Insert, false, window, 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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_cursors_with(|map, cursor, _| {
|
s.move_cursors_with(|map, cursor, _| {
|
||||||
(
|
(
|
||||||
|
@ -601,7 +601,7 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
self.start_recording(cx);
|
self.start_recording(cx);
|
||||||
self.switch_mode(Mode::Insert, false, window, 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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_cursors_with(|map, cursor, _| {
|
s.move_cursors_with(|map, cursor, _| {
|
||||||
(next_line_end(map, cursor, 1), SelectionGoal::None)
|
(next_line_end(map, cursor, 1), SelectionGoal::None)
|
||||||
|
@ -618,7 +618,7 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
self.start_recording(cx);
|
self.start_recording(cx);
|
||||||
self.switch_mode(Mode::Insert, false, window, 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 {
|
let Some(Mark::Local(marks)) = vim.get_mark("^", editor, window, cx) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -637,7 +637,7 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
self.start_recording(cx);
|
self.start_recording(cx);
|
||||||
self.switch_mode(Mode::Insert, false, window, 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| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let selections = editor.selections.all::<Point>(cx);
|
let selections = editor.selections.all::<Point>(cx);
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
@ -678,7 +678,7 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
self.start_recording(cx);
|
self.start_recording(cx);
|
||||||
self.switch_mode(Mode::Insert, false, window, 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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let selections = editor.selections.all::<Point>(cx);
|
let selections = editor.selections.all::<Point>(cx);
|
||||||
|
@ -725,7 +725,7 @@ impl Vim {
|
||||||
self.record_current_action(cx);
|
self.record_current_action(cx);
|
||||||
let count = Vim::take_count(cx).unwrap_or(1);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
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| {
|
editor.transact(window, cx, |editor, _, cx| {
|
||||||
let selections = editor.selections.all::<Point>(cx);
|
let selections = editor.selections.all::<Point>(cx);
|
||||||
|
|
||||||
|
@ -754,7 +754,7 @@ impl Vim {
|
||||||
self.record_current_action(cx);
|
self.record_current_action(cx);
|
||||||
let count = Vim::take_count(cx).unwrap_or(1);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
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| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let selections = editor.selections.all::<Point>(cx);
|
let selections = editor.selections.all::<Point>(cx);
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
@ -804,7 +804,7 @@ impl Vim {
|
||||||
times -= 1;
|
times -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
editor.join_lines_impl(insert_whitespace, window, cx)
|
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);
|
let count = Vim::take_count(cx);
|
||||||
Vim::take_forced_motion(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 selection = editor.selections.newest_anchor();
|
||||||
let Some((buffer, point, _)) = editor
|
let Some((buffer, point, _)) = editor
|
||||||
.buffer()
|
.buffer()
|
||||||
|
@ -875,7 +875,7 @@ impl Vim {
|
||||||
fn toggle_comments(&mut self, _: &ToggleComments, window: &mut Window, cx: &mut Context<Self>) {
|
fn toggle_comments(&mut self, _: &ToggleComments, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.record_current_action(cx);
|
self.record_current_action(cx);
|
||||||
self.store_visual_marks(window, 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| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let original_positions = vim.save_selection_starts(editor, cx);
|
let original_positions = vim.save_selection_starts(editor, cx);
|
||||||
editor.toggle_comments(&Default::default(), window, cx);
|
editor.toggle_comments(&Default::default(), window, cx);
|
||||||
|
@ -897,7 +897,7 @@ impl Vim {
|
||||||
let count = Vim::take_count(cx).unwrap_or(1);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
self.stop_recording(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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let (map, display_selections) = editor.selections.all_display(cx);
|
let (map, display_selections) = editor.selections.all_display(cx);
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl Vim {
|
||||||
} else {
|
} else {
|
||||||
None
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
// 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>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let mut objects_found = false;
|
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
|
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
|
|
|
@ -31,7 +31,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
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);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let text_layout_details = editor.text_layout_details(window);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
|
@ -87,7 +87,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let mut original_positions: HashMap<_, _> = Default::default();
|
let mut original_positions: HashMap<_, _> = Default::default();
|
||||||
|
@ -195,7 +195,7 @@ impl Vim {
|
||||||
let count = Vim::take_count(cx).unwrap_or(1) as u32;
|
let count = Vim::take_count(cx).unwrap_or(1) as u32;
|
||||||
Vim::take_forced_motion(cx);
|
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 ranges = Vec::new();
|
||||||
let mut cursor_positions = Vec::new();
|
let mut cursor_positions = Vec::new();
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
|
|
@ -22,7 +22,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
@ -96,7 +96,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
// Emulates behavior in vim where if we expanded backwards to include a newline
|
// Emulates behavior in vim where if we expanded backwards to include a newline
|
||||||
|
|
|
@ -53,7 +53,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.store_visual_marks(window, cx);
|
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 edits = Vec::new();
|
||||||
let mut new_anchors = Vec::new();
|
let mut new_anchors = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
|
|
||||||
impl Vim {
|
impl Vim {
|
||||||
pub fn create_mark(&mut self, text: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
|
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
|
let anchors = editor
|
||||||
.selections
|
.selections
|
||||||
.disjoint_anchors()
|
.disjoint_anchors()
|
||||||
|
@ -49,7 +49,7 @@ impl Vim {
|
||||||
let mut ends = vec![];
|
let mut ends = vec![];
|
||||||
let mut reversed = 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);
|
let (map, selections) = editor.selections.all_display(cx);
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
let end = movement::saturating_left(&map, selection.end);
|
let end = movement::saturating_left(&map, selection.end);
|
||||||
|
@ -190,7 +190,7 @@ impl Vim {
|
||||||
self.pop_operator(window, cx);
|
self.pop_operator(window, cx);
|
||||||
}
|
}
|
||||||
let mark = self
|
let mark = self
|
||||||
.update_editor(window, cx, |vim, editor, window, cx| {
|
.update_editor(cx, |vim, editor, cx| {
|
||||||
vim.get_mark(&text, editor, window, cx)
|
vim.get_mark(&text, editor, window, cx)
|
||||||
})
|
})
|
||||||
.flatten();
|
.flatten();
|
||||||
|
@ -209,7 +209,7 @@ impl Vim {
|
||||||
|
|
||||||
let Some(mut anchors) = anchors else { return };
|
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);
|
editor.create_nav_history_entry(cx);
|
||||||
});
|
});
|
||||||
let is_active_operator = self.active_operator().is_some();
|
let is_active_operator = self.active_operator().is_some();
|
||||||
|
@ -231,7 +231,7 @@ impl Vim {
|
||||||
|| self.mode == Mode::VisualLine
|
|| self.mode == Mode::VisualLine
|
||||||
|| self.mode == Mode::VisualBlock;
|
|| 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 map = editor.snapshot(window, cx);
|
||||||
let mut ranges: Vec<Range<Anchor>> = Vec::new();
|
let mut ranges: Vec<Range<Anchor>> = Vec::new();
|
||||||
for mut anchor in anchors {
|
for mut anchor in anchors {
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl Vim {
|
||||||
let count = Vim::take_count(cx).unwrap_or(1);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
@ -236,7 +236,7 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
self.stop_recording(cx);
|
||||||
let selected_register = self.selected_register.take();
|
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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
|
@ -273,7 +273,7 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
self.stop_recording(cx);
|
||||||
let selected_register = self.selected_register.take();
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, 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));
|
let amount = by(Vim::take_count(cx).map(|c| c as f32));
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
self.exit_temporary_normal(window, 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)
|
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 the active editor has changed during a search, don't panic.
|
||||||
if prior_selections.iter().any(|s| {
|
if prior_selections.iter().any(|s| {
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
!s.start
|
!s.start
|
||||||
.is_valid(&editor.snapshot(window, cx).buffer_snapshot)
|
.is_valid(&editor.snapshot(window, cx).buffer_snapshot)
|
||||||
})
|
})
|
||||||
|
@ -457,7 +457,7 @@ impl Vim {
|
||||||
else {
|
else {
|
||||||
return;
|
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 range = action.range.buffer_range(vim, editor, window, cx)?;
|
||||||
let snapshot = &editor.snapshot(window, cx).buffer_snapshot;
|
let snapshot = &editor.snapshot(window, cx).buffer_snapshot;
|
||||||
let end_point = Point::new(range.end.0, snapshot.line_len(range.end));
|
let end_point = Point::new(range.end.0, snapshot.line_len(range.end));
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.store_visual_marks(window, cx);
|
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.set_clip_at_line_ends(false, cx);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let text_layout_details = editor.text_layout_details(window);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
|
|
|
@ -14,7 +14,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let mut selection_starts: HashMap<_, _> = Default::default();
|
let mut selection_starts: HashMap<_, _> = Default::default();
|
||||||
|
@ -51,7 +51,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(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.transact(window, cx, |editor, window, cx| {
|
||||||
let mut original_positions: HashMap<_, _> = Default::default();
|
let mut original_positions: HashMap<_, _> = Default::default();
|
||||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
|
|
|
@ -25,7 +25,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
@ -70,7 +70,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let mut start_positions: HashMap<_, _> = Default::default();
|
let mut start_positions: HashMap<_, _> = Default::default();
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let map = editor.snapshot(window, cx);
|
let map = editor.snapshot(window, cx);
|
||||||
|
@ -94,7 +94,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let map = editor.snapshot(window, cx);
|
let map = editor.snapshot(window, cx);
|
||||||
|
@ -148,7 +148,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
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);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let mut selection = editor.selections.newest_display(cx);
|
let mut selection = editor.selections.newest_display(cx);
|
||||||
let snapshot = editor.snapshot(window, 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>) {
|
pub fn exchange_visual(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.stop_recording(cx);
|
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 selection = editor.selections.newest_anchor();
|
||||||
let new_range = selection.start..selection.end;
|
let new_range = selection.start..selection.end;
|
||||||
let snapshot = editor.snapshot(window, cx);
|
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>) {
|
pub fn clear_exchange(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.stop_recording(cx);
|
self.stop_recording(cx);
|
||||||
self.update_editor(window, cx, |_, editor, _, cx| {
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
editor.clear_background_highlights::<VimExchange>(cx);
|
editor.clear_background_highlights::<VimExchange>(cx);
|
||||||
});
|
});
|
||||||
self.clear_operator(window, cx);
|
self.clear_operator(window, cx);
|
||||||
|
@ -193,7 +193,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
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);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let text_layout_details = editor.text_layout_details(window);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
let mut selection = editor.selections.newest_display(cx);
|
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_count(cx);
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
vim.store_visual_marks(window, 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| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let mut positions = vim.save_selection_starts(editor, cx);
|
let mut positions = vim.save_selection_starts(editor, cx);
|
||||||
editor.rewrap_impl(
|
editor.rewrap_impl(
|
||||||
|
@ -55,7 +55,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(cx);
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
let mut selection_starts: HashMap<_, _> = Default::default();
|
let mut selection_starts: HashMap<_, _> = Default::default();
|
||||||
|
@ -100,7 +100,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(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.transact(window, cx, |editor, window, cx| {
|
||||||
let mut original_positions: HashMap<_, _> = Default::default();
|
let mut original_positions: HashMap<_, _> = Default::default();
|
||||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
|
|
|
@ -29,7 +29,7 @@ impl Vim {
|
||||||
let count = Vim::take_count(cx);
|
let count = Vim::take_count(cx);
|
||||||
let forced_motion = Vim::take_forced_motion(cx);
|
let forced_motion = Vim::take_forced_motion(cx);
|
||||||
let mode = self.mode;
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
@ -140,7 +140,7 @@ impl Vim {
|
||||||
};
|
};
|
||||||
let surround = pair.end != *text;
|
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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, 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) {
|
if let Some(will_replace_pair) = object_to_bracket_pair(target) {
|
||||||
self.stop_recording(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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
|
||||||
|
@ -344,7 +344,7 @@ impl Vim {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut valid = false;
|
let mut valid = false;
|
||||||
if let Some(pair) = object_to_bracket_pair(object) {
|
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.transact(window, cx, |editor, window, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
let (display_map, selections) = editor.selections.all_adjusted_display(cx);
|
let (display_map, selections) = editor.selections.all_adjusted_display(cx);
|
||||||
|
|
|
@ -748,7 +748,7 @@ impl Vim {
|
||||||
editor,
|
editor,
|
||||||
cx,
|
cx,
|
||||||
|vim, action: &editor::actions::AcceptEditPrediction, window, 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);
|
editor.accept_edit_prediction(action, window, cx);
|
||||||
});
|
});
|
||||||
// In non-insertion modes, predictions will be hidden and instead a jump will be
|
// 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() {
|
if let Some(action) = keystroke_event.action.as_ref() {
|
||||||
// Keystroke is handled by the vim system, so continue forward
|
// Keystroke is handled by the vim system, so continue forward
|
||||||
if action.name().starts_with("vim::") {
|
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)
|
editor.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx)
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -909,7 +909,7 @@ impl Vim {
|
||||||
anchor,
|
anchor,
|
||||||
is_deactivate,
|
is_deactivate,
|
||||||
} => {
|
} => {
|
||||||
self.update_editor(window, cx, |vim, editor, window, cx| {
|
self.update_editor(cx, |vim, editor, cx| {
|
||||||
let mark = if *is_deactivate {
|
let mark = if *is_deactivate {
|
||||||
"\"".to_string()
|
"\"".to_string()
|
||||||
} else {
|
} else {
|
||||||
|
@ -972,7 +972,7 @@ impl Vim {
|
||||||
if mode == Mode::Normal || mode != last_mode {
|
if mode == Mode::Normal || mode != last_mode {
|
||||||
self.current_tx.take();
|
self.current_tx.take();
|
||||||
self.current_anchor.take();
|
self.current_anchor.take();
|
||||||
self.update_editor(window, cx, |_, editor, _, _| {
|
self.update_editor(cx, |_, editor, _| {
|
||||||
editor.clear_selection_drag_state();
|
editor.clear_selection_drag_state();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -988,7 +988,7 @@ impl Vim {
|
||||||
&& self.mode != self.last_mode
|
&& self.mode != self.last_mode
|
||||||
&& (self.mode == Mode::Insert || self.last_mode == Mode::Insert)
|
&& (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;
|
let is_relative = vim.mode != Mode::Insert;
|
||||||
editor.set_relative_line_number(Some(is_relative), cx)
|
editor.set_relative_line_number(Some(is_relative), cx)
|
||||||
});
|
});
|
||||||
|
@ -1003,7 +1003,7 @@ impl Vim {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust selections
|
// 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
|
if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
|
||||||
{
|
{
|
||||||
vim.visual_block_motion(true, editor, window, cx, |_, point, goal| {
|
vim.visual_block_motion(true, editor, window, cx, |_, point, goal| {
|
||||||
|
@ -1214,7 +1214,7 @@ impl Vim {
|
||||||
if preserve_selection {
|
if preserve_selection {
|
||||||
self.switch_mode(Mode::Visual, true, window, cx);
|
self.switch_mode(Mode::Visual, true, window, cx);
|
||||||
} else {
|
} else {
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
s.move_with(|_, selection| {
|
s.move_with(|_, selection| {
|
||||||
|
@ -1232,18 +1232,18 @@ impl Vim {
|
||||||
if let Some(old_vim) = Vim::globals(cx).focused_vim() {
|
if let Some(old_vim) = Vim::globals(cx).focused_vim() {
|
||||||
if old_vim.entity_id() != cx.entity().entity_id() {
|
if old_vim.entity_id() != cx.entity().entity_id() {
|
||||||
old_vim.update(cx, |vim, cx| {
|
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)
|
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;
|
let is_relative = vim.mode != Mode::Insert;
|
||||||
editor.set_relative_line_number(Some(is_relative), cx)
|
editor.set_relative_line_number(Some(is_relative), cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.update_editor(window, cx, |vim, editor, _, cx| {
|
self.update_editor(cx, |vim, editor, cx| {
|
||||||
let is_relative = vim.mode != Mode::Insert;
|
let is_relative = vim.mode != Mode::Insert;
|
||||||
editor.set_relative_line_number(Some(is_relative), cx)
|
editor.set_relative_line_number(Some(is_relative), cx)
|
||||||
});
|
});
|
||||||
|
@ -1256,35 +1256,30 @@ impl Vim {
|
||||||
self.stop_recording_immediately(NormalBefore.boxed_clone(), cx);
|
self.stop_recording_immediately(NormalBefore.boxed_clone(), cx);
|
||||||
self.store_visual_marks(window, cx);
|
self.store_visual_marks(window, cx);
|
||||||
self.clear_operator(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 {
|
if vim.cursor_shape(cx) == CursorShape::Block {
|
||||||
editor.set_cursor_shape(CursorShape::Hollow, cx);
|
editor.set_cursor_shape(CursorShape::Hollow, cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_shape_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn cursor_shape_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.update_editor(window, cx, |vim, editor, _, cx| {
|
self.update_editor(cx, |vim, editor, cx| {
|
||||||
editor.set_cursor_shape(vim.cursor_shape(cx), cx);
|
editor.set_cursor_shape(vim.cursor_shape(cx), cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_editor<S>(
|
fn update_editor<S>(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
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> {
|
) -> Option<S> {
|
||||||
let editor = self.editor.upgrade()?;
|
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(
|
fn editor_selections(&mut self, _: &mut Window, cx: &mut Context<Self>) -> Vec<Range<Anchor>> {
|
||||||
&mut self,
|
self.update_editor(cx, |_, editor, _| {
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Vec<Range<Anchor>> {
|
|
||||||
self.update_editor(window, cx, |_, editor, _, _| {
|
|
||||||
editor
|
editor
|
||||||
.selections
|
.selections
|
||||||
.disjoint_anchors()
|
.disjoint_anchors()
|
||||||
|
@ -1300,7 +1295,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
let selection = editor.selections.newest::<usize>(cx);
|
let selection = editor.selections.newest::<usize>(cx);
|
||||||
|
|
||||||
let snapshot = &editor.snapshot(window, cx).buffer_snapshot;
|
let snapshot = &editor.snapshot(window, cx).buffer_snapshot;
|
||||||
|
@ -1489,7 +1484,7 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
|
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);
|
let original_mode = vim.undo_modes.get(transaction_id);
|
||||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
match original_mode {
|
match original_mode {
|
||||||
|
@ -1520,7 +1515,7 @@ impl Vim {
|
||||||
self.switch_mode(Mode::Normal, true, window, cx)
|
self.switch_mode(Mode::Normal, true, window, cx)
|
||||||
}
|
}
|
||||||
Mode::Normal => {
|
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| {
|
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
selection
|
selection
|
||||||
|
@ -1547,7 +1542,7 @@ impl Vim {
|
||||||
self.current_anchor = Some(newest);
|
self.current_anchor = Some(newest);
|
||||||
} else if self.current_anchor.as_ref().unwrap() != &newest {
|
} else if self.current_anchor.as_ref().unwrap() != &newest {
|
||||||
if let Some(tx_id) = self.current_tx.take() {
|
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)
|
editor.group_until_transaction(tx_id, cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1694,7 +1689,7 @@ impl Vim {
|
||||||
}
|
}
|
||||||
Some(Operator::Register) => match self.mode {
|
Some(Operator::Register) => match self.mode {
|
||||||
Mode::Insert => {
|
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| {
|
if let Some(register) = Vim::update_globals(cx, |globals, cx| {
|
||||||
globals.read_register(text.chars().next(), Some(editor), cx)
|
globals.read_register(text.chars().next(), Some(editor), cx)
|
||||||
}) {
|
}) {
|
||||||
|
@ -1720,7 +1715,7 @@ impl Vim {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mode == Mode::Normal {
|
if self.mode == Mode::Normal {
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
editor.accept_edit_prediction(
|
editor.accept_edit_prediction(
|
||||||
&editor::actions::AcceptEditPrediction {},
|
&editor::actions::AcceptEditPrediction {},
|
||||||
window,
|
window,
|
||||||
|
@ -1733,7 +1728,7 @@ impl Vim {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_vim_settings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
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_cursor_shape(vim.cursor_shape(cx), cx);
|
||||||
editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
|
editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
|
||||||
editor.set_collapse_matches(true);
|
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);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
for _ in 0..count {
|
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);
|
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);
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
for _ in 0..count {
|
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);
|
editor.select_smaller_syntax_node(&Default::default(), window, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let marks = vim
|
let marks = vim
|
||||||
.update_editor(window, cx, |vim, editor, window, cx| {
|
.update_editor(cx, |vim, editor, cx| {
|
||||||
vim.get_mark("<", editor, window, cx)
|
vim.get_mark("<", editor, window, cx)
|
||||||
.zip(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.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.set_clip_at_line_ends(false, cx);
|
||||||
editor.change_selections(Default::default(), window, cx, |s| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
let map = s.display_map();
|
let map = s.display_map();
|
||||||
|
@ -189,7 +189,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
if vim.mode == Mode::VisualBlock
|
if vim.mode == Mode::VisualBlock
|
||||||
&& !matches!(
|
&& !matches!(
|
||||||
|
@ -397,7 +397,7 @@ impl Vim {
|
||||||
self.switch_mode(target_mode, true, window, cx);
|
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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let mut mut_selection = selection.clone();
|
let mut mut_selection = selection.clone();
|
||||||
|
@ -475,7 +475,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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.split_selection_into_lines(&Default::default(), window, cx);
|
||||||
editor.change_selections(Default::default(), window, cx, |s| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_cursors_with(|map, cursor, _| {
|
s.move_cursors_with(|map, cursor, _| {
|
||||||
|
@ -493,7 +493,7 @@ impl Vim {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
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.split_selection_into_lines(&Default::default(), window, cx);
|
||||||
editor.change_selections(Default::default(), window, cx, |s| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_cursors_with(|map, cursor, _| {
|
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>) {
|
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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|_, selection| {
|
s.move_with(|_, selection| {
|
||||||
selection.reversed = !selection.reversed;
|
selection.reversed = !selection.reversed;
|
||||||
|
@ -533,7 +533,7 @@ impl Vim {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let mode = self.mode;
|
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| {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
s.move_with(|_, selection| {
|
s.move_with(|_, selection| {
|
||||||
selection.reversed = !selection.reversed;
|
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>) {
|
pub fn visual_delete(&mut self, line_mode: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.store_visual_marks(window, cx);
|
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 mut original_columns: HashMap<_, _> = Default::default();
|
||||||
let line_mode = line_mode || editor.selections.line_mode;
|
let line_mode = line_mode || editor.selections.line_mode;
|
||||||
editor.selections.line_mode = false;
|
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>) {
|
pub fn visual_yank(&mut self, line_mode: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.store_visual_marks(window, cx);
|
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;
|
let line_mode = line_mode || editor.selections.line_mode;
|
||||||
|
|
||||||
// For visual line mode, adjust selections to avoid yanking the next line when on \n
|
// 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>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.stop_recording(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.transact(window, cx, |editor, window, cx| {
|
||||||
let (display_map, selections) = editor.selections.all_adjusted_display(cx);
|
let (display_map, selections) = editor.selections.all_adjusted_display(cx);
|
||||||
|
|
||||||
|
@ -722,7 +722,7 @@ impl Vim {
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
let count =
|
let count =
|
||||||
Vim::take_count(cx).unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
|
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);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
if editor
|
if editor
|
||||||
|
@ -745,7 +745,7 @@ impl Vim {
|
||||||
Vim::take_forced_motion(cx);
|
Vim::take_forced_motion(cx);
|
||||||
let count =
|
let count =
|
||||||
Vim::take_count(cx).unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
|
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 {
|
for _ in 0..count {
|
||||||
if editor
|
if editor
|
||||||
.select_previous(&Default::default(), window, cx)
|
.select_previous(&Default::default(), window, cx)
|
||||||
|
@ -773,7 +773,7 @@ impl Vim {
|
||||||
let mut start_selection = 0usize;
|
let mut start_selection = 0usize;
|
||||||
let mut end_selection = 0usize;
|
let mut end_selection = 0usize;
|
||||||
|
|
||||||
self.update_editor(window, cx, |_, editor, _, _| {
|
self.update_editor(cx, |_, editor, _| {
|
||||||
editor.set_collapse_matches(false);
|
editor.set_collapse_matches(false);
|
||||||
});
|
});
|
||||||
if vim_is_normal {
|
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);
|
let latest = editor.selections.newest::<usize>(cx);
|
||||||
start_selection = latest.start;
|
start_selection = latest.start;
|
||||||
end_selection = latest.end;
|
end_selection = latest.end;
|
||||||
|
@ -812,7 +812,7 @@ impl Vim {
|
||||||
self.stop_replaying(cx);
|
self.stop_replaying(cx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
let latest = editor.selections.newest::<usize>(cx);
|
let latest = editor.selections.newest::<usize>(cx);
|
||||||
if vim_is_normal {
|
if vim_is_normal {
|
||||||
start_selection = latest.start;
|
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::separator(),
|
||||||
MenuItem::submenu(Menu {
|
MenuItem::os_submenu("Services", gpui::SystemMenuType::Services),
|
||||||
name: "Services".into(),
|
|
||||||
items: vec![],
|
|
||||||
}),
|
|
||||||
MenuItem::separator(),
|
MenuItem::separator(),
|
||||||
MenuItem::action("Extensions", zed_actions::Extensions::default()),
|
MenuItem::action("Extensions", zed_actions::Extensions::default()),
|
||||||
MenuItem::action("Install CLI", install_cli::Install),
|
MenuItem::action("Install CLI", install_cli::Install),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue