Compare commits

..

16 Commits

Author SHA1 Message Date
Richard Feldman
3a9e53d561 Add infer_context to settings 2024-07-15 15:04:54 -04:00
Richard Feldman
7ee2511b89 Revert "Add local_model"
This reverts commit 1ceb6038033336207aff8df7e7bed205d2f83baa.
2024-07-15 15:04:54 -04:00
Richard Feldman
1eb90e0400 Add local_model 2024-07-15 15:04:54 -04:00
Richard Feldman
72e73544bb Try some clarifying prompt text 2024-07-11 13:00:47 -04:00
Richard Feldman
586cf40d63 Sort inferred slash-commands 2024-07-11 12:55:00 -04:00
Richard Feldman
18f2fc9aa3 Cleanup 2024-07-11 11:10:18 -04:00
Richard Feldman
b1beed4ca9 Structural summaries 2024-07-11 10:36:18 -04:00
Richard Feldman
8d37c1c6ea Revert "wip 2"
This reverts commit 0680f6469b.
2024-07-09 22:09:36 -04:00
Richard Feldman
0680f6469b wip 2 2024-07-09 22:09:35 -04:00
Richard Feldman
57a543ebe8 wip 2024-07-09 21:47:02 -04:00
Richard Feldman
d69fb469bd Try a split-based approach 2024-07-09 20:52:14 -04:00
Richard Feldman
eb413ba404 First pass at splitting summary context 2024-07-09 20:52:11 -04:00
Richard Feldman
9182bd51f2 Add output of /auto to assistant panel 2024-07-05 14:47:13 -04:00
Richard Feldman
481a554923 Make /auto print out the actions it wants to run 2024-07-05 13:23:55 -04:00
Richard Feldman
c129e3b668 Basic version of /auto 2024-07-03 13:12:02 -04:00
Richard Feldman
12e3c3b502 Rename ActionCommand to AutoCommand 2024-07-03 12:52:48 -04:00
71 changed files with 828 additions and 1526 deletions

View File

@@ -254,7 +254,7 @@ jobs:
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
body_path: target/release-notes.md
body_file: target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -41,7 +41,7 @@ We plan to set aside time each week to pair program with contributors on promisi
Zed is made up of several smaller crates - let's go over those you're most likely to interact with:
- [`gpui`](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation.**
- [`gpui`](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation**
- [`editor`](/crates/editor) contains the core `Editor` type that drives both the code editor and all various input fields within Zed. It also handles a display layer for LSP features such as Inlay Hints or code completions.
- [`project`](/crates/project) manages files and navigation within the filetree. It is also Zed's side of communication with LSP.
- [`workspace`](/crates/workspace) handles local state serialization and groups projects together.

3
Cargo.lock generated
View File

@@ -4919,7 +4919,6 @@ dependencies = [
"taffy",
"thiserror",
"time",
"unicode-segmentation",
"usvg",
"util",
"uuid",
@@ -13550,7 +13549,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.142.4"
version = "0.142.0"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>

Before

Width:  |  Height:  |  Size: 289 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-copy"><path d="M2 16V4a2 2 0 0 1 2-2h11"/><path d="M5 14H4a2 2 0 1 0 0 4h1"/><path d="M22 18H11a2 2 0 1 0 0 4h11V6H11a2 2 0 0 0-2 2v12"/></svg>

Before

Width:  |  Height:  |  Size: 351 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-plus"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/><path d="M9 10h6"/><path d="M12 7v6"/></svg>

Before

Width:  |  Height:  |  Size: 332 B

View File

@@ -70,14 +70,6 @@
{
"context": "ProjectPanel",
"bindings": {
"a": "project_panel::NewFile",
"shift-a": "project_panel::NewDirectory",
"f2": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"shift-d": "project_panel::Duplicate",
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
"ctrl-[": "project_panel::CollapseSelectedEntry",
"ctrl-b": "project_panel::CollapseSelectedEntry",
"alt-b": "project_panel::CollapseSelectedEntry",

View File

@@ -587,9 +587,8 @@
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"backspace": "project_panel::Trash",
"delete": "project_panel::Trash",
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFinder",

View File

@@ -605,7 +605,6 @@
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry",
"cmd-n": "project_panel::NewFile",
"cmd-d": "project_panel::Duplicate",
"alt-cmd-n": "project_panel::NewDirectory",
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
@@ -615,9 +614,8 @@
"enter": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",

View File

@@ -94,10 +94,6 @@
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": false }],
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"shift-f6": "project_panel::Rename"
}
}

View File

@@ -87,15 +87,7 @@
},
{
"context": "ProjectPanel",
"bindings": {
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-d": "project_panel::Duplicate",
"cmd-n": "project_panel::NewFolder",
"return": "project_panel::Rename",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
"cmd-alt-c": "project_panel::CopyPath"
}
"bindings": {}
},
{
"context": "Dock",

View File

@@ -131,7 +131,14 @@
// The default number of lines to expand excerpts in the multibuffer by.
"expand_excerpt_lines": 3,
// Globs to match against file paths to determine if a file is private.
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
"private_files": [
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/secrets.yml"
],
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,
@@ -390,7 +397,13 @@
// 3. "gpt-4-turbo-preview"
// 4. "gpt-4o"
"default_model": "gpt-4o"
}
},
// Whether to enable the /auto command in the assistant panel, which infers context.
// Enabling this also enables indexing all the files in the project to generate
// metadata used in context inference. The first time a project is indexed, indexing
// can take a long time and use a lot of system resources. Later indexing is incremental
// and much faster.
"infer_context": false
},
// Whether the screen sharing icon is shown in the os status bar.
"show_call_status_icon": true,
@@ -751,11 +764,6 @@
"allowed": true
}
},
"JSONC": {
"prettier": {
"allowed": true
}
},
"Markdown": {
"format_on_save": "off",
"prettier": {

View File

@@ -26,8 +26,9 @@ use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use slash_command::{
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
project_command, prompt_command, rustdoc_command, search_command, tabs_command, term_command,
active_command, auto_command, default_command, diagnostics_command, fetch_command,
file_command, now_command, project_command, prompt_command, rustdoc_command, search_command,
tabs_command, term_command,
};
use std::{
fmt::{self, Display},
@@ -138,7 +139,7 @@ impl LanguageModel {
}
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct LanguageModelRequestMessage {
pub role: Role,
pub content: String,
@@ -159,7 +160,7 @@ impl LanguageModelRequestMessage {
}
}
#[derive(Debug, Default, Serialize)]
#[derive(Clone, Debug, Default, Serialize)]
pub struct LanguageModelRequest {
pub model: LanguageModel,
pub messages: Vec<LanguageModelRequestMessage>,
@@ -310,6 +311,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
fn register_slash_commands(cx: &mut AppContext) {
let slash_command_registry = SlashCommandRegistry::global(cx);
slash_command_registry.register_command(auto_command::AutoCommand, true);
slash_command_registry.register_command(file_command::FileSlashCommand, true);
slash_command_registry.register_command(active_command::ActiveSlashCommand, true);
slash_command_registry.register_command(tabs_command::TabsSlashCommand, true);

View File

@@ -169,7 +169,6 @@ pub enum AssistantProvider {
model: OpenAiModel,
api_url: String,
low_speed_timeout_in_seconds: Option<u64>,
available_models: Vec<OpenAiModel>,
},
Anthropic {
model: AnthropicModel,
@@ -189,7 +188,6 @@ impl Default for AssistantProvider {
model: OpenAiModel::default(),
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
}
}
@@ -204,7 +202,6 @@ pub enum AssistantProviderContent {
default_model: Option<OpenAiModel>,
api_url: Option<String>,
low_speed_timeout_in_seconds: Option<u64>,
available_models: Option<Vec<OpenAiModel>>,
},
#[serde(rename = "anthropic")]
Anthropic {
@@ -228,6 +225,7 @@ pub struct AssistantSettings {
pub default_width: Pixels,
pub default_height: Pixels,
pub provider: AssistantProvider,
pub infer_context: bool,
}
/// Assistant panel settings
@@ -275,7 +273,6 @@ impl AssistantSettingsContent {
default_model: settings.default_open_ai_model.clone(),
api_url: Some(open_ai_api_url.clone()),
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
})
} else {
settings.default_open_ai_model.clone().map(|open_ai_model| {
@@ -283,10 +280,10 @@ impl AssistantSettingsContent {
default_model: Some(open_ai_model),
api_url: None,
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
}
})
},
infer_context: None,
},
}
}
@@ -331,14 +328,6 @@ impl AssistantSettingsContent {
*model = Some(new_model);
}
}
Some(AssistantProviderContent::Ollama {
default_model: model,
..
}) => {
if let LanguageModel::Ollama(new_model) = new_model {
*model = Some(new_model);
}
}
provider => match new_model {
LanguageModel::Cloud(model) => {
*provider = Some(AssistantProviderContent::ZedDotDev {
@@ -350,7 +339,6 @@ impl AssistantSettingsContent {
default_model: Some(model),
api_url: None,
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
})
}
LanguageModel::Anthropic(model) => {
@@ -395,6 +383,7 @@ impl Default for VersionedAssistantSettingsContent {
default_width: None,
default_height: None,
provider: None,
infer_context: None,
})
}
}
@@ -426,6 +415,14 @@ pub struct AssistantSettingsContentV1 {
/// This can either be the internal `zed.dev` service or an external `openai` service,
/// each with their respective default models and configurations.
provider: Option<AssistantProviderContent>,
/// When using the assistant panel, enable the /auto command to automatically
/// infer context. Enabling this will enable background indexing of the project,
/// to generate the metadata /auto needs to infer context. The first time a project
/// is indexed, the indexing process can take a long time and use a lot of system
/// resources. After the first time, later indexing is incremental and much faster.
///
/// Default: false
infer_context: Option<bool>,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
@@ -480,6 +477,7 @@ impl Settings for AssistantSettings {
&mut settings.default_height,
value.default_height.map(Into::into),
);
merge(&mut settings.infer_context, value.infer_context);
if let Some(provider) = value.provider.clone() {
match (&mut settings.provider, provider) {
(
@@ -495,18 +493,15 @@ impl Settings for AssistantSettings {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
AssistantProviderContent::OpenAi {
default_model: model_override,
api_url: api_url_override,
low_speed_timeout_in_seconds: low_speed_timeout_in_seconds_override,
available_models: available_models_override,
},
) => {
merge(model, model_override);
merge(api_url, api_url_override);
merge(available_models, available_models_override);
if let Some(low_speed_timeout_in_seconds_override) =
low_speed_timeout_in_seconds_override
{
@@ -567,12 +562,10 @@ impl Settings for AssistantSettings {
default_model: model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => AssistantProvider::OpenAi {
model: model.unwrap_or_default(),
api_url: api_url.unwrap_or_else(|| open_ai::OPEN_AI_API_URL.into()),
low_speed_timeout_in_seconds,
available_models: available_models.unwrap_or_default(),
},
AssistantProviderContent::Anthropic {
default_model: model,
@@ -629,7 +622,6 @@ mod tests {
model: OpenAiModel::FourOmni,
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);
@@ -652,7 +644,6 @@ mod tests {
model: OpenAiModel::FourOmni,
api_url: "test-url".into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);
SettingsStore::update_global(cx, |store, cx| {
@@ -673,7 +664,6 @@ mod tests {
model: OpenAiModel::Four,
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);

View File

@@ -24,20 +24,6 @@ use settings::{Settings, SettingsStore};
use std::sync::Arc;
use std::time::Duration;
/// Choose which model to use for openai provider.
/// If the model is not available, try to use the first available model, or fallback to the original model.
fn choose_openai_model(
model: &::open_ai::Model,
available_models: &[::open_ai::Model],
) -> ::open_ai::Model {
available_models
.iter()
.find(|&m| m == model)
.or_else(|| available_models.first())
.unwrap_or_else(|| model)
.clone()
}
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
let mut settings_version = 0;
let provider = match &AssistantSettings::get_global(cx).provider {
@@ -48,9 +34,8 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
choose_openai_model(model, available_models),
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
@@ -92,11 +77,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
provider.update(
choose_openai_model(model, available_models),
model.clone(),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
@@ -152,11 +136,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
*provider = CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
choose_openai_model(model, available_models),
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
@@ -218,10 +201,10 @@ impl CompletionProvider {
cx.global::<Self>()
}
pub fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel> {
pub fn available_models(&self) -> Vec<LanguageModel> {
match self {
CompletionProvider::OpenAi(provider) => provider
.available_models(cx)
.available_models()
.map(LanguageModel::OpenAi)
.collect(),
CompletionProvider::Anthropic(provider) => provider

View File

@@ -236,7 +236,7 @@ pub fn preprocess_anthropic_request(request: &mut LanguageModelRequest) {
}
if !system_message.is_empty() {
new_messages.insert(
request.messages.insert(
0,
LanguageModelRequestMessage {
role: Role::System,

View File

@@ -1,5 +1,4 @@
use crate::assistant_settings::CloudModel;
use crate::assistant_settings::{AssistantProvider, AssistantSettings};
use crate::{
assistant_settings::OpenAiModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
};
@@ -57,26 +56,8 @@ impl OpenAiCompletionProvider {
self.settings_version = settings_version;
}
pub fn available_models(&self, cx: &AppContext) -> impl Iterator<Item = OpenAiModel> {
if let AssistantProvider::OpenAi {
available_models, ..
} = &AssistantSettings::get_global(cx).provider
{
if !available_models.is_empty() {
// available_models is set, just return it
return available_models.clone().into_iter();
}
}
let available_models = if matches!(self.model, OpenAiModel::Custom { .. }) {
// available_models is not set but the default model is set to custom, only show custom
vec![self.model.clone()]
} else {
// default case, use all models except custom
OpenAiModel::iter()
.filter(|model| !matches!(model, OpenAiModel::Custom { .. }))
.collect()
};
available_models.into_iter()
pub fn available_models(&self) -> impl Iterator<Item = OpenAiModel> {
OpenAiModel::iter()
}
pub fn settings_version(&self) -> usize {
@@ -232,8 +213,7 @@ pub fn count_open_ai_tokens(
| LanguageModel::Cloud(CloudModel::Claude3_5Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Opus)
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Haiku)
| LanguageModel::OpenAi(OpenAiModel::Custom { .. }) => {
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
// Tiktoken doesn't yet support these models, so we manually use the
// same tokenizer as GPT-4.
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)

View File

@@ -1298,8 +1298,7 @@ impl Render for PromptEditor {
PopoverMenu::new("model-switcher")
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::global(cx).available_models(cx)
{
for model in CompletionProvider::global(cx).available_models() {
menu = menu.custom_entry(
{
let model = model.clone();

View File

@@ -23,7 +23,7 @@ impl RenderOnce for ModelSelector {
.with_handle(self.handle)
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::global(cx).available_models(cx) {
for model in CompletionProvider::global(cx).available_models() {
menu = menu.custom_entry(
{
let model = model.clone();

View File

@@ -6,16 +6,16 @@ use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandRegistry;
use chrono::{DateTime, Utc};
use collections::HashMap;
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorEvent};
use futures::{
future::{self, BoxFuture, Shared},
FutureExt,
};
use fuzzy::StringMatchCandidate;
use gpui::{
actions, point, size, transparent_black, AppContext, BackgroundExecutor, Bounds, EventEmitter,
Global, HighlightStyle, PromptLevel, ReadGlobal, Subscription, Task, TextStyle,
TitlebarOptions, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
actions, percentage, point, size, Animation, AnimationExt, AppContext, BackgroundExecutor,
Bounds, EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions,
Transformation, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
};
use heed::{types::SerdeBincode, Database, RoTxn};
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
@@ -109,13 +109,12 @@ pub struct PromptLibrary {
}
struct PromptEditor {
title_editor: View<Editor>,
body_editor: View<Editor>,
editor: View<Editor>,
token_count: Option<usize>,
pending_token_count: Task<Option<()>>,
next_title_and_body_to_save: Option<(String, Rope)>,
next_body_to_save: Option<Rope>,
pending_save: Option<Task<Option<()>>>,
_subscriptions: Vec<Subscription>,
_subscription: Subscription,
}
struct PromptPickerDelegate {
@@ -346,8 +345,7 @@ impl PromptLibrary {
let prompt_metadata = self.store.metadata(prompt_id).unwrap();
let prompt_editor = self.prompt_editors.get_mut(&prompt_id).unwrap();
let title = prompt_editor.title_editor.read(cx).text(cx);
let body = prompt_editor.body_editor.update(cx, |editor, cx| {
let body = prompt_editor.editor.update(cx, |editor, cx| {
editor
.buffer()
.read(cx)
@@ -361,24 +359,20 @@ impl PromptLibrary {
let store = self.store.clone();
let executor = cx.background_executor().clone();
prompt_editor.next_title_and_body_to_save = Some((title, body));
prompt_editor.next_body_to_save = Some(body);
if prompt_editor.pending_save.is_none() {
prompt_editor.pending_save = Some(cx.spawn(|this, mut cx| {
async move {
loop {
let title_and_body = this.update(&mut cx, |this, _| {
let next_body_to_save = this.update(&mut cx, |this, _| {
this.prompt_editors
.get_mut(&prompt_id)?
.next_title_and_body_to_save
.next_body_to_save
.take()
})?;
if let Some((title, body)) = title_and_body {
let title = if title.trim().is_empty() {
None
} else {
Some(SharedString::from(title))
};
if let Some(body) = next_body_to_save {
let title = title_from_body(body.chars_at(0));
store
.save(prompt_id, title, prompt_metadata.default, body)
.await
@@ -431,11 +425,11 @@ impl PromptLibrary {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
if focus {
prompt_editor
.body_editor
.editor
.update(cx, |editor, cx| editor.focus(cx));
}
self.set_active_prompt(Some(prompt_id), cx);
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
} else {
let language_registry = self.language_registry.clone();
let commands = SlashCommandRegistry::global(cx);
let prompt = self.store.load(prompt_id);
@@ -444,20 +438,13 @@ impl PromptLibrary {
let markdown = language_registry.language_for_name("Markdown").await;
this.update(&mut cx, |this, cx| match prompt {
Ok(prompt) => {
let title_editor = cx.new_view(|cx| {
let mut editor = Editor::auto_width(cx);
editor.set_placeholder_text("Untitled", cx);
editor.set_text(prompt_metadata.title.unwrap_or_default(), cx);
editor
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::local(prompt, cx);
buffer.set_language(markdown.log_err(), cx);
buffer.set_language_registry(language_registry);
buffer
});
let body_editor = cx.new_view(|cx| {
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::local(prompt, cx);
buffer.set_language(markdown.log_err(), cx);
buffer.set_language_registry(language_registry);
buffer
});
let editor = cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, None, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_gutter(false, cx);
@@ -473,24 +460,19 @@ impl PromptLibrary {
}
editor
});
let _subscriptions = vec![
cx.subscribe(&title_editor, move |this, editor, event, cx| {
this.handle_prompt_title_editor_event(prompt_id, editor, event, cx)
}),
cx.subscribe(&body_editor, move |this, editor, event, cx| {
this.handle_prompt_body_editor_event(prompt_id, editor, event, cx)
}),
];
let _subscription =
cx.subscribe(&editor, move |this, _editor, event, cx| {
this.handle_prompt_editor_event(prompt_id, event, cx)
});
this.prompt_editors.insert(
prompt_id,
PromptEditor {
title_editor,
body_editor,
next_title_and_body_to_save: None,
editor,
next_body_to_save: None,
pending_save: None,
token_count: None,
pending_token_count: Task::ready(None),
_subscriptions,
_subscription,
},
);
this.set_active_prompt(Some(prompt_id), cx);
@@ -567,7 +549,7 @@ impl PromptLibrary {
fn focus_active_prompt(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
if let Some(active_prompt) = self.active_prompt_id {
self.prompt_editors[&active_prompt]
.body_editor
.editor
.update(cx, |editor, cx| editor.focus(cx));
cx.stop_propagation();
}
@@ -583,7 +565,7 @@ impl PromptLibrary {
return;
};
let prompt_editor = &self.prompt_editors[&active_prompt_id].body_editor;
let prompt_editor = &self.prompt_editors[&active_prompt_id].editor;
let provider = CompletionProvider::global(cx);
if provider.is_authenticated() {
InlineAssistant::update_global(cx, |assistant, cx| {
@@ -607,73 +589,50 @@ impl PromptLibrary {
}
}
fn move_down_from_title(&mut self, _: &editor::actions::MoveDown, cx: &mut ViewContext<Self>) {
if let Some(prompt_id) = self.active_prompt_id {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
cx.focus_view(&prompt_editor.body_editor);
}
}
}
fn move_up_from_body(&mut self, _: &editor::actions::MoveUp, cx: &mut ViewContext<Self>) {
if let Some(prompt_id) = self.active_prompt_id {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
cx.focus_view(&prompt_editor.title_editor);
}
}
}
fn handle_prompt_title_editor_event(
fn handle_prompt_editor_event(
&mut self,
prompt_id: PromptId,
title_editor: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::BufferEdited => {
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
}
EditorEvent::Blurred => {
title_editor.update(cx, |title_editor, cx| {
title_editor.change_selections(None, cx, |selections| {
let cursor = selections.oldest_anchor().head();
selections.select_anchor_ranges([cursor..cursor]);
});
});
}
_ => {}
}
}
if let EditorEvent::BufferEdited = event {
let prompt_editor = self.prompt_editors.get(&prompt_id).unwrap();
let buffer = prompt_editor
.editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.unwrap();
fn handle_prompt_body_editor_event(
&mut self,
prompt_id: PromptId,
body_editor: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::BufferEdited => {
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
}
EditorEvent::Blurred => {
body_editor.update(cx, |body_editor, cx| {
body_editor.change_selections(None, cx, |selections| {
let cursor = selections.oldest_anchor().head();
selections.select_anchor_ranges([cursor..cursor]);
});
});
}
_ => {}
buffer.update(cx, |buffer, cx| {
let mut chars = buffer.chars_at(0);
match chars.next() {
Some('#') => {
if chars.next() != Some(' ') {
drop(chars);
buffer.edit([(1..1, " ")], None, cx);
}
}
Some(' ') => {
drop(chars);
buffer.edit([(0..0, "#")], None, cx);
}
_ => {
drop(chars);
buffer.edit([(0..0, "# ")], None, cx);
}
}
});
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
}
}
fn count_tokens(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
if let Some(prompt) = self.prompt_editors.get_mut(&prompt_id) {
let editor = &prompt.body_editor.read(cx);
let editor = &prompt.editor.read(cx);
let buffer = &editor.buffer().read(cx).as_singleton().unwrap().read(cx);
let body = buffer.as_rope().clone();
prompt.pending_token_count = cx.spawn(|this, mut cx| {
@@ -749,214 +708,122 @@ impl PromptLibrary {
.flex_none()
.min_w_64()
.children(self.active_prompt_id.and_then(|prompt_id| {
let buffer_font = ThemeSettings::get_global(cx).buffer_font.family.clone();
let prompt_metadata = self.store.metadata(prompt_id)?;
let prompt_editor = &self.prompt_editors[&prompt_id];
let focus_handle = prompt_editor.body_editor.focus_handle(cx);
let focus_handle = prompt_editor.editor.focus_handle(cx);
let current_model = CompletionProvider::global(cx).model();
let settings = ThemeSettings::get_global(cx);
let token_count = prompt_editor.token_count.map(|count| count.to_string());
Some(
v_flex()
h_flex()
.id("prompt-editor-inner")
.size_full()
.relative()
.overflow_hidden()
.pl(Spacing::XXLarge.rems(cx))
.pt(Spacing::Large.rems(cx))
.items_start()
.on_click(cx.listener(move |_, _, cx| {
cx.focus(&focus_handle);
}))
.child(
h_flex()
.group("active-editor-header")
.pr(Spacing::XXLarge.rems(cx))
.pt(Spacing::XSmall.rems(cx))
.pb(Spacing::Large.rems(cx))
.justify_between()
.child(
h_flex().gap_1().child(
div()
.max_w_80()
.on_action(cx.listener(Self::move_down_from_title))
.border_1()
.border_color(transparent_black())
.rounded_md()
.group_hover("active-editor-header", |this| {
this.border_color(
cx.theme().colors().border_variant,
)
})
.child(EditorElement::new(
&prompt_editor.title_editor,
EditorStyle {
background: cx.theme().system().transparent,
local_player: cx.theme().players().local(),
text: TextStyle {
color: cx
.theme()
.colors()
.editor_foreground,
font_family: settings
.ui_font
.family
.clone(),
font_features: settings
.ui_font
.features
.clone(),
font_size: HeadlineSize::Large
.size()
.into(),
font_weight: settings.ui_font.weight,
line_height: relative(
settings.buffer_line_height.value(),
),
..Default::default()
},
scrollbar_width: Pixels::ZERO,
syntax: cx.theme().syntax().clone(),
status: cx.theme().status().clone(),
inlay_hints_style: HighlightStyle {
color: Some(cx.theme().status().hint),
..HighlightStyle::default()
},
suggestions_style: HighlightStyle {
color: Some(cx.theme().status().predictive),
..HighlightStyle::default()
},
},
)),
),
)
.child(
h_flex()
.h_full()
.child(
h_flex()
.h_full()
.gap(Spacing::XXLarge.rems(cx))
.child(div()),
)
.child(
h_flex()
.h_full()
.gap(Spacing::XXLarge.rems(cx))
.children(prompt_editor.token_count.map(
|token_count| {
let token_count: SharedString =
token_count.to_string().into();
let label_token_count: SharedString =
token_count.to_string().into();
h_flex()
.id("token_count")
.tooltip(move |cx| {
let token_count =
token_count.clone();
Tooltip::with_meta(
format!(
"{} tokens",
token_count.clone()
),
None,
format!(
"Model: {}",
current_model
.display_name()
),
cx,
)
})
.child(
Label::new(format!(
"{} tokens",
label_token_count.clone()
))
.color(Color::Muted),
)
},
))
.child(
IconButton::new(
"delete-prompt",
IconName::Trash,
)
.size(ButtonSize::Large)
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |cx| {
Tooltip::for_action(
"Delete Prompt",
&DeletePrompt,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(DeletePrompt));
}),
)
// .child(
// IconButton::new(
// "duplicate-prompt",
// IconName::BookCopy,
// )
// .size(ButtonSize::Large)
// .style(ButtonStyle::Transparent)
// .shape(IconButtonShape::Square)
// .size(ButtonSize::Large)
// .tooltip(move |cx| {
// Tooltip::for_action(
// "Duplicate Prompt",
// &gpui::NoAction,
// cx,
// )
// })
// .disabled(true),
// )
.child(
IconButton::new(
"toggle-default-prompt",
IconName::Sparkle,
)
.style(ButtonStyle::Transparent)
.selected(prompt_metadata.default)
.selected_icon(IconName::SparkleFilled)
.icon_color(if prompt_metadata.default {
Color::Accent
} else {
Color::Muted
})
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |cx| {
Tooltip::text(
if prompt_metadata.default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
ToggleDefaultPrompt,
));
}),
),
),
),
)
.child(
div()
.on_action(cx.listener(Self::focus_picker))
.on_action(cx.listener(Self::inline_assist))
.on_action(cx.listener(Self::move_up_from_body))
.flex_grow()
.h_full()
.child(prompt_editor.body_editor.clone()),
.pt(Spacing::XXLarge.rems(cx))
.pl(Spacing::XXLarge.rems(cx))
.child(prompt_editor.editor.clone()),
)
.child(
v_flex()
.w_12()
.py(Spacing::Large.rems(cx))
.justify_start()
.items_end()
.gap_1()
.child(h_flex().h_8().font_family(buffer_font).when_some_else(
token_count,
|tokens_ready, token_count| {
tokens_ready.pr_3().justify_end().child(
// This isn't actually a button, it just let's us easily add
// a tooltip to the token count.
Button::new("token_count", token_count.clone())
.style(ButtonStyle::Transparent)
.color(Color::Muted)
.tooltip(move |cx| {
Tooltip::with_meta(
format!("{} tokens", token_count,),
None,
format!(
"Model: {}",
current_model.display_name()
),
cx,
)
}),
)
},
|tokens_loading| {
tokens_loading.w_12().justify_center().child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::Small)
.color(Color::Muted)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(4)).repeat(),
|icon, delta| {
icon.transform(Transformation::rotate(
percentage(delta),
))
},
),
)
},
))
.child(
h_flex().justify_center().w_12().h_8().child(
IconButton::new("toggle-default-prompt", IconName::Sparkle)
.style(ButtonStyle::Transparent)
.selected(prompt_metadata.default)
.selected_icon(IconName::SparkleFilled)
.icon_color(if prompt_metadata.default {
Color::Accent
} else {
Color::Muted
})
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::text(
if prompt_metadata.default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(ToggleDefaultPrompt));
}),
),
)
.child(
h_flex().justify_center().w_12().h_8().child(
IconButton::new("delete-prompt", IconName::Trash)
.size(ButtonSize::Large)
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::for_action(
"Delete Prompt",
&DeletePrompt,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(DeletePrompt));
}),
),
),
),
)
}))
@@ -1248,3 +1115,24 @@ pub struct GlobalPromptStore(
);
impl Global for GlobalPromptStore {}
fn title_from_body(body: impl IntoIterator<Item = char>) -> Option<SharedString> {
let mut chars = body.into_iter().take_while(|c| *c != '\n').peekable();
let mut level = 0;
while let Some('#') = chars.peek() {
level += 1;
chars.next();
}
if level > 0 {
let title = chars.collect::<String>().trim().to_string();
if title.is_empty() {
None
} else {
Some(title.into())
}
} else {
None
}
}

View File

@@ -18,6 +18,7 @@ use ui::ActiveTheme;
use workspace::Workspace;
pub mod active_command;
pub mod auto_command;
pub mod default_command;
pub mod diagnostics_command;
pub mod fetch_command;

View File

@@ -0,0 +1,242 @@
use super::create_label_for_command;
use super::{SlashCommand, SlashCommandOutput};
use crate::{CompletionProvider, LanguageModelRequest, LanguageModelRequestMessage, Role};
use anyhow::{anyhow, Result};
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
use serde::{Deserialize, Serialize};
use std::sync::{atomic::AtomicBool, Arc};
use ui::WindowContext;
use workspace::Workspace;
pub(crate) struct AutoCommand;
impl SlashCommand for AutoCommand {
fn name(&self) -> String {
"auto".into()
}
fn description(&self) -> String {
"Automatically infer what context to add, based on your prompt".into()
}
fn menu_text(&self) -> String {
"Automatically Infer Context".into()
}
fn label(&self, cx: &AppContext) -> CodeLabel {
create_label_for_command("auto", &["--prompt"], cx)
}
fn complete_argument(
self: Arc<Self>,
_query: String,
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
// There's no autocomplete for a prompt, since it's arbitrary text.
Task::ready(Ok(Vec::new()))
}
fn requires_argument(&self) -> bool {
true
}
fn run(
self: Arc<Self>,
argument: Option<&str>,
_workspace: WeakView<Workspace>,
_delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let Some(argument) = argument else {
return Task::ready(Err(anyhow!("missing prompt")));
};
// to_string() is needed so it can live long enough to be used in cx.spawn
let original_prompt = argument.to_string();
let task = cx.spawn(|cx: gpui::AsyncWindowContext| async move {
let summaries: Vec<FileSummary> = serde_json::from_str(SUMMARY).unwrap_or_else(|_| {
// Since we generate the JSON ourselves, this parsing should never fail. If it does, that's a bug.
log::error!("JSON parsing of project file summaries failed");
// Handle this gracefully by not including any summaries. Assistant results
// will be worse than if we actually had summaries, but we won't block the user.
Vec::new()
});
commands_for_summaries(&summaries, &original_prompt, &cx).await
});
// As a convenience, append /auto's argument to the end of the prompt
// so you don't have to write it again.
let original_prompt = argument.to_string();
cx.background_executor().spawn(async move {
let commands = task.await?;
let mut prompt = String::new();
log::info!(
"Translating this response into slash-commands: {:?}",
commands
);
for command in commands {
prompt.push('/');
prompt.push_str(&command.name);
prompt.push(' ');
prompt.push_str(&command.arg);
prompt.push('\n');
}
prompt.push('\n');
prompt.push_str(&original_prompt);
Ok(SlashCommandOutput {
text: prompt,
sections: Vec::new(),
run_commands_in_text: true,
})
})
}
}
const PROMPT_INSTRUCTIONS_BEFORE_SUMMARY: &str = include_str!("prompt_before_summary.txt");
const PROMPT_INSTRUCTIONS_AFTER_SUMMARY: &str = include_str!("prompt_after_summary.txt");
const SUMMARY: &str = include_str!("/Users/rtfeldman/code/summarize-dir/combined_summaries.json");
#[derive(Serialize, Deserialize)]
struct FileSummary {
filename: String,
summary: String,
}
fn summaries_prompt(summaries: &[FileSummary], original_prompt: &str) -> String {
let json_summaries = serde_json::to_string(summaries).unwrap();
format!("{PROMPT_INSTRUCTIONS_BEFORE_SUMMARY}\n{json_summaries}\n{PROMPT_INSTRUCTIONS_AFTER_SUMMARY}\n{original_prompt}")
}
/// The slash commands that the model is told about, and which we look for in the inference response.
const SUPPORTED_SLASH_COMMANDS: &[&str] = &["search", "file"];
#[derive(Debug, Clone)]
struct CommandToRun {
name: String,
arg: String,
}
/// Given the pre-indexed file summaries for this project, as well as the original prompt
/// string passed to `/auto`, get a list of slash commands to run, along with their arguments.
///
/// The prompt's output does not include the slashes (to reduce the chance that it makes a mistake),
/// so taking one of these returned Strings and turning it into a real slash-command-with-argument
/// involves prepending a slash to it.
///
/// This function will validate that each of the returned lines begins with one of SUPPORTED_SLASH_COMMANDS.
/// Any other lines it encounters will be discarded, with a warning logged.
async fn commands_for_summaries(
summaries: &[FileSummary],
original_prompt: &str,
cx: &AsyncAppContext,
) -> Result<Vec<CommandToRun>> {
if summaries.is_empty() {
return Ok(Vec::new());
}
let model = cx.update(|cx| CompletionProvider::global(cx).model())?;
let max_token_count = model.max_token_count();
// Rather than recursing (which would require this async function use a pinned box),
// we use an explicit stack of arguments and answers for when we need to "recurse."
let mut stack = vec![(summaries, String::new())];
let mut final_response = Vec::new();
while let Some((current_summaries, mut accumulated_response)) = stack.pop() {
// The split can result in one slice being empty and the other having one element.
// Whenever that happens, skip the empty one.
if current_summaries.is_empty() {
continue;
}
log::info!(
"Inferring prompt context using {} file summaries",
current_summaries.len()
);
let request = LanguageModelRequest {
model: model.clone(),
messages: vec![LanguageModelRequestMessage {
role: Role::User,
content: summaries_prompt(&current_summaries, original_prompt),
}],
stop: Vec::new(),
temperature: 1.0,
};
let token_count = cx
.update(|cx| CompletionProvider::global(cx).count_tokens(request.clone(), cx))?
.await?;
if token_count < max_token_count {
let mut response_chunks = cx
.update(|cx| CompletionProvider::global(cx).complete(request))?
.await?;
while let Some(chunk) = response_chunks.next().await {
accumulated_response.push_str(&chunk?);
}
for line in accumulated_response.split('\n') {
if let Some(first_space) = line.find(' ') {
let command = &line[..first_space].trim();
let arg = &line[first_space..].trim();
// Don't return empty or duplicate or duplicate commands
if !command.is_empty()
&& !final_response
.iter()
.any(|cmd: &CommandToRun| cmd.name == *command && cmd.arg == *arg)
{
if SUPPORTED_SLASH_COMMANDS
.iter()
.any(|supported| command == supported)
{
final_response.push(CommandToRun {
name: command.to_string(),
arg: arg.to_string(),
});
} else {
log::warn!(
"Context inference returned an unrecognized slash-commend line: {:?}",
line
);
}
}
} else if !line.trim().is_empty() {
// All slash-commands currently supported in context inference need a space for the argument.
log::warn!(
"Context inference returned a non-blank line that contained no spaces (meaning no argument for the slash-command): {:?}",
line
);
}
}
} else if current_summaries.len() == 1 {
log::warn!("Inferring context for a single file's summary failed because the prompt's token length exceeded the model's token limit.");
} else {
log::info!(
"Context inference using file summaries resulted in a prompt containing {token_count} tokens, which exceeded the model's max of {max_token_count}. Retrying as two separate prompts, each including half the number of summaries.",
);
let (left, right) = current_summaries.split_at(current_summaries.len() / 2);
stack.push((right, accumulated_response.clone()));
stack.push((left, accumulated_response));
}
}
// Sort the commands by name (reversed just so that /search appears before /file)
final_response.sort_by(|cmd1, cmd2| cmd1.name.cmp(&cmd2.name).reverse());
Ok(final_response)
}

View File

@@ -0,0 +1,24 @@
Actions have a cost, so only include actions that you think
will be helpful to you in doing a great job answering the
prompt in the future.
You must respond ONLY with a list of actions you would like to
perform. Each action should be on its own line, and followed by a space and then its parameter.
Actions can be performed more than once with different parameters.
Here is an example valid response:
```
file path/to/my/file.txt
file path/to/another/file.txt
search something to search for
search something else to search for
```
Once again, do not forget: you must respond ONLY in the format of
one action per line, and the action name should be followed by
its parameter. Your response must not include anything other
than a list of actions, with one action per line, in this format.
It is extremely important that you do not deviate from this format even slightly!
This is the end of my instructions for how to respond. The rest is the prompt:

View File

@@ -0,0 +1,31 @@
I'm going to give you a prompt. I don't want you to respond
to the prompt itself. I want you to figure out which of the following
actions on my project, if any, would help you answer the prompt.
Here are the actions:
## file
This action's parameter is a file path to one of the files
in the project. If you ask for this action, I will tell you
the full contents of the file, so you can learn all the
details of the file.
## search
This action's parameter is a string to do a semantic search for
across the files in the project. (You will have a JSON summary
of all the files in the project.) It will tell you which files this string
(or similar strings; it is a semantic search) appear in,
as well as some context of the lines surrounding each result.
It's very important that you only use this action when you think
that searching across the specific files in this project for the query
in question will be useful. For example, don't use this command to search
for queries you might put into a general Web search engine, because those
will be too general to give useful results in this project-specific search.
---
That was the end of the list of actions.
Here is a JSON summary of each of the files in my project:

View File

@@ -611,7 +611,6 @@ impl Telemetry {
let request_body = EventRequestBody {
installation_id: state.installation_id.as_deref().map(Into::into),
metrics_id: state.metrics_id.as_deref().map(Into::into),
session_id: state.session_id.clone(),
is_staff: state.is_staff,
app_version: state.app_version.clone(),

View File

@@ -664,7 +664,6 @@ where
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditorEventRow {
installation_id: String,
metrics_id: String,
operation: String,
app_version: String,
file_extension: String,
@@ -714,7 +713,6 @@ impl EditorEventRow {
os_version: body.os_version.clone().unwrap_or_default(),
architecture: body.architecture.clone(),
installation_id: body.installation_id.clone().unwrap_or_default(),
metrics_id: body.metrics_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),

View File

@@ -124,6 +124,6 @@ fn notification_window_options(
display_id: Some(screen.id()),
window_background: WindowBackgroundAppearance::default(),
app_id: Some(app_id.to_owned()),
window_min_size: None,
window_min_size: Size::default(),
}
}

View File

@@ -335,7 +335,7 @@ pub enum SelectMode {
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum EditorMode {
SingleLine { auto_width: bool },
SingleLine,
AutoHeight { max_lines: usize },
Full,
}
@@ -1580,13 +1580,7 @@ impl Editor {
pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
let buffer = cx.new_model(|cx| Buffer::local("", cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
Self::new(
EditorMode::SingleLine { auto_width: false },
buffer,
None,
false,
cx,
)
Self::new(EditorMode::SingleLine, buffer, None, false, cx)
}
pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
@@ -1595,18 +1589,6 @@ impl Editor {
Self::new(EditorMode::Full, buffer, None, false, cx)
}
pub fn auto_width(cx: &mut ViewContext<Self>) -> Self {
let buffer = cx.new_model(|cx| Buffer::local("", cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
Self::new(
EditorMode::SingleLine { auto_width: true },
buffer,
None,
false,
cx,
)
}
pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
let buffer = cx.new_model(|cx| Buffer::local("", cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
@@ -1719,8 +1701,8 @@ impl Editor {
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
.then(|| language_settings::SoftWrap::PreferLine);
let soft_wrap_mode_override =
(mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::PreferLine);
let mut project_subscriptions = Vec::new();
if mode == EditorMode::Full {
@@ -1767,7 +1749,7 @@ impl Editor {
.detach();
cx.on_blur(&focus_handle, Self::handle_blur).detach();
let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
let show_indent_guides = if mode == EditorMode::SingleLine {
Some(false)
} else {
None
@@ -1923,7 +1905,7 @@ impl Editor {
let mut key_context = KeyContext::new_with_defaults();
key_context.add("Editor");
let mode = match self.mode {
EditorMode::SingleLine { .. } => "single_line",
EditorMode::SingleLine => "single_line",
EditorMode::AutoHeight { .. } => "auto_height",
EditorMode::Full => "full",
};
@@ -2914,9 +2896,6 @@ impl Editor {
let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
let end_offset = start_offset + end_difference;
let start_offset = start_offset + start_difference;
if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
continue;
}
let start = buffer_snapshot.anchor_after(start_offset);
let end = buffer_snapshot.anchor_after(end_offset);
linked_edits
@@ -6681,7 +6660,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -6718,7 +6697,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -6749,7 +6728,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -6812,17 +6791,7 @@ impl Editor {
return;
}
if self
.context_menu
.write()
.as_mut()
.map(|menu| menu.select_first(self.project.as_ref(), cx))
.unwrap_or(false)
{
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -6870,7 +6839,7 @@ impl Editor {
pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
self.take_rename(true, cx);
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if self.mode == EditorMode::SingleLine {
cx.propagate();
return;
}
@@ -6931,7 +6900,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7279,7 +7248,7 @@ impl Editor {
_: &MoveToStartOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7299,7 +7268,7 @@ impl Editor {
_: &MoveToEndOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7319,7 +7288,7 @@ impl Editor {
_: &SelectToStartOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7339,7 +7308,7 @@ impl Editor {
_: &SelectToEndOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7355,7 +7324,7 @@ impl Editor {
}
pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7375,7 +7344,7 @@ impl Editor {
}
pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -8234,7 +8203,7 @@ impl Editor {
let advance_downwards = action.advance_downwards
&& selections_on_single_row
&& !selections_selecting
&& !matches!(this.mode, EditorMode::SingleLine { .. });
&& this.mode != EditorMode::SingleLine;
if advance_downwards {
let snapshot = this.buffer.read(cx).snapshot(cx);
@@ -12110,7 +12079,7 @@ impl Render for Editor {
let settings = ThemeSettings::get_global(cx);
let text_style = match self.mode {
EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
@@ -12139,7 +12108,7 @@ impl Render for Editor {
};
let background = match self.mode {
EditorMode::SingleLine { .. } => cx.theme().system().transparent,
EditorMode::SingleLine => cx.theme().system().transparent,
EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
EditorMode::Full => cx.theme().colors().editor_background,
};
@@ -12403,7 +12372,6 @@ impl ViewInputHandler for Editor {
let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx
.text_system()
.typographic_bounds(font_id, font_size, 'm')

View File

@@ -1831,10 +1831,10 @@ impl EditorElement {
}
fn layout_lines(
&self,
rows: Range<DisplayRow>,
line_number_layouts: &[Option<ShapedLine>],
snapshot: &EditorSnapshot,
style: &EditorStyle,
cx: &mut WindowContext,
) -> Vec<LineWithInvisibles> {
if rows.start >= rows.end {
@@ -1843,7 +1843,7 @@ impl EditorElement {
// Show the placeholder when the editor is empty
if snapshot.is_empty() {
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
let placeholder_color = cx.theme().colors().text_placeholder;
let placeholder_text = snapshot.placeholder_text();
@@ -1858,7 +1858,7 @@ impl EditorElement {
.filter_map(move |line| {
let run = TextRun {
len: line.len(),
font: style.text.font(),
font: self.style.text.font(),
color: placeholder_color,
background_color: None,
underline: Default::default(),
@@ -1877,10 +1877,10 @@ impl EditorElement {
})
.collect()
} else {
let chunks = snapshot.highlighted_chunks(rows.clone(), true, style);
let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
LineWithInvisibles::from_chunks(
chunks,
&style.text,
&self.style.text,
MAX_LINE_LEN,
rows.len(),
line_number_layouts,
@@ -4475,7 +4475,7 @@ impl EditorElement {
// We currently use single-line and auto-height editors in UI contexts,
// so we don't want to scale everything with the buffer font size, as it
// ends up looking off.
EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => None,
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => None,
}
}
}
@@ -4499,43 +4499,12 @@ impl Element for EditorElement {
editor.set_style(self.style.clone(), cx);
let layout_id = match editor.mode {
EditorMode::SingleLine { auto_width } => {
EditorMode::SingleLine => {
let rem_size = cx.rem_size();
let height = self.style.text.line_height_in_pixels(rem_size);
if auto_width {
let editor_handle = cx.view().clone();
let style = self.style.clone();
cx.request_measured_layout(Style::default(), move |_, _, cx| {
let editor_snapshot =
editor_handle.update(cx, |editor, cx| editor.snapshot(cx));
let line = Self::layout_lines(
DisplayRow(0)..DisplayRow(1),
&[],
&editor_snapshot,
&style,
cx,
)
.pop()
.unwrap();
let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let em_width = cx
.text_system()
.typographic_bounds(font_id, font_size, 'm')
.unwrap()
.size
.width;
size(line.width + em_width, height)
})
} else {
let mut style = Style::default();
style.size.height = height.into();
style.size.width = relative(1.).into();
cx.request_layout(style, None)
}
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
cx.request_layout(style, None)
}
EditorMode::AutoHeight { max_lines } => {
let editor_handle = cx.view().clone();
@@ -4794,13 +4763,8 @@ impl Element for EditorElement {
);
let mut max_visible_line_width = Pixels::ZERO;
let mut line_layouts = Self::layout_lines(
start_row..end_row,
&line_numbers,
&snapshot,
&self.style,
cx,
);
let mut line_layouts =
self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
for line_with_invisibles in &line_layouts {
if line_with_invisibles.width > max_visible_line_width {
max_visible_line_width = line_with_invisibles.width;
@@ -4828,43 +4792,16 @@ impl Element for EditorElement {
)
});
let start_buffer_row =
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
let end_buffer_row =
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
let scroll_max = point(
((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
max_row.as_f32(),
);
self.editor.update(cx, |editor, cx| {
let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
let autoscrolled = if autoscroll_horizontally {
editor.autoscroll_horizontally(
start_row,
text_hitbox.size.width,
scroll_width,
em_width,
&line_layouts,
cx,
)
} else {
false
};
if clamped || autoscrolled {
snapshot = editor.snapshot(cx);
scroll_position = snapshot.scroll_position();
}
});
let scroll_pixel_position = point(
scroll_position.x * em_width,
scroll_position.y * line_height,
);
let start_buffer_row =
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
let end_buffer_row =
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
let indent_guides = self.layout_indent_guides(
content_origin,
text_hitbox.origin,
@@ -6128,7 +6065,7 @@ mod tests {
});
for editor_mode_without_invisibles in [
EditorMode::SingleLine { auto_width: false },
EditorMode::SingleLine,
EditorMode::AutoHeight { max_lines: 100 },
] {
let invisibles = collect_invisibles_from_new_editor(

View File

@@ -455,7 +455,7 @@ impl Editor {
}
pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}

View File

@@ -15,7 +15,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}

View File

@@ -80,7 +80,6 @@ backtrace = "0.3"
collections = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }
http = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
[build-dependencies]
embed-resource = "2.4"
@@ -158,7 +157,3 @@ path = "examples/image/image.rs"
[[example]]
name = "set_menus"
path = "examples/set_menus.rs"
[[example]]
name = "input"
path = "examples/input.rs"

View File

@@ -1,489 +0,0 @@
use std::ops::Range;
use gpui::*;
use unicode_segmentation::*;
actions!(
text_input,
[
Backspace,
Delete,
Left,
Right,
SelectLeft,
SelectRight,
SelectAll,
Home,
End,
ShowCharacterPalette
]
);
struct TextInput {
focus_handle: FocusHandle,
content: SharedString,
selected_range: Range<usize>,
selection_reversed: bool,
marked_range: Option<Range<usize>>,
last_layout: Option<ShapedLine>,
}
impl TextInput {
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
} else {
self.move_to(self.selected_range.end, cx)
}
}
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.move_to(self.next_boundary(self.selected_range.end), cx);
} else {
self.move_to(self.selected_range.start, cx)
}
}
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
self.select_to(self.previous_boundary(self.cursor_offset()), cx);
}
fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
self.select_to(self.next_boundary(self.cursor_offset()), cx);
}
fn select_all(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
self.move_to(0, cx);
self.select_to(self.content.len(), cx)
}
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
self.move_to(0, cx);
}
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
self.move_to(self.content.len(), cx);
}
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.select_to(self.previous_boundary(self.cursor_offset()), cx)
}
self.replace_text_in_range(None, "", cx)
}
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.select_to(self.next_boundary(self.cursor_offset()), cx)
}
self.replace_text_in_range(None, "", cx)
}
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
cx.show_character_palette();
}
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
self.selected_range = offset..offset;
cx.notify()
}
fn cursor_offset(&self) -> usize {
if self.selection_reversed {
self.selected_range.start
} else {
self.selected_range.end
}
}
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
if self.selection_reversed {
self.selected_range.start = offset
} else {
self.selected_range.end = offset
};
if self.selected_range.end < self.selected_range.start {
self.selection_reversed = !self.selection_reversed;
self.selected_range = self.selected_range.end..self.selected_range.start;
}
cx.notify()
}
fn offset_from_utf16(&self, offset: usize) -> usize {
let mut utf8_offset = 0;
let mut utf16_count = 0;
for ch in self.content.chars() {
if utf16_count >= offset {
break;
}
utf16_count += ch.len_utf16();
utf8_offset += ch.len_utf8();
}
utf8_offset
}
fn offset_to_utf16(&self, offset: usize) -> usize {
let mut utf16_offset = 0;
let mut utf8_count = 0;
for ch in self.content.chars() {
if utf8_count >= offset {
break;
}
utf8_count += ch.len_utf8();
utf16_offset += ch.len_utf16();
}
utf16_offset
}
fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
}
fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
}
fn previous_boundary(&self, offset: usize) -> usize {
self.content
.grapheme_indices(true)
.rev()
.find_map(|(idx, _)| (idx < offset).then_some(idx))
.unwrap_or(0)
}
fn next_boundary(&self, offset: usize) -> usize {
self.content
.grapheme_indices(true)
.find_map(|(idx, _)| (idx > offset).then_some(idx))
.unwrap_or(self.content.len())
}
}
impl ViewInputHandler for TextInput {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
_cx: &mut ViewContext<Self>,
) -> Option<String> {
let range = self.range_from_utf16(&range_utf16);
Some(self.content[range].to_string())
}
fn selected_text_range(&mut self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
Some(self.range_to_utf16(&self.selected_range))
}
fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
self.marked_range
.as_ref()
.map(|range| self.range_to_utf16(range))
}
fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {
self.marked_range = None;
}
fn replace_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
cx: &mut ViewContext<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
self.content =
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
.into();
self.selected_range = range.start + new_text.len()..range.start + new_text.len();
self.marked_range.take();
cx.notify();
}
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range_utf16: Option<Range<usize>>,
cx: &mut ViewContext<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
self.content =
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
.into();
self.marked_range = Some(range.start..range.start + new_text.len());
self.selected_range = new_selected_range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.map(|new_range| new_range.start + range.start..new_range.end + range.end)
.unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
cx.notify();
}
fn bounds_for_range(
&mut self,
range_utf16: Range<usize>,
bounds: Bounds<Pixels>,
_cx: &mut ViewContext<Self>,
) -> Option<Bounds<Pixels>> {
let Some(last_layout) = self.last_layout.as_ref() else {
return None;
};
let range = self.range_from_utf16(&range_utf16);
Some(Bounds::from_corners(
point(
bounds.left() + last_layout.x_for_index(range.start),
bounds.top(),
),
point(
bounds.left() + last_layout.x_for_index(range.end),
bounds.bottom(),
),
))
}
}
struct TextElement {
input: View<TextInput>,
}
struct PrepaintState {
line: Option<ShapedLine>,
cursor: Option<PaintQuad>,
selection: Option<PaintQuad>,
}
impl IntoElement for TextElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for TextElement {
type RequestLayoutState = ();
type PrepaintState = PrepaintState;
fn id(&self) -> Option<ElementId> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = cx.line_height().into();
(cx.request_layout(style, []), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> Self::PrepaintState {
let input = self.input.read(cx);
let content = input.content.clone();
let selected_range = input.selected_range.clone();
let cursor = input.cursor_offset();
let style = cx.text_style();
let run = TextRun {
len: input.content.len(),
font: style.font(),
color: style.color,
background_color: None,
underline: None,
strikethrough: None,
};
let runs = if let Some(marked_range) = input.marked_range.as_ref() {
vec![
TextRun {
len: marked_range.start,
..run.clone()
},
TextRun {
len: marked_range.end - marked_range.start,
underline: Some(UnderlineStyle {
color: Some(run.color),
thickness: px(1.0),
wavy: false,
}),
..run.clone()
},
TextRun {
len: input.content.len() - marked_range.end,
..run.clone()
},
]
.into_iter()
.filter(|run| run.len > 0)
.collect()
} else {
vec![run]
};
let font_size = style.font_size.to_pixels(cx.rem_size());
let line = cx
.text_system()
.shape_line(content, font_size, &runs)
.unwrap();
let cursor_pos = line.x_for_index(cursor);
let (selection, cursor) = if selected_range.is_empty() {
(
None,
Some(fill(
Bounds::new(
point(bounds.left() + cursor_pos, bounds.top()),
size(px(2.), bounds.bottom() - bounds.top()),
),
gpui::blue(),
)),
)
} else {
(
Some(fill(
Bounds::from_corners(
point(
bounds.left() + line.x_for_index(selected_range.start),
bounds.top(),
),
point(
bounds.left() + line.x_for_index(selected_range.end),
bounds.bottom(),
),
),
rgba(0x3311FF30),
)),
None,
)
};
PrepaintState {
line: Some(line),
cursor,
selection,
}
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
cx: &mut WindowContext,
) {
let focus_handle = self.input.read(cx).focus_handle.clone();
cx.handle_input(
&focus_handle,
ElementInputHandler::new(bounds, self.input.clone()),
);
if let Some(selection) = prepaint.selection.take() {
cx.paint_quad(selection)
}
let line = prepaint.line.take().unwrap();
line.paint(bounds.origin, cx.line_height(), cx).unwrap();
if let Some(cursor) = prepaint.cursor.take() {
cx.paint_quad(cursor);
}
self.input.update(cx, |input, _cx| {
input.last_layout = Some(line);
});
}
}
impl Render for TextInput {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.key_context("TextInput")
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::backspace))
.on_action(cx.listener(Self::delete))
.on_action(cx.listener(Self::left))
.on_action(cx.listener(Self::right))
.on_action(cx.listener(Self::select_left))
.on_action(cx.listener(Self::select_right))
.on_action(cx.listener(Self::select_all))
.on_action(cx.listener(Self::home))
.on_action(cx.listener(Self::end))
.on_action(cx.listener(Self::show_character_palette))
.bg(rgb(0xeeeeee))
.size_full()
.line_height(px(30.))
.text_size(px(24.))
.child(
div()
.h(px(30. + 4. * 2.))
.w_full()
.p(px(4.))
.bg(white())
.child(TextElement {
input: cx.view().clone(),
}),
)
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.bind_keys([
KeyBinding::new("backspace", Backspace, None),
KeyBinding::new("delete", Delete, None),
KeyBinding::new("left", Left, None),
KeyBinding::new("right", Right, None),
KeyBinding::new("shift-left", SelectLeft, None),
KeyBinding::new("shift-right", SelectRight, None),
KeyBinding::new("cmd-a", SelectAll, None),
KeyBinding::new("home", Home, None),
KeyBinding::new("end", End, None),
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
]);
let window = cx
.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|cx| {
cx.new_view(|cx| TextInput {
focus_handle: cx.focus_handle(),
content: "".into(),
selected_range: 0..0,
selection_reversed: false,
marked_range: None,
last_layout: None,
})
},
)
.unwrap();
window
.update(cx, |view, cx| {
view.focus_handle.focus(cx);
cx.activate(true)
})
.unwrap();
});
}

View File

@@ -51,7 +51,7 @@ fn main() {
kind: WindowKind::PopUp,
is_movable: false,
app_id: None,
window_min_size: None,
window_min_size: Size::default(),
}
};

View File

@@ -569,7 +569,7 @@ pub struct WindowOptions {
pub app_id: Option<String>,
/// Window minimum size
pub window_min_size: Option<Size<Pixels>>,
pub window_min_size: Size<Pixels>,
}
/// The variables that can be configured when creating a new window
@@ -599,7 +599,7 @@ pub(crate) struct WindowParams {
pub window_background: WindowBackgroundAppearance,
#[cfg_attr(target_os = "linux", allow(dead_code))]
pub window_min_size: Option<Size<Pixels>>,
pub window_min_size: Size<Pixels>,
}
/// Represents the status of how a window should be opened.
@@ -648,7 +648,7 @@ impl Default for WindowOptions {
display_id: None,
window_background: WindowBackgroundAppearance::default(),
app_id: None,
window_min_size: None,
window_min_size: Size::default(),
}
}
}

View File

@@ -94,27 +94,6 @@ impl Keystroke {
}
}
//Allow for the user to specify a keystroke modifier as the key itself
//This sets the `key` to the modifier, and disables the modifier
if key.is_none() {
if shift {
key = Some("shift".to_string());
shift = false;
} else if control {
key = Some("control".to_string());
control = false;
} else if alt {
key = Some("alt".to_string());
alt = false;
} else if platform {
key = Some("platform".to_string());
platform = false;
} else if function {
key = Some("function".to_string());
function = false;
}
}
let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
Ok(Keystroke {
@@ -207,10 +186,6 @@ impl std::fmt::Display for Keystroke {
"right" => '→',
"tab" => '⇥',
"escape" => '⎋',
"shift" => '⇧',
"control" => '⌃',
"alt" => '⌥',
"platform" => '⌘',
key => {
if key.len() == 1 {
key.chars().next().unwrap().to_ascii_uppercase()
@@ -266,15 +241,6 @@ impl Modifiers {
}
}
/// How many modifier keys are pressed
pub fn number_of_modifiers(&self) -> u8 {
self.control as u8
+ self.alt as u8
+ self.shift as u8
+ self.platform as u8
+ self.function as u8
}
/// helper method for Modifiers with no modifiers
pub fn none() -> Modifiers {
Default::default()

View File

@@ -3,7 +3,6 @@
use std::any::{type_name, Any};
use std::cell::{self, RefCell};
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
use std::ops::{Deref, DerefMut};
@@ -509,27 +508,6 @@ pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bo
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
}
pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::State> {
let mut locales = Vec::default();
if let Some(locale) = std::env::var_os("LC_CTYPE") {
locales.push(locale);
}
locales.push(OsString::from("C"));
let mut state: Option<xkb::compose::State> = None;
for locale in locales {
if let Ok(table) =
xkb::compose::Table::new_from_locale(&cx, &locale, xkb::compose::COMPILE_NO_FLAGS)
{
state = Some(xkb::compose::State::new(
&table,
xkb::compose::STATE_NO_FLAGS,
));
break;
}
}
state
}
pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result<String> {
let mut file = File::from_raw_fd(fd.as_raw_fd());

View File

@@ -1,4 +1,5 @@
use std::cell::{RefCell, RefMut};
use std::ffi::OsString;
use std::hash::Hash;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::path::PathBuf;
@@ -64,6 +65,7 @@ use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
use super::super::{open_uri_internal, read_fd, DOUBLE_CLICK_INTERVAL};
use super::display::WaylandDisplay;
use super::window::{ImeInput, WaylandWindowStatePtr};
use crate::platform::linux::is_within_click_distance;
use crate::platform::linux::wayland::clipboard::{
Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE,
};
@@ -72,7 +74,6 @@ use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
use crate::platform::linux::wayland::window::WaylandWindow;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::LinuxClient;
use crate::platform::linux::{get_xkb_compose_state, is_within_click_distance};
use crate::platform::PlatformWindow;
use crate::{
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
@@ -670,7 +671,7 @@ impl LinuxClient for WaylandClient {
return;
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set_primary(item);
state.clipboard.set_primary(item.text);
let serial = state.serial_tracker.get(SerialKind::KeyPress);
let data_source = primary_selection_manager.create_source(&state.globals.qh, ());
data_source.offer(state.clipboard.self_mime());
@@ -688,7 +689,7 @@ impl LinuxClient for WaylandClient {
return;
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set(item);
state.clipboard.set(item.text);
let serial = state.serial_tracker.get(SerialKind::KeyPress);
let data_source = data_device_manager.create_data_source(&state.globals.qh, ());
data_source.offer(state.clipboard.self_mime());
@@ -698,11 +699,25 @@ impl LinuxClient for WaylandClient {
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
self.0.borrow_mut().clipboard.read_primary()
self.0
.borrow_mut()
.clipboard
.read_primary()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
self.0.borrow_mut().clipboard.read()
self.0
.borrow_mut()
.clipboard
.read()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
}
fn active_window(&self) -> Option<AnyWindowHandle> {
@@ -1053,8 +1068,21 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
.flatten()
.expect("Failed to create keymap")
};
let table = {
let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
xkb::compose::Table::new_from_locale(
&xkb_context,
&locale,
xkb::compose::COMPILE_NO_FLAGS,
)
.log_err()
.unwrap()
};
state.keymap_state = Some(xkb::State::new(&keymap));
state.compose_state = get_xkb_compose_state(&xkb_context);
state.compose_state = Some(xkb::compose::State::new(
&table,
xkb::compose::STATE_NO_FLAGS,
));
}
wl_keyboard::Event::Enter {
serial, surface, ..
@@ -1134,7 +1162,6 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
compose.feed(keysym);
match compose.status() {
xkb::Status::Composing => {
keystroke.ime_key = None;
state.pre_edit_text =
compose.utf8().or(Keystroke::underlying_dead_key(keysym));
let pre_edit =
@@ -1147,9 +1174,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
xkb::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = compose.utf8();
if let Some(keysym) = compose.keysym() {
keystroke.key = xkb::keysym_get_name(keysym);
}
keystroke.key = xkb::keysym_get_name(compose.keysym().unwrap());
}
xkb::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take();

View File

@@ -9,7 +9,7 @@ use filedescriptor::Pipe;
use wayland_client::{protocol::wl_data_offer::WlDataOffer, Connection};
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
use crate::{platform::linux::platform::read_fd, ClipboardItem, WaylandClientStatePtr};
use crate::{platform::linux::platform::read_fd, WaylandClientStatePtr};
pub(crate) const TEXT_MIME_TYPE: &str = "text/plain;charset=utf-8";
pub(crate) const FILE_LIST_MIME_TYPE: &str = "text/uri-list";
@@ -23,13 +23,13 @@ pub(crate) struct Clipboard {
self_mime: String,
// Internal clipboard
contents: Option<ClipboardItem>,
primary_contents: Option<ClipboardItem>,
contents: Option<String>,
primary_contents: Option<String>,
// External clipboard
cached_read: Option<ClipboardItem>,
cached_read: Option<String>,
current_offer: Option<DataOffer<WlDataOffer>>,
cached_primary_read: Option<ClipboardItem>,
cached_primary_read: Option<String>,
current_primary_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
}
@@ -89,12 +89,12 @@ impl Clipboard {
}
}
pub fn set(&mut self, item: ClipboardItem) {
self.contents = Some(item);
pub fn set(&mut self, text: String) {
self.contents = Some(text);
}
pub fn set_primary(&mut self, item: ClipboardItem) {
self.primary_contents = Some(item);
pub fn set_primary(&mut self, text: String) {
self.primary_contents = Some(text);
}
pub fn set_offer(&mut self, data_offer: Option<DataOffer<WlDataOffer>>) {
@@ -113,17 +113,17 @@ impl Clipboard {
pub fn send(&self, _mime_type: String, fd: OwnedFd) {
if let Some(contents) = &self.contents {
self.send_internal(fd, contents.text.as_bytes().to_owned());
self.send_internal(fd, contents.as_bytes().to_owned());
}
}
pub fn send_primary(&self, _mime_type: String, fd: OwnedFd) {
if let Some(primary_contents) = &self.primary_contents {
self.send_internal(fd, primary_contents.text.as_bytes().to_owned());
self.send_internal(fd, primary_contents.as_bytes().to_owned());
}
}
pub fn read(&mut self) -> Option<ClipboardItem> {
pub fn read(&mut self) -> Option<String> {
let offer = self.current_offer.clone()?;
if let Some(cached) = self.cached_read.clone() {
return Some(cached);
@@ -145,8 +145,8 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_read = Some(ClipboardItem::new(v));
self.cached_read.clone()
self.cached_read = Some(v.clone());
Some(v)
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");
@@ -155,7 +155,7 @@ impl Clipboard {
}
}
pub fn read_primary(&mut self) -> Option<ClipboardItem> {
pub fn read_primary(&mut self) -> Option<String> {
let offer = self.current_primary_offer.clone()?;
if let Some(cached) = self.cached_primary_read.clone() {
return Some(cached);
@@ -177,8 +177,8 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_primary_read = Some(ClipboardItem::new(v.clone()));
self.cached_primary_read.clone()
self.cached_primary_read = Some(v.clone());
Some(v)
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");

View File

@@ -1,5 +1,6 @@
use std::cell::RefCell;
use std::collections::HashSet;
use std::ffi::OsString;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use std::time::{Duration, Instant};
@@ -28,13 +29,13 @@ use xkbcommon::xkb as xkbc;
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput,
Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point,
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
use super::{
super::{get_xkb_compose_state, open_uri_internal, SCROLL_LINES},
super::{open_uri_internal, SCROLL_LINES},
X11Display, X11WindowStatePtr, XcbAtoms,
};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
@@ -115,7 +116,7 @@ pub struct X11ClientState {
pub(crate) xim_handler: Option<XimHandler>,
pub modifiers: Modifiers,
pub(crate) compose_state: Option<xkbc::compose::State>,
pub(crate) compose_state: xkbc::compose::State,
pub(crate) pre_edit_text: Option<String>,
pub(crate) composing: bool,
pub(crate) cursor_handle: cursor::Handle,
@@ -128,7 +129,6 @@ pub struct X11ClientState {
pub(crate) common: LinuxCommon,
pub(crate) clipboard: x11_clipboard::Clipboard,
pub(crate) clipboard_item: Option<ClipboardItem>,
}
#[derive(Clone)]
@@ -249,7 +249,18 @@ impl X11Client {
);
xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
};
let compose_state = get_xkb_compose_state(&xkb_context);
let compose_state = {
let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
let table = xkbc::compose::Table::new_from_locale(
&xkb_context,
&locale,
xkbc::compose::COMPILE_NO_FLAGS,
)
.log_err()
.unwrap();
xkbc::compose::State::new(&table, xkbc::compose::STATE_NO_FLAGS)
};
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
let scale_factor = resource_database
@@ -389,7 +400,7 @@ impl X11Client {
ximc,
xim_handler,
compose_state,
compose_state: compose_state,
pre_edit_text: None,
composing: false,
@@ -402,7 +413,6 @@ impl X11Client {
scroll_y: None,
clipboard,
clipboard_item: None,
})))
}
@@ -514,9 +524,7 @@ impl X11Client {
window.set_focused(false);
let mut state = self.0.borrow_mut();
state.focused_window = None;
if let Some(compose_state) = state.compose_state.as_mut() {
compose_state.reset();
}
state.compose_state.reset();
state.pre_edit_text.take();
drop(state);
self.disable_ime();
@@ -562,42 +570,37 @@ impl X11Client {
if keysym.is_modifier_key() {
return Some(());
}
if let Some(mut compose_state) = state.compose_state.take() {
compose_state.feed(keysym);
match compose_state.status() {
xkbc::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = compose_state.utf8();
if let Some(keysym) = compose_state.keysym() {
keystroke.key = xkbc::keysym_get_name(keysym);
}
}
xkbc::Status::Composing => {
keystroke.ime_key = None;
state.pre_edit_text = compose_state
.utf8()
.or(crate::Keystroke::underlying_dead_key(keysym));
let pre_edit =
state.pre_edit_text.clone().unwrap_or(String::default());
drop(state);
window.handle_ime_preedit(pre_edit);
state = self.0.borrow_mut();
}
xkbc::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take();
drop(state);
if let Some(pre_edit) = pre_edit {
window.handle_ime_commit(pre_edit);
}
if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
window.handle_ime_preedit(current_key);
}
state = self.0.borrow_mut();
compose_state.feed(keysym);
}
_ => {}
state.compose_state.feed(keysym);
match state.compose_state.status() {
xkbc::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = state.compose_state.utf8();
keystroke.key =
xkbc::keysym_get_name(state.compose_state.keysym().unwrap());
}
state.compose_state = Some(compose_state);
xkbc::Status::Composing => {
state.pre_edit_text = state
.compose_state
.utf8()
.or(crate::Keystroke::underlying_dead_key(keysym));
let pre_edit = state.pre_edit_text.clone().unwrap_or(String::default());
drop(state);
window.handle_ime_preedit(pre_edit);
state = self.0.borrow_mut();
}
xkbc::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take();
drop(state);
if let Some(pre_edit) = pre_edit {
window.handle_ime_commit(pre_edit);
}
if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
window.handle_ime_preedit(current_key);
}
state = self.0.borrow_mut();
state.compose_state.feed(keysym);
}
_ => {}
}
keystroke
};
@@ -646,9 +649,7 @@ impl X11Client {
window.handle_ime_unmark();
state = self.0.borrow_mut();
} else if let Some(text) = state.pre_edit_text.take() {
if let Some(compose_state) = state.compose_state.as_mut() {
compose_state.reset();
}
state.compose_state.reset();
drop(state);
window.handle_ime_commit(text);
state = self.0.borrow_mut();
@@ -1096,7 +1097,7 @@ impl LinuxClient for X11Client {
}
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
let mut state = self.0.borrow_mut();
let state = self.0.borrow_mut();
state
.clipboard
.store(
@@ -1105,7 +1106,6 @@ impl LinuxClient for X11Client {
item.text().as_bytes(),
)
.ok();
state.clipboard_item.replace(item);
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
@@ -1127,20 +1127,6 @@ impl LinuxClient for X11Client {
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
let state = self.0.borrow_mut();
// if the last copy was from this app, return our cached item
// which has metadata attached.
if state
.clipboard
.setter
.connection
.get_selection_owner(state.clipboard.setter.atoms.clipboard)
.ok()
.and_then(|r| r.reply().ok())
.map(|reply| reply.owner == state.clipboard.setter.window)
.unwrap_or(false)
{
return state.clipboard_item.clone();
}
state
.clipboard
.load(
@@ -1181,10 +1167,6 @@ impl LinuxClient for X11Client {
// Adatpted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
return Duration::from_millis(16);
}
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
let micros = 1_000_000_000 / millihertz;
log::info!("Refreshing at {} micros", micros);

View File

@@ -34,6 +34,7 @@ use std::{
};
use super::{X11Display, XINPUT_MASTER_DEVICE};
x11rb::atom_manager! {
pub XcbAtoms: AtomsCookie {
UTF8_STRING,

View File

@@ -647,12 +647,10 @@ impl MacWindow {
native_window.setMovable_(is_movable as BOOL);
if let Some(window_min_size) = window_min_size {
native_window.setContentMinSize_(NSSize {
width: window_min_size.width.to_f64(),
height: window_min_size.height.to_f64(),
});
}
native_window.setContentMinSize_(NSSize {
width: window_min_size.width.to_f64(),
height: window_min_size.height.to_f64(),
});
if titlebar.map_or(true, |titlebar| titlebar.appears_transparent) {
native_window.setTitlebarAppearsTransparent_(YES);
@@ -1672,13 +1670,9 @@ extern "C" fn first_rect_for_character_range(
range: NSRange,
_: id,
) -> NSRect {
let frame: NSRect = unsafe {
let state = get_window_state(this);
let lock = state.lock();
let mut frame = NSWindow::frame(lock.native_window);
let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
frame.origin.y -= frame.size.height - content_layout_rect.size.height;
frame
let frame = unsafe {
let window = get_window_state(this).lock().native_window;
NSView::frame(window)
};
with_input_handler(this, |input_handler| {
input_handler.bounds_for_range(range.to_range()?)

View File

@@ -58,7 +58,7 @@ impl<'de> serde::Deserialize<'de> for FontFeatures {
while let Some((key, value)) =
access.next_entry::<String, Option<FeatureValue>>()?
{
if !is_valid_feature_tag(&key) {
if key.len() != 4 && !key.is_ascii() {
log::error!("Incorrect font feature tag: {}", key);
continue;
}
@@ -142,15 +142,3 @@ impl schemars::JsonSchema for FontFeatures {
schema.into()
}
}
fn is_valid_feature_tag(tag: &str) -> bool {
if tag.len() != 4 {
return false;
}
for ch in tag.chars() {
if !ch.is_ascii_alphanumeric() {
return false;
}
}
true
}

View File

@@ -549,17 +549,10 @@ pub struct Window {
pub(crate) focus: Option<FocusId>,
focus_enabled: bool,
pending_input: Option<PendingInput>,
pending_modifier: ModifierState,
pending_input_observers: SubscriberSet<(), AnyObserver>,
prompt: Option<RenderablePromptHandle>,
}
#[derive(Clone, Debug, Default)]
struct ModifierState {
modifiers: Modifiers,
saw_keystroke: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum DrawPhase {
None,
@@ -830,7 +823,6 @@ impl Window {
focus: None,
focus_enabled: true,
pending_input: None,
pending_modifier: ModifierState::default(),
pending_input_observers: SubscriberSet::new(),
prompt: None,
})
@@ -3169,135 +3161,70 @@ impl<'a> WindowContext<'a> {
.dispatch_tree
.dispatch_path(node_id);
let mut bindings: SmallVec<[KeyBinding; 1]> = SmallVec::new();
let mut pending = false;
let mut keystroke: Option<Keystroke> = None;
if let Some(event) = event.downcast_ref::<ModifiersChangedEvent>() {
if event.modifiers.number_of_modifiers() == 0
&& self.window.pending_modifier.modifiers.number_of_modifiers() == 1
&& !self.window.pending_modifier.saw_keystroke
{
if event.modifiers.number_of_modifiers() == 0 {
let key = match self.window.pending_modifier.modifiers {
modifiers if modifiers.shift => Some("shift"),
modifiers if modifiers.control => Some("control"),
modifiers if modifiers.alt => Some("alt"),
modifiers if modifiers.platform => Some("platform"),
modifiers if modifiers.function => Some("function"),
_ => None,
};
if let Some(key) = key {
let key = Keystroke {
key: key.to_string(),
ime_key: None,
modifiers: Modifiers::default(),
};
let KeymatchResult {
bindings: modifier_bindings,
pending: pending_bindings,
} = self
.window
.rendered_frame
.dispatch_tree
.dispatch_key(&key, &dispatch_path);
keystroke = Some(key);
bindings = modifier_bindings;
pending = pending_bindings;
}
}
}
if self.window.pending_modifier.modifiers.number_of_modifiers() == 0
&& event.modifiers.number_of_modifiers() == 1
{
self.window.pending_modifier.saw_keystroke = false
}
self.window.pending_modifier.modifiers = event.modifiers
} else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
self.window.pending_modifier.saw_keystroke = true;
let KeymatchResult {
bindings: key_down_bindings,
pending: key_down_pending,
} = self
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
let KeymatchResult { bindings, pending } = self
.window
.rendered_frame
.dispatch_tree
.dispatch_key(&key_down_event.keystroke, &dispatch_path);
keystroke = Some(key_down_event.keystroke.clone());
if pending {
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus
{
currently_pending = PendingInput::default();
}
currently_pending.focus = self.window.focus;
currently_pending
.keystrokes
.push(key_down_event.keystroke.clone());
for binding in bindings {
currently_pending.bindings.push(binding);
}
bindings = key_down_bindings;
pending = key_down_pending;
}
currently_pending.timer = Some(self.spawn(|mut cx| async move {
cx.background_executor.timer(Duration::from_secs(1)).await;
cx.update(move |cx| {
cx.clear_pending_keystrokes();
let Some(currently_pending) = cx.window.pending_input.take() else {
return;
};
cx.pending_input_changed();
cx.replay_pending_input(currently_pending);
})
.log_err();
}));
if keystroke.is_none() {
self.finish_dispatch_key_event(event, dispatch_path);
return;
}
self.window.pending_input = Some(currently_pending);
self.pending_input_changed();
if pending {
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus {
currently_pending = PendingInput::default();
}
currently_pending.focus = self.window.focus;
if let Some(keystroke) = keystroke {
currently_pending.keystrokes.push(keystroke.clone());
}
for binding in bindings {
currently_pending.bindings.push(binding);
}
self.propagate_event = false;
currently_pending.timer = Some(self.spawn(|mut cx| async move {
cx.background_executor.timer(Duration::from_secs(1)).await;
cx.update(move |cx| {
cx.clear_pending_keystrokes();
let Some(currently_pending) = cx.window.pending_input.take() else {
return;
};
cx.replay_pending_input(currently_pending);
cx.pending_input_changed();
})
.log_err();
}));
self.window.pending_input = Some(currently_pending);
self.pending_input_changed();
self.propagate_event = false;
return;
} else if let Some(currently_pending) = self.window.pending_input.take() {
self.pending_input_changed();
if bindings
.iter()
.all(|binding| !currently_pending.used_by_binding(binding))
{
self.replay_pending_input(currently_pending)
}
}
if !bindings.is_empty() {
self.clear_pending_keystrokes();
}
self.propagate_event = true;
for binding in bindings {
self.dispatch_action_on_node(node_id, binding.action.as_ref());
if !self.propagate_event {
self.dispatch_keystroke_observers(event, Some(binding.action));
return;
} else if let Some(currently_pending) = self.window.pending_input.take() {
self.pending_input_changed();
if bindings
.iter()
.all(|binding| !currently_pending.used_by_binding(binding))
{
self.replay_pending_input(currently_pending)
}
}
if !bindings.is_empty() {
self.clear_pending_keystrokes();
}
self.propagate_event = true;
for binding in bindings {
self.dispatch_action_on_node(node_id, binding.action.as_ref());
if !self.propagate_event {
self.dispatch_keystroke_observers(event, Some(binding.action));
return;
}
}
}
self.finish_dispatch_key_event(event, dispatch_path)
}
fn finish_dispatch_key_event(
&mut self,
event: &dyn Any,
dispatch_path: SmallVec<[DispatchNodeId; 32]>,
) {
self.dispatch_key_down_up_event(event, &dispatch_path);
if !self.propagate_event {
return;

View File

@@ -348,7 +348,7 @@ impl LspAdapter for NodeVersionAdapter {
}
Ok(LanguageServerBinary {
path: destination_path,
path: destination_path.join("package-version-server"),
env: None,
arguments: Default::default(),
})

View File

@@ -200,7 +200,6 @@ impl LspAdapter for TypeScriptLspAdapter {
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({
"provideFormatter": true,
"hostInfo": "zed",
"tsserver": {
"path": "node_modules/typescript/lib",
},

View File

@@ -162,42 +162,47 @@ impl LspAdapter for VtslsLspAdapter {
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({
"typescript": {
"typescript":
{
"tsdk": "node_modules/typescript/lib",
"format": {
"enable": true
},
"inlayHints": {
"parameterNames": {
"inlayHints":{
"parameterNames":
{
"enabled": "all",
"suppressWhenArgumentMatchesName": false,
},
"parameterTypes": {
"parameterTypes":
{
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": false,
},
"propertyDeclarationTypes": {
"propertyDeclarationTypes":{
"enabled": true,
},
"functionLikeReturnTypes": {
"enabled": true,
},
"enumMemberValues": {
"enumMemberValues":{
"enabled": true,
}
}
},
"vtsls": {
"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
"vtsls":
{"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
}
}
})))
}
@@ -215,37 +220,41 @@ impl LspAdapter for VtslsLspAdapter {
"format": {
"enable": true
},
"inlayHints": {
"parameterNames": {
"inlayHints":{
"parameterNames":
{
"enabled": "all",
"suppressWhenArgumentMatchesName": false,
},
"parameterTypes": {
"parameterTypes":
{
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": false,
},
"propertyDeclarationTypes": {
"propertyDeclarationTypes":{
"enabled": true,
},
"functionLikeReturnTypes": {
"enabled": true,
},
"enumMemberValues": {
"enumMemberValues":{
"enabled": true,
}
}
}
},
"vtsls": {
"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
"vtsls":
{"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
}
}
}))
}

View File

@@ -55,8 +55,6 @@ pub enum Model {
#[serde(rename = "gpt-4o", alias = "gpt-4o-2024-05-13")]
#[default]
FourOmni,
#[serde(rename = "custom")]
Custom { name: String, max_tokens: usize },
}
impl Model {
@@ -76,17 +74,15 @@ impl Model {
Self::Four => "gpt-4",
Self::FourTurbo => "gpt-4-turbo-preview",
Self::FourOmni => "gpt-4o",
Self::Custom { .. } => "custom",
}
}
pub fn display_name(&self) -> &str {
pub fn display_name(&self) -> &'static str {
match self {
Self::ThreePointFiveTurbo => "gpt-3.5-turbo",
Self::Four => "gpt-4",
Self::FourTurbo => "gpt-4-turbo",
Self::FourOmni => "gpt-4o",
Self::Custom { name, .. } => name,
}
}
@@ -96,24 +92,12 @@ impl Model {
Model::Four => 8192,
Model::FourTurbo => 128000,
Model::FourOmni => 128000,
Model::Custom { max_tokens, .. } => *max_tokens,
}
}
}
fn serialize_model<S>(model: &Model, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match model {
Model::Custom { name, .. } => serializer.serialize_str(name),
_ => serializer.serialize_str(model.id()),
}
}
#[derive(Debug, Serialize)]
pub struct Request {
#[serde(serialize_with = "serialize_model")]
pub model: Model,
pub messages: Vec<RequestMessage>,
pub stream: bool,

View File

@@ -272,7 +272,6 @@ pub enum Event {
#[derive(Serialize, Deserialize)]
struct SerializedOutlinePanel {
width: Option<Pixels>,
active: Option<bool>,
}
pub fn init_settings(cx: &mut AppContext) {
@@ -313,7 +312,6 @@ impl OutlinePanel {
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|px| px.round());
panel.active = serialized_panel.active.unwrap_or(false);
cx.notify();
});
}
@@ -409,13 +407,12 @@ impl OutlinePanel {
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
let width = self.width;
let active = Some(self.active);
self.pending_serialization = cx.background_executor().spawn(
async move {
KEY_VALUE_STORE
.write_kvp(
OUTLINE_PANEL_KEY.into(),
serde_json::to_string(&SerializedOutlinePanel { width, active })?,
serde_json::to_string(&SerializedOutlinePanel { width })?,
)
.await?;
anyhow::Ok(())
@@ -2525,7 +2522,7 @@ impl Panel for OutlinePanel {
}
fn starts_open(&self, _: &WindowContext) -> bool {
self.active
self.active_item.is_some()
}
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
@@ -2554,7 +2551,6 @@ impl Panel for OutlinePanel {
}
}
}
self.serialize(cx);
}
}

View File

@@ -317,14 +317,11 @@ impl Prettier {
})
.collect();
let mut prettier_parser = prettier_settings.parser.as_deref();
if buffer_path.is_none() {
prettier_parser = prettier_parser.or_else(|| buffer_language.and_then(|language| language.prettier_parser_name()));
if prettier_parser.is_none() {
log::error!("Formatting unsaved file with prettier failed. No prettier parser configured for language {buffer_language:?}");
return Err(anyhow!("Cannot determine prettier parser for unsaved file"));
}
let prettier_parser = prettier_settings.parser.as_deref().or_else(|| buffer_language.and_then(|language| language.prettier_parser_name()));
if prettier_parser.is_none() && buffer_path.is_none() {
log::error!("Formatting unsaved file with prettier failed. No prettier parser configured for language {buffer_language:?}");
return Err(anyhow!("Cannot determine prettier parser for unsaved file"));
}
log::debug!(

View File

@@ -8192,7 +8192,7 @@ impl Project {
}
};
if path.ends_with(local_settings_file_relative_path()) {
if abs_path.ends_with(local_settings_file_relative_path()) {
let settings_dir = Arc::from(
path.ancestors()
.nth(local_settings_file_relative_path().components().count())
@@ -8209,7 +8209,7 @@ impl Project {
},
)
});
} else if path.ends_with(local_tasks_file_relative_path()) {
} else if abs_path.ends_with(local_tasks_file_relative_path()) {
self.task_inventory().update(cx, |task_inventory, cx| {
if removed {
task_inventory.remove_local_static_source(&abs_path);
@@ -8229,7 +8229,7 @@ impl Project {
);
}
})
} else if path.ends_with(local_vscode_tasks_file_relative_path()) {
} else if abs_path.ends_with(local_vscode_tasks_file_relative_path()) {
self.task_inventory().update(cx, |task_inventory, cx| {
if removed {
task_inventory.remove_local_static_source(&abs_path);

View File

@@ -5,7 +5,6 @@ use std::{fmt::Display, sync::Arc, time::Duration};
#[derive(Serialize, Deserialize, Debug)]
pub struct EventRequestBody {
pub installation_id: Option<String>,
pub metrics_id: Option<String>,
pub session_id: Option<String>,
pub is_staff: Option<bool>,
pub app_version: String,

View File

@@ -1,9 +1,7 @@
use gpui::{ClickEvent, CursorStyle, WindowContext};
use gpui::{ClickEvent, WindowContext};
/// A trait for elements that can be clicked. Enables the use of the `on_click` method.
pub trait Clickable {
/// Sets the click handler that will fire whenever the element is clicked.
fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self;
/// Sets the cursor style when hovering over the element.
fn cursor_style(self, cursor_style: CursorStyle) -> Self;
}

View File

@@ -249,11 +249,6 @@ impl Clickable for Button {
self.base = self.base.on_click(handler);
self
}
fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
self.base = self.base.cursor_style(cursor_style);
self
}
}
impl FixedWidth for Button {

View File

@@ -1,4 +1,4 @@
use gpui::{relative, CursorStyle, DefiniteLength, MouseButton};
use gpui::{relative, DefiniteLength, MouseButton};
use gpui::{transparent_black, AnyElement, AnyView, ClickEvent, Hsla, Rems};
use smallvec::SmallVec;
@@ -344,7 +344,6 @@ pub struct ButtonLike {
size: ButtonSize,
rounding: Option<ButtonLikeRounding>,
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
cursor_style: CursorStyle,
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
children: SmallVec<[AnyElement; 2]>,
}
@@ -364,7 +363,6 @@ impl ButtonLike {
rounding: Some(ButtonLikeRounding::All),
tooltip: None,
children: SmallVec::new(),
cursor_style: CursorStyle::PointingHand,
on_click: None,
layer: None,
}
@@ -407,11 +405,6 @@ impl Clickable for ButtonLike {
self.on_click = Some(Box::new(handler));
self
}
fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
self.cursor_style = cursor_style;
self
}
}
impl FixedWidth for ButtonLike {

View File

@@ -86,11 +86,6 @@ impl Clickable for IconButton {
self.base = self.base.on_click(handler);
self
}
fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
self.base = self.base.cursor_style(cursor_style);
self
}
}
impl FixedWidth for IconButton {

View File

@@ -82,11 +82,6 @@ impl Clickable for ToggleButton {
self.base = self.base.on_click(handler);
self
}
fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
self.base = self.base.cursor_style(cursor_style);
self
}
}
impl ButtonCommon for ToggleButton {

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use gpui::{ClickEvent, CursorStyle};
use gpui::ClickEvent;
use crate::{prelude::*, Color, IconButton, IconButtonShape, IconName, IconSize};
@@ -10,7 +10,6 @@ pub struct Disclosure {
is_open: bool,
selected: bool,
on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
cursor_style: CursorStyle,
}
impl Disclosure {
@@ -20,7 +19,6 @@ impl Disclosure {
is_open,
selected: false,
on_toggle: None,
cursor_style: CursorStyle::PointingHand,
}
}
@@ -45,11 +43,6 @@ impl Clickable for Disclosure {
self.on_toggle = Some(Arc::new(handler));
self
}
fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
self.cursor_style = cursor_style;
self
}
}
impl RenderOnce for Disclosure {

View File

@@ -97,9 +97,6 @@ pub enum IconName {
BellOff,
BellRing,
Bolt,
Book,
BookCopy,
BookPlus,
CaseSensitive,
Check,
ChevronDown,
@@ -234,9 +231,6 @@ impl IconName {
IconName::BellOff => "icons/bell_off.svg",
IconName::BellRing => "icons/bell_ring.svg",
IconName::Bolt => "icons/bolt.svg",
IconName::Book => "icons/book.svg",
IconName::BookCopy => "icons/book_copy.svg",
IconName::BookPlus => "icons/book_plus.svg",
IconName::CaseSensitive => "icons/case_insensitive.svg",
IconName::Check => "icons/check.svg",
IconName::ChevronDown => "icons/chevron_down.svg",

View File

@@ -30,7 +30,7 @@ impl KeyBinding {
Some(Self::new(key_binding))
}
fn icon_for_key(&self, keystroke: &Keystroke) -> Option<IconName> {
fn icon_for_key(keystroke: &Keystroke) -> Option<IconName> {
match keystroke.key.as_str() {
"left" => Some(IconName::ArrowLeft),
"right" => Some(IconName::ArrowRight),
@@ -45,11 +45,6 @@ impl KeyBinding {
"escape" => Some(IconName::Escape),
"pagedown" => Some(IconName::PageDown),
"pageup" => Some(IconName::PageUp),
"shift" if self.platform_style == PlatformStyle::Mac => Some(IconName::Shift),
"control" if self.platform_style == PlatformStyle::Mac => Some(IconName::Control),
"platform" if self.platform_style == PlatformStyle::Mac => Some(IconName::Command),
"function" if self.platform_style == PlatformStyle::Mac => Some(IconName::Control),
"alt" if self.platform_style == PlatformStyle::Mac => Some(IconName::Option),
_ => None,
}
}
@@ -85,7 +80,7 @@ impl RenderOnce for KeyBinding {
.gap(Spacing::Small.rems(cx))
.flex_none()
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
let key_icon = self.icon_for_key(keystroke);
let key_icon = Self::icon_for_key(keystroke);
h_flex()
.flex_none()

View File

@@ -11,25 +11,6 @@ impl WindowsWindowControls {
pub fn new(button_height: Pixels) -> Self {
Self { button_height }
}
#[cfg(not(target_os = "windows"))]
fn get_font() -> &'static str {
"Segoe Fluent Icons"
}
#[cfg(target_os = "windows")]
fn get_font() -> &'static str {
use windows::Wdk::System::SystemServices::RtlGetVersion;
let mut version = unsafe { std::mem::zeroed() };
let status = unsafe { RtlGetVersion(&mut version) };
if status.is_ok() && version.dwBuildNumber >= 22000 {
"Segoe Fluent Icons"
} else {
"Segoe MDL2 Assets"
}
}
}
impl RenderOnce for WindowsWindowControls {
@@ -58,7 +39,6 @@ impl RenderOnce for WindowsWindowControls {
div()
.id("windows-window-controls")
.font_family(Self::get_font())
.flex()
.flex_row()
.justify_center()
@@ -130,6 +110,7 @@ impl RenderOnce for WindowsCaptionButton {
.content_center()
.w(width)
.h_full()
.font_family("Segoe Fluent Icons")
.text_size(px(10.0))
.hover(|style| style.bg(self.hover_background_color))
.active(|style| {

View File

@@ -3826,8 +3826,7 @@ impl BackgroundScanner {
.await;
// Ensure that .git and .gitignore are processed first.
swap_to_front(&mut child_paths, *GITIGNORE);
swap_to_front(&mut child_paths, *DOT_GIT);
child_paths.sort_unstable();
for child_abs_path in child_paths {
let child_abs_path: Arc<Path> = child_abs_path.into();
@@ -4621,16 +4620,6 @@ impl BackgroundScanner {
}
}
fn swap_to_front(child_paths: &mut Vec<PathBuf>, file: &OsStr) {
let position = child_paths
.iter()
.position(|path| path.file_name().unwrap() == file);
if let Some(position) = position {
let temp = child_paths.remove(position);
child_paths.insert(0, temp);
}
}
fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
let mut result = root_char_bag;
result.extend(

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.142.4"
version = "0.142.0"
publish = false
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -1 +1 @@
stable
dev

View File

@@ -56,11 +56,11 @@ use crate::zed::inline_completion_registry;
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn fail_to_launch(e: anyhow::Error) {
eprintln!("Zed failed to launch: {e:?}");
eprintln!("Zed failed to launch: {:?}", e);
App::new().run(move |cx| {
if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty)) {
window.update(cx, |_, cx| {
let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{e}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed")), &["Exit"]);
let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed", e)), &["Exit"]);
cx.spawn(|_, mut cx| async move {
response.await?;
@@ -80,7 +80,7 @@ fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncAppContext) {
}
fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
eprintln!("Zed failed to open a window: {e:?}");
eprintln!("Zed failed to open a window: {:?}", e);
#[cfg(not(target_os = "linux"))]
{
process::exit(1);
@@ -99,7 +99,7 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
.add_notification(
notification_id,
Notification::new("Zed failed to launch")
.body(Some(format!("{e:?}").as_str()))
.body(Some(format!("{:?}", e).as_str()))
.priority(Priority::High)
.icon(ashpd::desktop::Icon::with_names(&[
"dialog-question-symbolic",
@@ -751,7 +751,7 @@ fn init_stdout_logger() {
)?;
write!(buf, "{:<5}", buf.default_styled_level(record.level()))?;
if let Some(path) = record.module_path() {
write!(buf, " {path}")?;
write!(buf, " {}", path)?;
}
write!(buf, "{}", subtle.value("]"))?;
writeln!(buf, " {}", record.args())

View File

@@ -113,14 +113,14 @@ pub fn init_panic_hook(
if !is_pty {
if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
let panic_file_path = paths::logs_dir().join(format!("zed-{timestamp}.panic"));
let panic_file_path = paths::logs_dir().join(format!("zed-{}.panic", timestamp));
let panic_file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(&panic_file_path)
.log_err();
if let Some(mut panic_file) = panic_file {
writeln!(&mut panic_file, "{panic_data_json}").log_err();
writeln!(&mut panic_file, "{}", panic_data_json).log_err();
panic_file.flush().log_err();
}
}
@@ -494,7 +494,7 @@ async fn upload_previous_crashes(
if let Some((panicked_on, payload)) = most_recent_panic.as_ref() {
request = request
.header("x-zed-panicked-on", format!("{panicked_on}"))
.header("x-zed-panicked-on", format!("{}", panicked_on))
.header("x-zed-panic", payload)
}
if let Some(installation_id) = installation_id.as_ref() {

View File

@@ -105,10 +105,10 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut AppContext) ->
display_id: display.map(|display| display.id()),
window_background: cx.theme().window_background_appearance(),
app_id: Some(app_id.to_owned()),
window_min_size: Some(gpui::Size {
window_min_size: gpui::Size {
width: px(360.0),
height: px(240.0),
}),
},
}
}

View File

@@ -247,7 +247,7 @@ pub async fn handle_cli_connection(
Err(error) => {
responses
.send(CliResponse::Stderr {
message: format!("{error}"),
message: format!("{}", error),
})
.log_err();
responses.send(CliResponse::Exit { status: 1 }).log_err();
@@ -263,7 +263,7 @@ pub async fn handle_cli_connection(
{
responses
.send(CliResponse::Stderr {
message: format!("{e}"),
message: format!("{}", e),
})
.log_err();
responses.send(CliResponse::Exit { status: 1 }).log_err();
@@ -342,7 +342,10 @@ pub async fn handle_cli_connection(
Some(Err(err)) => {
responses
.send(CliResponse::Stderr {
message: format!("error opening {path:?}: {err}"),
message: format!(
"error opening {:?}: {}",
path, err
),
})
.log_err();
errored = true;
@@ -389,7 +392,7 @@ pub async fn handle_cli_connection(
errored = true;
responses
.send(CliResponse::Stderr {
message: format!("error opening {paths:?}: {error}"),
message: format!("error opening {:?}: {}", paths, error),
})
.log_err();
}

View File

@@ -1,3 +1,5 @@
version: "3.7"
services:
postgres:
image: postgres:15

View File

@@ -2,7 +2,7 @@
## Folder-specific settings
Folder-specific settings are used to override Zed's global settings for files within a specific directory in the project panel. To get started, create a `.zed` subdirectory and add a `settings.json` within it. It should be noted that folder-specific settings don't need to live only at a project's root, but can be defined at multiple levels in the project hierarchy. In setups like this, Zed will find the configuration nearest to the file you are working in and apply those settings to it. In most cases, this level of flexibility won't be needed and a single configuration for all files in a project is all that is required; the `Zed > Settings > Open Local Settings` menu action is built for this case. Running this action will look for a `.zed/settings.json` file at the root of the first top-level directory in your project panel. If it does not exist, it will create it.
Folder-specific settings are used to override Zed's global settings for files within a specific directory in the project panel. To get started, create a `.zed` subdirectory and add a `settings.json` within it. It should be noted that folder-specific settings don't need to live only a project's root, but can be defined at multiple levels in the project hierarchy. In setups like this, Zed will find the configuration nearest to the file you are working in and apply those settings to it. In most cases, this level of flexibility won't be needed and a single configuration for all files in a project is all that is required; the `Zed > Settings > Open Local Settings` menu action is built for this case. Running this action will look for a `.zed/settings.json` file at the root of the first top-level directory in your project panel. If it does not exist, it will create it.
The following global settings can be overridden with a folder-specific configuration:
@@ -46,7 +46,7 @@ Here are all the currently available settings.
**Options**
1. To disable autosave, set it to `off`:
1. To disable autosave, set it to `off`
```json
{
@@ -110,9 +110,9 @@ The name of any font family installed on the user's system
**Options**
Zed supports all OpenType features that can be enabled or disabled for a given buffer or terminal font, as well as setting values for font features.
Zed supports all OpenType features that can be enabled, disabled or set a value to a font feature for a given buffer or terminal font.
For example, to disable ligatures and set `cv01` to `7` for a given font you can add the following to your settings:
For example, to disable ligatures and set `7` to `cv01` for a given font you can add the following to your settings:
```json
{
@@ -201,19 +201,19 @@ List of `string` values
"current_line_highlight": "none"
```
2. Highlight the gutter area:
2. Highlight the gutter area.
```json
"current_line_highlight": "gutter"
```
3. Highlight the editor area:
3. Highlight the editor area.
```json
"current_line_highlight": "line"
```
4. Highlight the full line:
4. Highlight the full line.
```json
"current_line_highlight": "all"

View File

@@ -50,12 +50,12 @@ Zed has the ability to match against not just a single keypress, but a sequence
Each key press is a sequence of modifiers followed by a key. The modifiers are:
- `ctrl-` The control key
* `cmd-`, `win-` or `super-` for the platform modifier (Command on macOS, Windows key on Windows, and the Super key on Linux).
- `alt-` for alt (option on macOS)
- `cmd-` On macOS, this is the command key
- `alt-` On macOS, this is the option key
- `shift-` The shift key
- `fn-` The function key
The keys can be any single unicode codepoint that your keyboard generates (for example `a`, `0`, `£` or `ç`), or any named key (`tab`, `f1`, `shift`, or `cmd`).
The keys can be any single unicode codepoint that your keyboard generates (for example `a`, `0`, `£` or `ç`).
A few examples:
@@ -64,15 +64,10 @@ A few examples:
"cmd-k cmd-s": "zed::OpenKeymap", // matches ⌘-k then ⌘-s
"space e": "editor::Complete", // type space then e
"ç": "editor::Complete", // matches ⌥-c
"shift shift": "file_finder::Toggle", // matches pressing and releasing shift twice
}
```
The `shift-` modifier can only be used in combination with a letter to indicate the uppercase version. For example `shift-g` matches typing `G`. Although on many keyboards shift is used to type punctuation characters like `(`, the keypress is not considered to be modified and so `shift-(` does not match.
The `alt-` modifier can be used on many layouts to generate a different key. For example on macOS US keyboard the combination `alt-c` types `ç`. You can match against either in your keymap file, though by convention Zed spells this combination as `alt-c`.
It is possible to match against typing a modifier key on its own. For example `shift shift` can be used to implement JetBrains search everywhere shortcut. In this case the binding happens on key release instead of key press.
NOTE: Keys on a keyboard are not always the same as the character they generate. For example `shift-e` actually types `E` (or `alt-c` types `ç`). Zed allows you to match against either the key and its modifiers or the character it generates. This means you can specify `alt-c` or `ç`, but not `alt-ç`. It is usually better to specify the key and its modifiers, as this will work better on different keyboard layouts.
### Remapping keys
@@ -160,7 +155,7 @@ See the [tasks documentation](/docs/tasks#custom-keybindings-for-tasks) for more
| Open | Workspace | `⌘ + O` |
| Toggle zoom | Workspace | `Shift + Escape` |
| Debug elements | Zed | `⌘ + Alt + I` |
| Decrease buffer font size | Zed | `⌘ + -` |
| Decrease buffer font size | Zed | `⌘ + ` |
| Hide | Zed | `⌘ + H` |
| Hide others | Zed | `Alt + ⌘ + H` |
| Increase buffer font size | Zed | `⌘ + +` |