mirror of
https://github.com/zed-industries/zed.git
synced 2026-07-17 11:52:13 +00:00
Compare commits
No commits in common. "main" and "collab-staging" have entirely different histories.
main
...
collab-sta
34 changed files with 185 additions and 805 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -58,5 +58,3 @@ crates/docs_preprocessor/actions.json
|
|||
|
||||
# NixOS integration test state
|
||||
.nixos-test-history
|
||||
|
||||
.local*
|
||||
|
|
|
|||
5
Cargo.lock
generated
5
Cargo.lock
generated
|
|
@ -12560,7 +12560,6 @@ dependencies = [
|
|||
"lsp",
|
||||
"menu",
|
||||
"picker",
|
||||
"picker_preview",
|
||||
"project",
|
||||
"rope",
|
||||
"serde_json",
|
||||
|
|
@ -18275,9 +18274,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "taffy"
|
||||
version = "0.12.2"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "340a09581f29809fc0df82a3955501dc7f2a21f887e5d1c13dbe288fe1c0bef4"
|
||||
checksum = "73afc801dd6bd47529eaa7c7e90557f107527d1b7c9c7ed7d7803c7b8d0c357f"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"grid",
|
||||
|
|
|
|||
|
|
@ -387,11 +387,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#d2a6ffff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#5ac1feff",
|
||||
"font_style": null,
|
||||
|
|
@ -794,11 +789,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#a37accff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#3b9ee5ff",
|
||||
"font_style": null,
|
||||
|
|
@ -1201,11 +1191,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#dfbfffff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#72cffeff",
|
||||
"font_style": null,
|
||||
|
|
|
|||
|
|
@ -397,11 +397,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
|
|
@ -819,11 +814,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
|
|
@ -1241,11 +1231,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
|
|
@ -1663,11 +1648,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#076678ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
|
|
@ -2085,11 +2065,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#076678ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
|
|
@ -2507,11 +2482,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#076678ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
|
|
|
|||
|
|
@ -394,11 +394,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#d07277ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#bf956aff",
|
||||
"font_style": null,
|
||||
|
|
@ -811,11 +806,6 @@
|
|||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#d3604fff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#ad6e25ff",
|
||||
"font_style": null,
|
||||
|
|
|
|||
|
|
@ -2223,7 +2223,7 @@ fn diagnostics_crease_label(
|
|||
diagnostics_label(summary, include_errors, include_warnings).into()
|
||||
}
|
||||
|
||||
pub(crate) fn pluralize(noun: &str, count: usize) -> String {
|
||||
fn pluralize(noun: &str, count: usize) -> String {
|
||||
if count == 1 {
|
||||
noun.to_string()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use cloud_api_types::{SubmitAgentThreadFeedbackBody, SubmitAgentThreadFeedbackCo
|
|||
use editor::actions::OpenExcerpts;
|
||||
use sandbox::{SandboxFsPolicy, SandboxNetPolicy, SandboxPolicy};
|
||||
|
||||
use crate::completion_provider::{AvailableSkill, PromptLocalCommand, pluralize};
|
||||
use crate::completion_provider::{AvailableSkill, PromptLocalCommand};
|
||||
use crate::message_editor::SharedSessionCapabilities;
|
||||
use crate::ui::{
|
||||
SandboxGroup, SandboxRow, SandboxSection, SandboxStatusTooltip, TerminalSandboxWarning,
|
||||
|
|
@ -5780,12 +5780,8 @@ impl Render for TokenUsageTooltip {
|
|||
Button::new(
|
||||
"open-project-rules",
|
||||
format!(
|
||||
"{} {}",
|
||||
project_rules_count,
|
||||
pluralize(
|
||||
"project rule",
|
||||
project_rules_count
|
||||
)
|
||||
"{} project rules",
|
||||
project_rules_count
|
||||
),
|
||||
)
|
||||
.end_icon(
|
||||
|
|
|
|||
|
|
@ -49,10 +49,6 @@ pub struct CsvPreviewView {
|
|||
/// Performance metrics for debugging and monitoring CSV operations.
|
||||
pub(crate) performance_metrics: PerformanceMetrics,
|
||||
pub(crate) list_state: gpui::ListState,
|
||||
/// Cached row height, refreshed from the actual text line height on every render.
|
||||
/// Used to size not-yet-rendered rows for the scrollbar without a full `.measure_all()`
|
||||
/// pass, so it tracks the real row height instead of a hardcoded guess.
|
||||
pub(crate) row_height: Pixels,
|
||||
/// Time when the last parsing operation ended, used for smart debouncing
|
||||
pub(crate) last_parse_end_time: Option<std::time::Instant>,
|
||||
}
|
||||
|
|
@ -98,7 +94,7 @@ impl CsvPreviewView {
|
|||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
.filter(|editor| Self::is_csv_file(editor, cx))
|
||||
{
|
||||
let csv_preview = Self::new(&editor, window, cx);
|
||||
let csv_preview = Self::new(&editor, cx);
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
let existing = pane
|
||||
.items_of_type::<CsvPreviewView>()
|
||||
|
|
@ -119,7 +115,7 @@ impl CsvPreviewView {
|
|||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
.filter(|editor| Self::is_csv_file(editor, cx))
|
||||
{
|
||||
let csv_preview = Self::new(&editor, window, cx);
|
||||
let csv_preview = Self::new(&editor, cx);
|
||||
let pane = workspace
|
||||
.find_pane_in_direction(SplitDirection::Right, cx)
|
||||
.unwrap_or_else(|| {
|
||||
|
|
@ -156,7 +152,7 @@ impl CsvPreviewView {
|
|||
});
|
||||
}
|
||||
|
||||
fn new(editor: &Entity<Editor>, window: &Window, cx: &mut Context<Workspace>) -> Entity<Self> {
|
||||
fn new(editor: &Entity<Editor>, cx: &mut Context<Workspace>) -> Entity<Self> {
|
||||
let contents = TableLikeContent::default();
|
||||
let table_interaction_state = cx.new(|cx| {
|
||||
TableInteractionState::new(cx).with_custom_scrollbar(ui::Scrollbars::for_settings::<
|
||||
|
|
@ -177,7 +173,6 @@ impl CsvPreviewView {
|
|||
},
|
||||
);
|
||||
|
||||
let row_height = window.pixel_snap(window.line_height());
|
||||
let mut view = CsvPreviewView {
|
||||
focus_handle: cx.focus_handle(),
|
||||
active_editor_state: EditorState {
|
||||
|
|
@ -191,8 +186,7 @@ impl CsvPreviewView {
|
|||
filter_sort_task: None,
|
||||
performance_metrics: PerformanceMetrics::default(),
|
||||
list_state: gpui::ListState::new(contents.rows.len(), ListAlignment::Top, px(1.))
|
||||
.with_uniform_item_height(row_height),
|
||||
row_height,
|
||||
.with_uniform_item_height(px(24.)),
|
||||
settings: CsvPreviewSettings::default(),
|
||||
last_parse_end_time: None,
|
||||
engine: TableDataEngine::default(),
|
||||
|
|
@ -245,10 +239,11 @@ impl CsvPreviewView {
|
|||
this.update(cx, |view, cx| {
|
||||
view.engine.set_d2d_mapping(mapping);
|
||||
let visible_rows = view.engine.d2d_mapping().visible_row_count();
|
||||
// Uses the row height measured on the last render. Cheaper than a full
|
||||
// `.measure_all()` pass; exact row heights are re-measured on scrolling.
|
||||
// Approximation of single csv table row height. Will be re-measured on scrolling.
|
||||
// This cheap solution allow to render scrollbar with fraction of a cost compared to `.measure_all()` call
|
||||
let approximate_height = px(24.);
|
||||
view.list_state
|
||||
.reset_with_uniform_height(visible_rows, view.row_height);
|
||||
.reset_with_uniform_height(visible_rows, approximate_height);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
|
|
|
|||
|
|
@ -7,15 +7,7 @@ use crate::CsvPreviewView;
|
|||
impl Render for CsvPreviewView {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
let row_height = window.pixel_snap(window.line_height());
|
||||
if row_height != self.row_height {
|
||||
self.row_height = row_height;
|
||||
// Font size (rem size, buffer font override, ...) changed since the list was last
|
||||
// measured: existing rows and unmeasured-item height hints are now the wrong size.
|
||||
// Unlike `reset_with_uniform_height`, this preserves scroll position and keeps each
|
||||
// item's prior size as a hint rather than dropping straight to a fresh guess.
|
||||
self.list_state.remeasure();
|
||||
}
|
||||
|
||||
let render_prep_start = Instant::now();
|
||||
let table_with_settings = v_flex()
|
||||
.size_full()
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ impl CsvPreviewView {
|
|||
cols,
|
||||
display_row,
|
||||
row_identifier_text_color,
|
||||
this.row_height,
|
||||
cx,
|
||||
)
|
||||
.unwrap_or_else(|| panic!("Expected to render a table row"))
|
||||
|
|
@ -84,7 +83,6 @@ impl CsvPreviewView {
|
|||
.rendered_indices
|
||||
.extend(range.clone());
|
||||
|
||||
let row_height = this.row_height;
|
||||
range
|
||||
.filter_map(|display_index| {
|
||||
Self::render_single_table_row(
|
||||
|
|
@ -92,7 +90,6 @@ impl CsvPreviewView {
|
|||
cols,
|
||||
DisplayRow(display_index),
|
||||
row_identifier_text_color,
|
||||
row_height,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
|
|
@ -113,7 +110,6 @@ impl CsvPreviewView {
|
|||
cols: usize,
|
||||
display_row: DisplayRow,
|
||||
row_identifier_text_color: gpui::Hsla,
|
||||
row_height: Pixels,
|
||||
cx: &Context<CsvPreviewView>,
|
||||
) -> Option<UncheckedTableRow<AnyElement>> {
|
||||
// Get the actual row index from our sorted indices
|
||||
|
|
@ -132,23 +128,14 @@ impl CsvPreviewView {
|
|||
|
||||
let display_cell_id = DisplayCellId::new(display_row, col);
|
||||
|
||||
let cell = div()
|
||||
.size_full()
|
||||
.when(
|
||||
!this.settings.multiline_cells_effectively_enabled(),
|
||||
|div| {
|
||||
div.whitespace_nowrap()
|
||||
.text_ellipsis()
|
||||
.h(row_height)
|
||||
.overflow_hidden()
|
||||
},
|
||||
)
|
||||
.child(CsvPreviewView::create_selectable_cell(
|
||||
let cell = div().size_full().whitespace_nowrap().text_ellipsis().child(
|
||||
CsvPreviewView::create_selectable_cell(
|
||||
display_cell_id,
|
||||
cell_content,
|
||||
this.settings.vertical_alignment,
|
||||
cx,
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
elements.push(
|
||||
div()
|
||||
|
|
@ -167,6 +154,7 @@ impl CsvPreviewView {
|
|||
},
|
||||
))
|
||||
})
|
||||
.text_ui(cx)
|
||||
.child(cell)
|
||||
.into_any_element(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use ui::{
|
||||
ActiveTheme as _, AnyElement, Button, ButtonCommon as _, ButtonSize, ButtonStyle,
|
||||
Clickable as _, Context, ElementId, IntoElement as _, ParentElement as _, SharedString,
|
||||
Styled as _, StyledTypography as _, Tooltip, div,
|
||||
Clickable as _, Context, ElementId, FluentBuilder as _, IntoElement as _, ParentElement as _,
|
||||
SharedString, Styled as _, StyledTypography as _, Tooltip, div,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
CsvPreviewView,
|
||||
settings::RowIdentifiers,
|
||||
settings::{RowIdentifiers, VerticalAlignment},
|
||||
types::{DataRow, DisplayRow, LineNumber},
|
||||
};
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ impl LineNumber {
|
|||
if start + 1 == end {
|
||||
format!("{start}\n{end}")
|
||||
} else {
|
||||
format!("{start}\n-\n{end}")
|
||||
format!("{start}\n...\n{end}")
|
||||
}
|
||||
}
|
||||
RowIdentDisplayMode::Horizontal => {
|
||||
|
|
@ -78,6 +78,11 @@ impl CsvPreviewView {
|
|||
(max_line_number as f32).log10().floor() as usize + 1
|
||||
};
|
||||
|
||||
// if !self.settings.multiline_cells_enabled {
|
||||
// // Uses horizontal line numbers layout like `123-456`. Needs twice the size
|
||||
// digit_count *= 2;
|
||||
// }
|
||||
|
||||
let char_width_px = 9.0; // TODO: get real width of the characters
|
||||
let base_width = (digit_count as f32) * char_width_px;
|
||||
let padding = 20.0;
|
||||
|
|
@ -152,7 +157,7 @@ impl CsvPreviewView {
|
|||
.contents
|
||||
.line_numbers
|
||||
.get(*data_row)?
|
||||
.display_string(if self.settings.multiline_cells_effectively_enabled() {
|
||||
.display_string(if self.settings.multiline_cells_enabled {
|
||||
RowIdentDisplayMode::Vertical
|
||||
} else {
|
||||
RowIdentDisplayMode::Horizontal
|
||||
|
|
@ -167,11 +172,14 @@ impl CsvPreviewView {
|
|||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.h_full()
|
||||
.text_ui(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.map(|div| match self.settings.vertical_alignment {
|
||||
VerticalAlignment::Top => div.items_start(),
|
||||
VerticalAlignment::Center => div.items_center(),
|
||||
})
|
||||
.font_buffer(cx)
|
||||
.text_ui(cx)
|
||||
.child(row_identifier)
|
||||
.into_any_element();
|
||||
Some(value)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use ui::{
|
||||
ActiveTheme as _, AnyElement, ButtonSize, Checkbox, Context, ContextMenu, DropdownMenu,
|
||||
ElementId, IntoElement as _, ParentElement as _, Styled as _, ToggleState, Tooltip, Window,
|
||||
div, h_flex,
|
||||
ActiveTheme as _, AnyElement, ButtonSize, Context, ContextMenu, DropdownMenu, ElementId,
|
||||
IntoElement as _, ParentElement as _, Styled as _, Tooltip, Window, div, h_flex,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -122,30 +121,6 @@ impl CsvPreviewView {
|
|||
),
|
||||
);
|
||||
|
||||
let multiline_enabled = self.settings.multiline_cells_enabled;
|
||||
let panel = panel.child({
|
||||
let view = view.clone();
|
||||
Checkbox::new(
|
||||
ElementId::Name("multiline-rows-checkbox".into()),
|
||||
if multiline_enabled {
|
||||
ToggleState::Selected
|
||||
} else {
|
||||
ToggleState::Unselected
|
||||
},
|
||||
)
|
||||
.label("Display multiline rows")
|
||||
.tooltip(Tooltip::text(
|
||||
"When enabled, row height grows to show all content. \
|
||||
When disabled, only the first line is visible — hover a cell to see the rest.",
|
||||
))
|
||||
.on_click(move |_state, _window, cx| {
|
||||
view.update(cx, |this, cx| {
|
||||
this.settings.multiline_cells_enabled = !this.settings.multiline_cells_enabled;
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
#[cfg(feature = "dev-tools")]
|
||||
let panel = panel.child(
|
||||
h_flex()
|
||||
|
|
@ -196,6 +171,7 @@ fn create_dev_only_popover_menu(
|
|||
view_entity.update(cx, |view, cx| {
|
||||
view.settings.rendering_with =
|
||||
RowRenderMechanism::VariableList;
|
||||
view.settings.multiline_cells_enabled = true;
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
|
@ -212,6 +188,7 @@ fn create_dev_only_popover_menu(
|
|||
view_entity.update(cx, |view, cx| {
|
||||
view.settings.rendering_with =
|
||||
RowRenderMechanism::UniformList;
|
||||
view.settings.multiline_cells_enabled = false;
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ impl CsvPreviewView {
|
|||
.w_full()
|
||||
.items_center()
|
||||
.font_buffer(cx)
|
||||
.text_buffer(cx)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
|
|
|
|||
|
|
@ -46,12 +46,3 @@ pub(crate) struct CsvPreviewSettings {
|
|||
pub(crate) show_perf_metrics_overlay: bool,
|
||||
pub(crate) multiline_cells_enabled: bool,
|
||||
}
|
||||
|
||||
impl CsvPreviewSettings {
|
||||
/// `multiline_cells_enabled` only makes sense with `VariableList`, which
|
||||
/// supports per-row heights; `UniformList` requires every row to share one
|
||||
/// height, so multiline is never honored there regardless of the setting.
|
||||
pub(crate) fn multiline_cells_effectively_enabled(&self) -> bool {
|
||||
self.multiline_cells_enabled && self.rendering_with == RowRenderMechanism::VariableList
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
pub use crate::commit_context_menu::{CopyCommitSha, CopyCommitTag, OpenCommitView};
|
||||
use crate::{
|
||||
commit_context_menu::{CommitContextMenuData, CommitContextMenuSource, commit_context_menu},
|
||||
commit_tooltip::CommitAvatar,
|
||||
commit_tooltip::{CommitAvatar, CommitDetails, CommitTooltip},
|
||||
commit_view::CommitView,
|
||||
git_status_icon,
|
||||
};
|
||||
|
|
@ -10,6 +10,7 @@ use editor::Editor;
|
|||
use file_icons::FileIcons;
|
||||
use git::{
|
||||
BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, Oid, ParsedGitRemote,
|
||||
commit::ParsedCommitMessage,
|
||||
parse_git_remote_url,
|
||||
repository::{
|
||||
CommitDiff, CommitFile, InitialGraphCommitData, LogOrder, LogSource, RepoPath,
|
||||
|
|
@ -1693,6 +1694,10 @@ impl GitGraph {
|
|||
git_store.repositories().get(&self.repo_id).cloned()
|
||||
}
|
||||
|
||||
fn has_context_menu(&self) -> bool {
|
||||
self.context_menu.is_some()
|
||||
}
|
||||
|
||||
/// Checks whether a ref name from git's `%D` decoration
|
||||
/// format refers to the currently checked-out branch.
|
||||
fn is_head_ref(ref_name: &str, head_branch_name: &Option<SharedString>) -> bool {
|
||||
|
|
@ -1786,6 +1791,7 @@ impl GitGraph {
|
|||
});
|
||||
|
||||
let row_height = Self::row_height(window, cx);
|
||||
let has_context_menu = self.has_context_menu();
|
||||
|
||||
// We fetch data outside the visible viewport to avoid loading entries when
|
||||
// users scroll through the git graph
|
||||
|
|
@ -1893,6 +1899,55 @@ impl GitGraph {
|
|||
div()
|
||||
.id(ElementId::NamedInteger("commit-subject".into(), idx as u64))
|
||||
.overflow_hidden()
|
||||
.when(!has_context_menu, |this| {
|
||||
if let CommitDataState::Loaded(commit_data) = &data {
|
||||
let sha = commit.data.sha.to_string();
|
||||
let author_name = commit_data.author_name.clone();
|
||||
let author_email = commit_data.author_email.clone();
|
||||
let message = commit_data.message.clone();
|
||||
let commit_timestamp = commit_data.commit_timestamp;
|
||||
let tag_names = commit
|
||||
.data
|
||||
.tag_names()
|
||||
.into_iter()
|
||||
.map(SharedString::from)
|
||||
.collect::<Vec<_>>();
|
||||
let workspace = self.workspace.clone();
|
||||
let repository = repository.clone();
|
||||
this.hoverable_tooltip(move |_window, cx| {
|
||||
let remote_url = repository.read(cx).default_remote_url();
|
||||
let provider_registry =
|
||||
GitHostingProviderRegistry::default_global(cx);
|
||||
let commit_details = CommitDetails {
|
||||
sha: sha.clone().into(),
|
||||
author_name: author_name.clone(),
|
||||
author_email: author_email.clone(),
|
||||
commit_time: OffsetDateTime::from_unix_timestamp(
|
||||
commit_timestamp,
|
||||
)
|
||||
.unwrap_or_else(|_| OffsetDateTime::now_utc()),
|
||||
message: Some(ParsedCommitMessage::parse(
|
||||
sha.clone(),
|
||||
message.to_string(),
|
||||
remote_url.as_deref(),
|
||||
Some(provider_registry),
|
||||
)),
|
||||
tag_names: tag_names.clone(),
|
||||
};
|
||||
cx.new(|cx| {
|
||||
CommitTooltip::new(
|
||||
commit_details,
|
||||
repository.clone(),
|
||||
workspace.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.into()
|
||||
})
|
||||
} else {
|
||||
this
|
||||
}
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
|
|
@ -3670,6 +3725,7 @@ impl GitGraph {
|
|||
// grid, on the other hand, doesn't appear to give taffy the same
|
||||
// problems.
|
||||
.w_full()
|
||||
.max_h(line_height * 12.)
|
||||
.py_2()
|
||||
.pl_2()
|
||||
.grid()
|
||||
|
|
@ -3678,13 +3734,12 @@ impl GitGraph {
|
|||
.child(
|
||||
div()
|
||||
.relative()
|
||||
.w_full()
|
||||
.size_full()
|
||||
.child(
|
||||
div()
|
||||
.id("commit-message")
|
||||
.text_sm()
|
||||
.w_full()
|
||||
.max_h(line_height * 12.)
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(scroll_handle)
|
||||
.child(MarkdownElement::new(message.clone(), message_style)),
|
||||
|
|
@ -7326,136 +7381,6 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_long_commit_message_is_constrained_to_scroll_viewport(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
Path::new("/project"),
|
||||
json!({ ".git": {}, "file.txt": "content" }),
|
||||
)
|
||||
.await;
|
||||
|
||||
let commit_sha = Oid::from_bytes(&[1; 20]).expect("commit SHA should be valid");
|
||||
let commits = vec![Arc::new(InitialGraphCommitData {
|
||||
sha: commit_sha,
|
||||
parents: smallvec![],
|
||||
ref_names: vec!["HEAD -> main".into()],
|
||||
})];
|
||||
fs.set_graph_commits(Path::new("/project/.git"), commits);
|
||||
|
||||
let message = (0..40)
|
||||
.map(|line_number| {
|
||||
format!(
|
||||
"Line {line_number}: This commit message is long enough to require scrolling."
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n");
|
||||
fs.set_commit_data(
|
||||
Path::new("/project/.git"),
|
||||
[(
|
||||
CommitData {
|
||||
sha: commit_sha,
|
||||
parents: smallvec![],
|
||||
author_name: "Author".into(),
|
||||
author_email: "author@example.com".into(),
|
||||
commit_timestamp: 1_700_000_000,
|
||||
subject: "Long commit message".into(),
|
||||
message: message.into(),
|
||||
},
|
||||
false,
|
||||
)],
|
||||
);
|
||||
|
||||
let project = Project::test(fs.clone(), [Path::new("/project")], cx).await;
|
||||
cx.run_until_parked();
|
||||
|
||||
let repository = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.active_repository(cx)
|
||||
.expect("should have a repository")
|
||||
});
|
||||
let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
workspace::MultiWorkspace::test_new(project.clone(), window, cx)
|
||||
});
|
||||
let workspace_weak =
|
||||
multi_workspace.read_with(&*cx, |multi, _| multi.workspace().downgrade());
|
||||
let git_graph = cx.new_window_entity(|window, cx| {
|
||||
GitGraph::new(
|
||||
repository.read(cx).id,
|
||||
project.read(cx).git_store().clone(),
|
||||
workspace_weak,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
git_graph.update_in(cx, |graph, window, cx| {
|
||||
graph.select_first(&menu::SelectFirst, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
git_graph.update(cx, |graph, cx| {
|
||||
graph.selected_commit_diff = Some(CommitDiff {
|
||||
files: vec![CommitFile {
|
||||
path: RepoPath::new("file.txt").expect("repository path should be valid"),
|
||||
old_text: Some("content".into()),
|
||||
new_text: Some("updated content".into()),
|
||||
is_binary: false,
|
||||
}],
|
||||
});
|
||||
graph.selected_commit_diff_stats = Some((1, 1));
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
cx.draw(
|
||||
point(px(0.), px(0.)),
|
||||
gpui::size(px(1200.), px(800.)),
|
||||
|_, _| git_graph.clone().into_any_element(),
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
||||
let (message_scroll_handle, changed_files_scroll_handle) =
|
||||
git_graph.read_with(&*cx, |graph, _| {
|
||||
(
|
||||
graph
|
||||
.selected_commit_message
|
||||
.as_ref()
|
||||
.expect("selected commit message should be loaded")
|
||||
.scroll_handle
|
||||
.clone(),
|
||||
graph.changed_files_scroll_handle.clone(),
|
||||
)
|
||||
});
|
||||
let maximum_message_height = git_graph.update_in(cx, |_, window, cx| {
|
||||
editor::hover_markdown_style(window, cx)
|
||||
.base_text_style
|
||||
.line_height_in_pixels(window.rem_size())
|
||||
* 12.
|
||||
});
|
||||
let message_bounds = message_scroll_handle.bounds();
|
||||
let changed_files_bounds = changed_files_scroll_handle.0.borrow().base_handle.bounds();
|
||||
|
||||
assert!(
|
||||
message_bounds.size.height <= maximum_message_height,
|
||||
"commit message viewport height ({}) should not exceed its maximum ({})",
|
||||
message_bounds.size.height,
|
||||
maximum_message_height,
|
||||
);
|
||||
assert!(
|
||||
message_scroll_handle.max_offset().y > px(0.),
|
||||
"long commit message should be scrollable"
|
||||
);
|
||||
assert!(
|
||||
message_bounds.bottom() <= changed_files_bounds.top(),
|
||||
"commit message viewport {message_bounds:?} should not overlap changed files {changed_files_bounds:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_commit_message_not_reloaded_for_same_sha(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ async-channel.workspace = true
|
|||
stacksafe.workspace = true
|
||||
strum.workspace = true
|
||||
sum_tree.workspace = true
|
||||
taffy = "=0.12.2"
|
||||
taffy = "=0.12.1"
|
||||
thiserror.workspace = true
|
||||
gpui_util.workspace = true
|
||||
hdrhistogram = { workspace = true, optional = true }
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 378 B |
|
|
@ -100,7 +100,7 @@ impl Render for ImageShowcase {
|
|||
.items_center()
|
||||
.gap_8()
|
||||
.child(ImageContainer::new(
|
||||
"Image loaded from a local file with EXIF orientation",
|
||||
"Image loaded from a local file",
|
||||
self.local_resource.clone(),
|
||||
))
|
||||
.child(ImageContainer::new(
|
||||
|
|
@ -202,9 +202,7 @@ fn run_example() {
|
|||
cx.open_window(window_options, |_, cx| {
|
||||
cx.new(|_| ImageShowcase {
|
||||
// Relative path to your root project path
|
||||
local_resource: manifest_dir
|
||||
.join("examples/image/exif-orientation-rotate-180.jpg")
|
||||
.into(),
|
||||
local_resource: manifest_dir.join("examples/image/app-icon.png").into(),
|
||||
remote_resource: "https://picsum.photos/800/400".into(),
|
||||
asset_resource: "image/color.svg".into(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -262,10 +262,7 @@ impl Interactivity {
|
|||
) {
|
||||
self.mouse_down_listeners
|
||||
.push(Box::new(move |event, phase, hitbox, window, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& !window.has_active_prompt()
|
||||
&& !hitbox.contains(&window.mouse_position())
|
||||
{
|
||||
if phase == DispatchPhase::Capture && !hitbox.contains(&window.mouse_position()) {
|
||||
(listener)(event, window, cx)
|
||||
}
|
||||
}));
|
||||
|
|
@ -2672,8 +2669,8 @@ impl Interactivity {
|
|||
if phase == DispatchPhase::Capture && group_hovered != was_group_hovered {
|
||||
if let Some(hover_state) = &hover_state {
|
||||
hover_state.borrow_mut().group = group_hovered;
|
||||
cx.notify(current_view);
|
||||
}
|
||||
cx.notify(current_view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -4117,108 +4114,9 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::{
|
||||
AnyWindowHandle, AppContext as _, Context, InputEvent, Keystroke, MouseMoveEvent,
|
||||
TestAppContext, canvas, util::FluentBuilder as _,
|
||||
TestAppContext, util::FluentBuilder as _,
|
||||
};
|
||||
use std::{cell::Cell, rc::Weak};
|
||||
|
||||
struct GroupHoverTestView {
|
||||
render_count: Rc<Cell<usize>>,
|
||||
anonymous_paint_count: Rc<Cell<usize>>,
|
||||
stateful_width: Rc<Cell<Pixels>>,
|
||||
}
|
||||
|
||||
impl Render for GroupHoverTestView {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
self.render_count.set(self.render_count.get() + 1);
|
||||
let anonymous_paint_count = self.anonymous_paint_count.clone();
|
||||
let stateful_width = self.stateful_width.clone();
|
||||
div().size_full().child(
|
||||
div()
|
||||
.ml(px(20.))
|
||||
.mt(px(20.))
|
||||
.size(px(50.))
|
||||
.relative()
|
||||
.group("hover-group")
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.size_full()
|
||||
.invisible()
|
||||
.group_hover("hover-group", |style| style.visible())
|
||||
.child(canvas(
|
||||
|_, _, _| {},
|
||||
move |_, _, _, _| {
|
||||
anonymous_paint_count.set(anonymous_paint_count.get() + 1)
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("stateful-group-hover-target")
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size(px(10.))
|
||||
.group_hover("hover-group", |style| style.size(px(20.)))
|
||||
.child(canvas(
|
||||
move |bounds, _, _| stateful_width.set(bounds.size.width),
|
||||
|_, _, _, _| {},
|
||||
)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn group_hover_styles_update_only_on_transitions(cx: &mut TestAppContext) {
|
||||
let render_count = Rc::new(Cell::new(0));
|
||||
let anonymous_paint_count = Rc::new(Cell::new(0));
|
||||
let stateful_width = Rc::new(Cell::new(px(0.)));
|
||||
let window = cx.add_window({
|
||||
let render_count = render_count.clone();
|
||||
let anonymous_paint_count = anonymous_paint_count.clone();
|
||||
let stateful_width = stateful_width.clone();
|
||||
move |_, _| GroupHoverTestView {
|
||||
render_count,
|
||||
anonymous_paint_count,
|
||||
stateful_width,
|
||||
}
|
||||
});
|
||||
let window = AnyWindowHandle::from(window);
|
||||
|
||||
cx.update_window(window, |_, window, cx| window.draw(cx).clear())
|
||||
.unwrap();
|
||||
assert_eq!(anonymous_paint_count.get(), 0);
|
||||
assert_eq!(stateful_width.get(), px(10.));
|
||||
|
||||
let move_mouse = |cx: &mut TestAppContext, position| {
|
||||
cx.update_window(window, |_, window, cx| {
|
||||
window.simulate_mouse_move(position, cx)
|
||||
})
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let initial_render_count = render_count.get();
|
||||
move_mouse(cx, point(px(25.), px(25.)));
|
||||
assert_eq!(render_count.get(), initial_render_count + 1);
|
||||
assert_eq!(anonymous_paint_count.get(), 1);
|
||||
assert_eq!(stateful_width.get(), px(20.));
|
||||
|
||||
move_mouse(cx, point(px(30.), px(30.)));
|
||||
assert_eq!(render_count.get(), initial_render_count + 1);
|
||||
assert_eq!(anonymous_paint_count.get(), 1);
|
||||
assert_eq!(stateful_width.get(), px(20.));
|
||||
|
||||
move_mouse(cx, point(px(5.), px(5.)));
|
||||
assert_eq!(render_count.get(), initial_render_count + 2);
|
||||
assert_eq!(anonymous_paint_count.get(), 1);
|
||||
assert_eq!(stateful_width.get(), px(10.));
|
||||
|
||||
move_mouse(cx, point(px(10.), px(10.)));
|
||||
assert_eq!(render_count.get(), initial_render_count + 2);
|
||||
assert_eq!(anonymous_paint_count.get(), 1);
|
||||
assert_eq!(stateful_width.get(), px(10.));
|
||||
}
|
||||
use std::rc::Weak;
|
||||
|
||||
struct TestTooltipView;
|
||||
|
||||
|
|
@ -4526,87 +4424,6 @@ mod tests {
|
|||
assert!(active_tooltip.borrow().is_none());
|
||||
}
|
||||
|
||||
struct MouseDownOutOwner {
|
||||
mouse_down_out_count: Rc<RefCell<usize>>,
|
||||
}
|
||||
|
||||
impl Render for MouseDownOutOwner {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let mouse_down_out_count = self.mouse_down_out_count.clone();
|
||||
div()
|
||||
.size_full()
|
||||
.child(div().id("target").w(px(50.)).h(px(50.)).on_mouse_down_out(
|
||||
move |_, _, _| {
|
||||
*mouse_down_out_count.borrow_mut() += 1;
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
|
||||
let mut test_app = TestAppContext::single();
|
||||
let mouse_down_out_count = Rc::new(RefCell::new(0));
|
||||
let window = test_app.add_window({
|
||||
let mouse_down_out_count = mouse_down_out_count.clone();
|
||||
move |_, _| MouseDownOutOwner {
|
||||
mouse_down_out_count,
|
||||
}
|
||||
});
|
||||
let any_window: AnyWindowHandle = window.into();
|
||||
|
||||
fn dispatch_mouse_down_outside_target(
|
||||
test_app: &mut TestAppContext,
|
||||
any_window: AnyWindowHandle,
|
||||
) {
|
||||
test_app
|
||||
.update_window(any_window, |_, window, cx| {
|
||||
window.dispatch_event(
|
||||
MouseDownEvent {
|
||||
position: point(px(75.), px(75.)),
|
||||
button: MouseButton::Left,
|
||||
modifiers: Default::default(),
|
||||
click_count: 1,
|
||||
first_mouse: false,
|
||||
}
|
||||
.to_platform_input(),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
test_app
|
||||
.update_window(any_window, |_, window, cx| {
|
||||
window.draw(cx).clear();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
dispatch_mouse_down_outside_target(&mut test_app, any_window);
|
||||
assert_eq!(
|
||||
*mouse_down_out_count.borrow(),
|
||||
1,
|
||||
"mouse down outside the element should fire mouse-down-out listeners"
|
||||
);
|
||||
|
||||
test_app
|
||||
.update_window(any_window, |_, window, cx| {
|
||||
cx.set_prompt_builder(crate::fallback_prompt_renderer);
|
||||
let _receiver =
|
||||
window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
|
||||
assert!(window.has_active_prompt());
|
||||
window.draw(cx).clear();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
dispatch_mouse_down_outside_target(&mut test_app, any_window);
|
||||
assert_eq!(
|
||||
*mouse_down_out_count.borrow(),
|
||||
1,
|
||||
"mouse down over an active prompt should not fire mouse-down-out listeners"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_a11y_info_string_and_numeric_properties() {
|
||||
let mut interactivity = Interactivity::default();
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@ use crate::{
|
|||
AnyElement, AnyImageCache, App, Asset, AssetLogger, Bounds, DefiniteLength, Element, ElementId,
|
||||
Entity, GlobalElementId, Hitbox, Image, ImageCache, InspectorElementId, InteractiveElement,
|
||||
Interactivity, IntoElement, LayoutId, Length, ObjectFit, Pixels, RenderImage, Resource,
|
||||
SharedString, SharedUri, StyleRefinement, Styled, Task, Window, decode_static_image,
|
||||
decode_static_image_from_decoder, px,
|
||||
SharedString, SharedUri, StyleRefinement, Styled, Task, Window, px,
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
||||
use futures::Future;
|
||||
use gpui_util::ResultExt;
|
||||
use image::{
|
||||
AnimationDecoder, ImageError, ImageFormat, Rgba,
|
||||
AnimationDecoder, DynamicImage, Frame, ImageError, ImageFormat, Rgba,
|
||||
codecs::{gif::GifDecoder, webp::WebPDecoder},
|
||||
};
|
||||
use scheduler::Instant;
|
||||
|
|
@ -717,10 +716,27 @@ impl Asset for ImageAssetLoader {
|
|||
|
||||
frames
|
||||
} else {
|
||||
decode_static_image_from_decoder(decoder)?
|
||||
let mut data = DynamicImage::from_decoder(decoder)?.into_rgba8();
|
||||
|
||||
// Convert from RGBA to BGRA.
|
||||
for pixel in data.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
|
||||
SmallVec::from_elem(Frame::new(data), 1)
|
||||
}
|
||||
}
|
||||
_ => decode_static_image(&bytes, format)?,
|
||||
_ => {
|
||||
let mut data =
|
||||
image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
|
||||
|
||||
// Convert from RGBA to BGRA.
|
||||
for pixel in data.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
|
||||
SmallVec::from_elem(Frame::new(data), 1)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Arc::new(RenderImage::new(data)))
|
||||
|
|
|
|||
|
|
@ -1038,7 +1038,7 @@ impl StateInner {
|
|||
let mut rendered_focused_item = false;
|
||||
|
||||
let available_item_space = size(
|
||||
available_width.map_or(AvailableSpace::MaxContent, |width| {
|
||||
available_width.map_or(AvailableSpace::MinContent, |width| {
|
||||
AvailableSpace::Definite(width)
|
||||
}),
|
||||
AvailableSpace::MinContent,
|
||||
|
|
|
|||
|
|
@ -618,7 +618,6 @@ struct TextLayoutInner {
|
|||
lines: SmallVec<[WrappedLine; 1]>,
|
||||
line_height: Pixels,
|
||||
wrap_width: Option<Pixels>,
|
||||
truncate_width: Option<Pixels>,
|
||||
size: Option<Size<Pixels>>,
|
||||
bounds: Option<Bounds<Pixels>>,
|
||||
}
|
||||
|
|
@ -681,20 +680,16 @@ impl TextLayout {
|
|||
// 2. wrap_width matches (or both are None)
|
||||
// 3. truncate_width is None (if truncate_width is Some, we need to re-layout
|
||||
// because the previous layout may have been computed without truncation)
|
||||
// 4. the cached layout was not truncated (a truncated layout answers an
|
||||
// unconstrained probe with the truncated size, which poisons intrinsic
|
||||
// sizing with whatever width some earlier measure pass happened to use)
|
||||
if let Some(text_layout) = element_state.0.borrow().as_ref()
|
||||
&& let Some(size) = text_layout.size
|
||||
&& (wrap_width.is_none() || wrap_width == text_layout.wrap_width)
|
||||
&& truncate_width.is_none()
|
||||
&& text_layout.truncate_width.is_none()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size);
|
||||
let (text, runs) = if let Some(truncate_width) = truncate_width {
|
||||
let (text, runs) = if truncate_width.is_some() {
|
||||
if let Some(max_lines) = text_style.line_clamp
|
||||
&& let Some(wrap_width) = wrap_width
|
||||
{
|
||||
|
|
@ -706,25 +701,10 @@ impl TextLayout {
|
|||
&runs,
|
||||
truncate_from,
|
||||
)
|
||||
} else if let Some(unclipped) = window
|
||||
.text_system()
|
||||
.shape_text(text.clone(), font_size, &runs, None, None)
|
||||
.log_err()
|
||||
&& unclipped
|
||||
.iter()
|
||||
.all(|line| line.size(line_height).width <= truncate_width)
|
||||
{
|
||||
// The truncation decision below sums per-character advances,
|
||||
// which overestimates the shaped width (no kerning), truncating
|
||||
// text that fits exactly in its measured width. Skip truncation
|
||||
// whenever the honestly-shaped text fits; the shaping result
|
||||
// comes from the line layout cache when the same text was
|
||||
// already measured untruncated this frame.
|
||||
(text.clone(), Cow::Borrowed(&*runs))
|
||||
} else {
|
||||
line_wrapper.truncate_line(
|
||||
text.clone(),
|
||||
truncate_width,
|
||||
truncate_width.unwrap_or(Pixels::MAX),
|
||||
&truncation_affix,
|
||||
&runs,
|
||||
truncate_from,
|
||||
|
|
@ -751,7 +731,6 @@ impl TextLayout {
|
|||
len: 0,
|
||||
line_height,
|
||||
wrap_width,
|
||||
truncate_width,
|
||||
size: Some(Size::default()),
|
||||
bounds: None,
|
||||
});
|
||||
|
|
@ -770,7 +749,6 @@ impl TextLayout {
|
|||
len,
|
||||
line_height,
|
||||
wrap_width,
|
||||
truncate_width,
|
||||
size: Some(size),
|
||||
bounds: None,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -671,7 +671,7 @@ impl UniformList {
|
|||
return Size::default();
|
||||
};
|
||||
let available_space = size(
|
||||
list_width.map_or(AvailableSpace::MaxContent, |width| {
|
||||
list_width.map_or(AvailableSpace::MinContent, |width| {
|
||||
AvailableSpace::Definite(width)
|
||||
}),
|
||||
AvailableSpace::MinContent,
|
||||
|
|
|
|||
|
|
@ -42,15 +42,15 @@ use crate::{
|
|||
RenderImageParams, RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString, Size,
|
||||
SvgRenderer, SystemWindowTab, Task, Window, WindowControlArea, hash, point, px, size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use anyhow::bail;
|
||||
use anyhow::{Context as _, Result};
|
||||
use async_task::Runnable;
|
||||
use futures::channel::oneshot;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use image::RgbaImage;
|
||||
use image::codecs::gif::GifDecoder;
|
||||
use image::{AnimationDecoder as _, DynamicImage, Frame};
|
||||
use image::{AnimationDecoder as _, Frame};
|
||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
use scheduler::Instant;
|
||||
pub use scheduler::RunnableMeta;
|
||||
|
|
@ -2368,33 +2368,6 @@ pub struct Image {
|
|||
pub id: u64,
|
||||
}
|
||||
|
||||
pub(crate) fn decode_static_image(
|
||||
bytes: &[u8],
|
||||
format: image::ImageFormat,
|
||||
) -> Result<SmallVec<[Frame; 1]>> {
|
||||
let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
|
||||
.into_decoder()
|
||||
.context("creating image decoder")?;
|
||||
decode_static_image_from_decoder(decoder)
|
||||
}
|
||||
|
||||
pub(crate) fn decode_static_image_from_decoder(
|
||||
mut decoder: impl image::ImageDecoder,
|
||||
) -> Result<SmallVec<[Frame; 1]>> {
|
||||
let orientation = decoder
|
||||
.orientation()
|
||||
.context("reading decoder's orientation")?;
|
||||
let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
|
||||
image.apply_orientation(orientation);
|
||||
|
||||
let mut data = image.into_rgba8();
|
||||
for pixel in data.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
|
||||
Ok(SmallVec::from_elem(Frame::new(data), 1))
|
||||
}
|
||||
|
||||
impl Hash for Image {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.id);
|
||||
|
|
@ -2450,6 +2423,20 @@ impl Image {
|
|||
|
||||
/// Convert the clipboard image to an `ImageData` object.
|
||||
pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
|
||||
fn frames_for_image(
|
||||
bytes: &[u8],
|
||||
format: image::ImageFormat,
|
||||
) -> Result<SmallVec<[Frame; 1]>> {
|
||||
let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
|
||||
|
||||
// Convert from RGBA to BGRA.
|
||||
for pixel in data.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
|
||||
Ok(SmallVec::from_elem(Frame::new(data), 1))
|
||||
}
|
||||
|
||||
let frames = match self.format {
|
||||
ImageFormat::Gif => {
|
||||
let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
|
||||
|
|
@ -2476,18 +2463,18 @@ impl Image {
|
|||
|
||||
frames
|
||||
}
|
||||
ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
|
||||
ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
|
||||
ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
|
||||
ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
|
||||
ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
|
||||
ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
|
||||
ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
|
||||
ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
|
||||
ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
|
||||
ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
|
||||
ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
|
||||
ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
|
||||
ImageFormat::Svg => {
|
||||
return svg_renderer
|
||||
.render_single_frame(&self.bytes, 1.0)
|
||||
.map_err(Into::into);
|
||||
}
|
||||
ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
|
||||
ImageFormat::Pnm => frames_for_image(&self.bytes, image::ImageFormat::Pnm)?,
|
||||
};
|
||||
|
||||
Ok(Arc::new(RenderImage::new(frames)))
|
||||
|
|
@ -2572,22 +2559,6 @@ mod image_tests {
|
|||
use super::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn test_image_to_image_data_applies_exif_orientation() {
|
||||
let image = Image::from_bytes(
|
||||
ImageFormat::Jpeg,
|
||||
include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
|
||||
);
|
||||
|
||||
let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
|
||||
|
||||
assert_eq!(render_image.size(0), size(16.into(), 32.into()));
|
||||
|
||||
let bytes = render_image.as_bytes(0).unwrap();
|
||||
assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
|
||||
assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_svg_image_to_image_data_converts_to_bgra() {
|
||||
let image = Image::from_bytes(
|
||||
|
|
|
|||
|
|
@ -5343,14 +5343,6 @@ impl Window {
|
|||
receiver
|
||||
}
|
||||
|
||||
/// Returns whether a prompt rendered by GPUI is currently active in this window.
|
||||
///
|
||||
/// This is only true for prompts rendered in the window (see
|
||||
/// [`App::set_prompt_builder`]), not for platform-native prompt dialogs.
|
||||
pub fn has_active_prompt(&self) -> bool {
|
||||
self.prompt.is_some()
|
||||
}
|
||||
|
||||
/// Returns the current context stack.
|
||||
pub fn context_stack(&self) -> Vec<KeyContext> {
|
||||
let node_id = self.focus_node_id_in_rendered_frame(self.focus);
|
||||
|
|
|
|||
|
|
@ -489,56 +489,27 @@ impl LspAdapter for RustLspAdapter {
|
|||
.collect::<SmallVec<[_; 8]>>();
|
||||
all_stop_ranges.sort_unstable_by_key(|a| (a.start, Reverse(a.end)));
|
||||
|
||||
// Placeholders may nest, e.g. `$2` inside `${1:"$2"}`
|
||||
struct OpenPlaceholder {
|
||||
snippet_text_end: usize,
|
||||
label_run_start: usize,
|
||||
}
|
||||
let mut open_placeholders = SmallVec::<[OpenPlaceholder; 4]>::new();
|
||||
|
||||
for range in &all_stop_ranges {
|
||||
let start_pos = range.start as usize;
|
||||
let end_pos = range.end as usize;
|
||||
|
||||
while let Some(placeholder) = open_placeholders.last() {
|
||||
if placeholder.snippet_text_end > start_pos {
|
||||
break;
|
||||
}
|
||||
label.push_str(&snippet.text[text_pos..placeholder.snippet_text_end]);
|
||||
text_pos = placeholder.snippet_text_end;
|
||||
runs.push((
|
||||
placeholder.label_run_start..label.len(),
|
||||
HighlightId::TABSTOP_REPLACE_ID,
|
||||
));
|
||||
open_placeholders.pop();
|
||||
}
|
||||
|
||||
label.push_str(&snippet.text[text_pos..start_pos]);
|
||||
text_pos = start_pos;
|
||||
|
||||
if start_pos == end_pos {
|
||||
let caret_start = label.len();
|
||||
label.push('…');
|
||||
runs.push((caret_start..label.len(), HighlightId::TABSTOP_INSERT_ID));
|
||||
} else {
|
||||
open_placeholders.push(OpenPlaceholder {
|
||||
snippet_text_end: end_pos,
|
||||
label_run_start: label.len(),
|
||||
});
|
||||
let label_start = label.len();
|
||||
label.push_str(&snippet.text[start_pos..end_pos]);
|
||||
let label_end = label.len();
|
||||
runs.push((label_start..label_end, HighlightId::TABSTOP_REPLACE_ID));
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(placeholder) = open_placeholders.pop() {
|
||||
label.push_str(&snippet.text[text_pos..placeholder.snippet_text_end]);
|
||||
text_pos = placeholder.snippet_text_end;
|
||||
runs.push((
|
||||
placeholder.label_run_start..label.len(),
|
||||
HighlightId::TABSTOP_REPLACE_ID,
|
||||
));
|
||||
text_pos = end_pos;
|
||||
}
|
||||
|
||||
label.push_str(&snippet.text[text_pos..]);
|
||||
runs.sort_unstable_by_key(|(range, _)| (range.start, Reverse(range.end)));
|
||||
|
||||
if detail_left.is_some_and(|detail_left| detail_left == new_text) {
|
||||
// We only include the left detail if it isn't the snippet again
|
||||
|
|
@ -1482,7 +1453,6 @@ mod tests {
|
|||
use crate::language;
|
||||
use gpui::{BorrowAppContext, Hsla, TestAppContext};
|
||||
use lsp::CompletionItemLabelDetails;
|
||||
use pretty_assertions::assert_eq;
|
||||
use settings::SettingsStore;
|
||||
use theme::SyntaxTheme;
|
||||
use util::path;
|
||||
|
|
@ -1906,34 +1876,6 @@ mod tests {
|
|||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
adapter
|
||||
.label_for_completion(
|
||||
&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::SNIPPET),
|
||||
label: "unimplemented".to_string(),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
range: lsp::Range::default(),
|
||||
new_text: "unimplemented!(${1:\"$2\"})".to_string(),
|
||||
})),
|
||||
..lsp::CompletionItem::default()
|
||||
},
|
||||
&language,
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel::new(
|
||||
"unimplemented!(\"…\")".to_string(),
|
||||
0..13,
|
||||
vec![
|
||||
(15..20, HighlightId::TABSTOP_REPLACE_ID),
|
||||
(16..19, HighlightId::TABSTOP_INSERT_ID),
|
||||
(0..13, HighlightId::new(2)),
|
||||
(13..14, HighlightId::new(2)),
|
||||
],
|
||||
))
|
||||
);
|
||||
|
||||
// Postfix completion without actual tabstops (only implicit final $0)
|
||||
// The label should use completion.label so it can be filtered by "ref"
|
||||
let ref_completion = adapter
|
||||
|
|
|
|||
|
|
@ -637,8 +637,6 @@ impl TypeScriptLspAdapter {
|
|||
"node_modules/typescript/lib"
|
||||
};
|
||||
|
||||
// typescript-language-server doesn't support TypeScript 7+, which no longer
|
||||
// ships `tsserver.js`.
|
||||
if self
|
||||
.fs
|
||||
.is_file(
|
||||
|
|
|
|||
|
|
@ -57,15 +57,9 @@ impl VtslsLspAdapter {
|
|||
Self::TYPESCRIPT_TSDK_PATH
|
||||
};
|
||||
|
||||
// vtsls doesn't support TypeScript 7+, which no longer ships `tsserver.js`.
|
||||
if self
|
||||
.fs
|
||||
.is_file(
|
||||
&adapter
|
||||
.worktree_root_path()
|
||||
.join(tsdk_path)
|
||||
.join("tsserver.js"),
|
||||
)
|
||||
.is_dir(&adapter.worktree_root_path().join(tsdk_path))
|
||||
.await
|
||||
{
|
||||
Some(tsdk_path)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ fuzzy_nucleo.workspace = true
|
|||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
picker.workspace = true
|
||||
picker_preview.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
theme_settings.workspace = true
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ use gpui::{
|
|||
ParentElement, Point, Rems, Render, Styled, StyledText, Task, TextStyle, WeakEntity, Window,
|
||||
div, rems,
|
||||
};
|
||||
use language::{OffsetRangeExt, Outline, OutlineItem, OutlineSearchEntry};
|
||||
use picker::{MatchLocation, Picker, PickerDelegate, PreviewUpdate};
|
||||
use language::{Outline, OutlineItem, OutlineSearchEntry};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use settings::Settings;
|
||||
use theme::ActiveTheme;
|
||||
use theme_settings::ThemeSettings;
|
||||
|
|
@ -176,16 +176,9 @@ impl OutlineView {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> OutlineView {
|
||||
let project = editor.read(cx).project().cloned();
|
||||
let delegate = OutlineViewDelegate::new(cx.entity().downgrade(), outline, editor, cx);
|
||||
let picker = cx.new(|cx| {
|
||||
let picker = if let Some(project) = project {
|
||||
let preview = picker_preview::editor_preview(project, window, cx);
|
||||
Picker::uniform_list_with_preview(delegate, preview, window, cx)
|
||||
} else {
|
||||
Picker::uniform_list(delegate, window, cx)
|
||||
};
|
||||
picker
|
||||
Picker::uniform_list(delegate, window, cx)
|
||||
.max_height(Rems::from_pixels(
|
||||
window.viewport_size().height * 0.75,
|
||||
window,
|
||||
|
|
@ -300,30 +293,6 @@ impl PickerDelegate for OutlineViewDelegate {
|
|||
self.set_selected_index(ix, true, cx);
|
||||
}
|
||||
|
||||
fn try_get_preview_data_for_match(&self, cx: &App) -> Option<PreviewUpdate> {
|
||||
let selected_match = self.matches.get(self.selected_match_index)?;
|
||||
let outline_item = self.outline.items.get(selected_match.candidate_id())?;
|
||||
let multi_buffer = self.active_editor.read(cx).buffer().clone();
|
||||
let (buffer, start) = multi_buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(outline_item.selection_range.start, cx)?;
|
||||
let (end_buffer, end) = multi_buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(outline_item.selection_range.end, cx)?;
|
||||
if buffer != end_buffer {
|
||||
return None;
|
||||
}
|
||||
|
||||
let range = (start..end).to_offset(&buffer.read(cx).text_snapshot());
|
||||
Some(PreviewUpdate::from_buffer(
|
||||
buffer,
|
||||
MatchLocation {
|
||||
anchor_range: start..end,
|
||||
range,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ use collections::{BTreeMap, BTreeSet, HashMap, HashSet, btree_map};
|
|||
use futures::{
|
||||
AsyncWriteExt, Future, FutureExt, StreamExt,
|
||||
channel::oneshot,
|
||||
future::{Either, Shared, join_all, select},
|
||||
future::{Either, Shared, join_all, pending, select},
|
||||
select, select_biased,
|
||||
stream::FuturesUnordered,
|
||||
};
|
||||
|
|
@ -123,6 +123,7 @@ use std::{
|
|||
collections::{VecDeque, hash_map},
|
||||
convert::TryInto,
|
||||
ffi::OsStr,
|
||||
future::ready,
|
||||
iter, mem,
|
||||
ops::{ControlFlow, Range},
|
||||
path::{self, Path, PathBuf},
|
||||
|
|
@ -14151,10 +14152,9 @@ fn lsp_workspace_diagnostics_refresh(
|
|||
let mut requests = 0;
|
||||
|
||||
loop {
|
||||
let Some(completion_tx) = refresh_rx.recv().await else {
|
||||
let Some(mut completion_tx) = refresh_rx.recv().await else {
|
||||
return;
|
||||
};
|
||||
let mut completion_txs = completion_tx.into_iter().collect::<Vec<_>>();
|
||||
|
||||
'request: loop {
|
||||
requests += 1;
|
||||
|
|
@ -14170,12 +14170,6 @@ fn lsp_workspace_diagnostics_refresh(
|
|||
.await;
|
||||
attempts += 1;
|
||||
|
||||
// Absorb refresh requests that queued up in the meantime, so their
|
||||
// waiters are resolved by this attempt instead of waiting behind it.
|
||||
while let Ok(queued_refresh) = refresh_rx.try_recv() {
|
||||
completion_txs.extend(queued_refresh);
|
||||
}
|
||||
|
||||
let Ok(previous_result_ids) = lsp_store.update(cx, |lsp_store, _| {
|
||||
lsp_store
|
||||
.result_ids_for_workspace_refresh(server.server_id(), ®istration_id_shared)
|
||||
|
|
@ -14202,21 +14196,8 @@ fn lsp_workspace_diagnostics_refresh(
|
|||
};
|
||||
|
||||
progress_rx.try_recv().ok();
|
||||
// Restart the timeout whenever a partial result arrives: streaming
|
||||
// servers may legitimately take longer than a single timeout period,
|
||||
// but a server that stops streaming without completing the request
|
||||
// should still time out instead of hanging forever.
|
||||
let timer = async {
|
||||
loop {
|
||||
let timer = pin!(server.request_timer(timeout).fuse());
|
||||
let progress = pin!(progress_rx.recv().fuse());
|
||||
match select(timer, progress).await {
|
||||
Either::Left((message, ..)) => break message,
|
||||
Either::Right((Some(()), ..)) => {}
|
||||
Either::Right((None, timer)) => break timer.await,
|
||||
}
|
||||
}
|
||||
};
|
||||
let timer = server.request_timer(timeout).fuse();
|
||||
let progress = pin!(progress_rx.recv().fuse());
|
||||
let response_result = server
|
||||
.request_with_timer::<lsp::WorkspaceDiagnosticRequest, _>(
|
||||
lsp::WorkspaceDiagnosticParams {
|
||||
|
|
@ -14227,7 +14208,10 @@ fn lsp_workspace_diagnostics_refresh(
|
|||
partial_result_token: Some(lsp::ProgressToken::String(token)),
|
||||
},
|
||||
},
|
||||
timer,
|
||||
select(timer, progress).then(|either| match either {
|
||||
Either::Left((message, ..)) => ready(message).left_future(),
|
||||
Either::Right(..) => pending::<String>().right_future(),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
|
@ -14236,16 +14220,6 @@ fn lsp_workspace_diagnostics_refresh(
|
|||
match response_result {
|
||||
ConnectionResult::Timeout => {
|
||||
log::error!("Timeout during workspace diagnostics pull");
|
||||
// Release everyone waiting on this refresh (e.g. the agent's
|
||||
// diagnostics tool), including waiters that queued up while the
|
||||
// request was in flight, so they can fall back to cached
|
||||
// diagnostics instead of waiting through every retry.
|
||||
while let Ok(queued_refresh) = refresh_rx.try_recv() {
|
||||
completion_txs.extend(queued_refresh);
|
||||
}
|
||||
for tx in completion_txs.drain(..) {
|
||||
tx.send(false).ok();
|
||||
}
|
||||
continue 'request;
|
||||
}
|
||||
ConnectionResult::ConnectionReset => {
|
||||
|
|
@ -14254,7 +14228,7 @@ fn lsp_workspace_diagnostics_refresh(
|
|||
}
|
||||
ConnectionResult::Result(Err(e)) => {
|
||||
log::error!("Error during workspace diagnostics pull: {e:#}");
|
||||
for tx in completion_txs.drain(..) {
|
||||
if let Some(tx) = completion_tx.take() {
|
||||
tx.send(false).ok();
|
||||
}
|
||||
break 'request;
|
||||
|
|
@ -14274,7 +14248,7 @@ fn lsp_workspace_diagnostics_refresh(
|
|||
{
|
||||
return;
|
||||
}
|
||||
for tx in completion_txs.drain(..) {
|
||||
if let Some(tx) = completion_tx.take() {
|
||||
tx.send(true).ok();
|
||||
}
|
||||
break 'request;
|
||||
|
|
|
|||
|
|
@ -4644,71 +4644,6 @@ async fn test_diagnostic_summaries_retained_on_buffer_close_with_workspace_diagn
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_workspace_diagnostics_pull_timeout_releases_waiters(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/dir"), json!({ "a.rs": "one two three" }))
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
|
||||
lsp::DiagnosticOptions {
|
||||
identifier: Some("test-ws-timeout".to_string()),
|
||||
inter_file_dependencies: true,
|
||||
workspace_diagnostics: true,
|
||||
work_done_progress_options: Default::default(),
|
||||
},
|
||||
)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
initializer: Some(Box::new(move |fake_server| {
|
||||
// Simulate a server that holds workspace diagnostic pull requests
|
||||
// open forever without responding or streaming partial results.
|
||||
fake_server.set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
|
||||
move |_, _| async move {
|
||||
future::pending::<()>().await;
|
||||
Err(anyhow::anyhow!("should never respond"))
|
||||
},
|
||||
);
|
||||
})),
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
|
||||
let (_buffer, _handle) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer_with_lsp(path!("/dir/a.rs"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _fake_server = fake_servers.next().await.unwrap();
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let pull_task = lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.pull_workspace_diagnostics_once(cx)
|
||||
});
|
||||
|
||||
// The waiter must be released after the request times out, instead of
|
||||
// being held through every retry of the background refresh loop.
|
||||
cx.executor().advance_clock(DEFAULT_LSP_REQUEST_TIMEOUT * 2);
|
||||
let refreshed = pull_task.await;
|
||||
assert!(
|
||||
!refreshed,
|
||||
"pull should report a failed refresh when the server never responds"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ WizardStyle=modern
|
|||
|
||||
CloseApplications=force
|
||||
|
||||
#if GetEnv("ZED_SIGN_BUNDLE") != ""
|
||||
#if GetEnv("CI") != ""
|
||||
SignTool=Defaultsign
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ $ErrorActionPreference = 'Stop'
|
|||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
|
||||
$buildSuccess = $false
|
||||
$canCodeSign = $false
|
||||
|
||||
$OSArchitecture = switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) {
|
||||
"X64" { "x86_64" }
|
||||
|
|
@ -67,31 +66,18 @@ function CheckEnvironmentVariables {
|
|||
return
|
||||
}
|
||||
|
||||
$requiredVars = @('ZED_WORKSPACE', 'RELEASE_VERSION', 'ZED_RELEASE_CHANNEL')
|
||||
|
||||
foreach ($var in $requiredVars) {
|
||||
if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($var))) {
|
||||
Write-Error "$var is not set"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# On PRs from forks the signing secrets are not populated,
|
||||
# so skip code signing instead of failing, like bundle-mac does.
|
||||
$signingVars = @(
|
||||
$requiredVars = @(
|
||||
'ZED_WORKSPACE', 'RELEASE_VERSION', 'ZED_RELEASE_CHANNEL',
|
||||
'AZURE_TENANT_ID', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET',
|
||||
'ACCOUNT_NAME', 'CERT_PROFILE_NAME', 'ENDPOINT',
|
||||
'FILE_DIGEST', 'TIMESTAMP_DIGEST', 'TIMESTAMP_SERVER'
|
||||
)
|
||||
|
||||
$missingVars = @($signingVars | Where-Object { [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($_)) })
|
||||
if ($missingVars.Count -eq 0) {
|
||||
$script:canCodeSign = $true
|
||||
} else {
|
||||
Write-Host "====== WARNING ======"
|
||||
Write-Host "One or more of the following variables are missing: $($missingVars -join ', ')"
|
||||
Write-Host "This bundle will not be code signed"
|
||||
Write-Host "====== WARNING ======"
|
||||
foreach ($var in $requiredVars) {
|
||||
if (-not (Test-Path "env:$var")) {
|
||||
Write-Error "$var is not set"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +128,7 @@ function BuildRemoteServer {
|
|||
# Create zipped remote server binary
|
||||
$remoteServerSrc = (Resolve-Path ".\$CargoOutDir\remote_server.exe").Path
|
||||
|
||||
if ($canCodeSign) {
|
||||
if ($env:CI) {
|
||||
Write-Output "Code signing remote_server.exe"
|
||||
& "$innoDir\sign.ps1" $remoteServerSrc
|
||||
}
|
||||
|
|
@ -173,7 +159,7 @@ function UploadToSentry {
|
|||
Write-Output "install with: 'winget install -e --id=Sentry.sentry-cli'"
|
||||
return
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($env:SENTRY_AUTH_TOKEN)) {
|
||||
if (-not (Test-Path "env:SENTRY_AUTH_TOKEN")) {
|
||||
Write-Output "missing SENTRY_AUTH_TOKEN. skipping sentry upload."
|
||||
return
|
||||
}
|
||||
|
|
@ -214,7 +200,7 @@ function MakeAppx {
|
|||
}
|
||||
|
||||
function SignZedAndItsFriends {
|
||||
if (-not $canCodeSign) {
|
||||
if (-not $env:CI) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -354,9 +340,7 @@ function BuildInstaller {
|
|||
}
|
||||
|
||||
$innoArgs = @($issFilePath) + $defs
|
||||
if($canCodeSign) {
|
||||
# Checked by zed.iss to decide whether to sign the installer.
|
||||
$env:ZED_SIGN_BUNDLE = "1"
|
||||
if($env:CI) {
|
||||
$signTool = "powershell.exe -ExecutionPolicy Bypass -File $innoDir\sign.ps1 `$f"
|
||||
$innoArgs += "/sDefaultsign=`"$signTool`""
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue