The PR matches project search layout with the recent changes in the buffer project layout. https://github.com/zed-industries/zed/assets/2101250/91b905ea-aed8-4740-9e60-67f3052885e2 Release Notes: - Improve project search bar layout, match it with the buffer search bar ([7722](https://github.com/zed-industries/zed/issues/7722))
Unified · Split
| @@ -38,7 +38,7 @@ pub use registrar::DivRegistrar; · 37 unmodified lines | |||
| 38 | use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults}; | 38 | use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults}; |
| 39 | 39 | ||
| 40 | const MIN_INPUT_WIDTH_REMS: f32 = 15.; | 40 | const MIN_INPUT_WIDTH_REMS: f32 = 15.; |
| 41 | const MAX_INPUT_WIDTH_REMS: f32 = 25.; | 41 | const MAX_INPUT_WIDTH_REMS: f32 = 30.; |
| 42 | 42 | ||
| 43 | #[derive(PartialEq, Clone, Deserialize)] | 43 | #[derive(PartialEq, Clone, Deserialize)] |
| 44 | pub struct Deploy { | 44 | pub struct Deploy { |
| @@ -217,7 +217,6 @@ impl Render for BufferSearchBar { · 172 unmodified lines | |||
| 217 | .flex_1() | 217 | .flex_1() |
| 218 | .px_2() | 218 | .px_2() |
| 219 | .py_1() | 219 | .py_1() |
| 220 | .gap_2() | ||
| 221 | .border_1() | 220 | .border_1() |
| 222 | .border_color(editor_border) | 221 | .border_color(editor_border) |
| 223 | .min_w(rems(MIN_INPUT_WIDTH_REMS)) | 222 | .min_w(rems(MIN_INPUT_WIDTH_REMS)) |
| @@ -50,6 +50,9 @@ use workspace::{ · 49 unmodified lines | |||
| 50 | WorkspaceId, | 50 | WorkspaceId, |
| 51 | }; | 51 | }; |
| 52 | 52 | ||
| 53 | const MIN_INPUT_WIDTH_REMS: f32 = 15.; | ||
| 54 | const MAX_INPUT_WIDTH_REMS: f32 = 30.; | ||
| 55 | |||
| 53 | actions!( | 56 | actions!( |
| 54 | project_search, | 57 | project_search, |
| 55 | [SearchInNew, ToggleFocus, NextField, ToggleFilters] | 58 | [SearchInNew, ToggleFocus, NextField, ToggleFilters] |
| @@ -1409,25 +1412,22 @@ impl ProjectSearchBar { · 1353 unmodified lines | |||
| 1409 | 1412 | ||
| 1410 | active_project_search.update(cx, |project_view, cx| { | 1413 | active_project_search.update(cx, |project_view, cx| { |
| 1411 | let mut views = vec![&project_view.query_editor]; | 1414 | let mut views = vec![&project_view.query_editor]; |
| 1415 | if project_view.replace_enabled { | ||
| 1416 | views.push(&project_view.replacement_editor); | ||
| 1417 | } | ||
| 1412 | if project_view.filters_enabled { | 1418 | if project_view.filters_enabled { |
| 1413 | views.extend([ | 1419 | views.extend([ |
| 1414 | &project_view.included_files_editor, | 1420 | &project_view.included_files_editor, |
| 1415 | &project_view.excluded_files_editor, | 1421 | &project_view.excluded_files_editor, |
| 1416 | ]); | 1422 | ]); |
| 1417 | } | 1423 | } |
| 1418 | if project_view.replace_enabled { | ||
| 1419 | views.push(&project_view.replacement_editor); | ||
| 1420 | } | ||
| 1421 | let current_index = match views | 1424 | let current_index = match views |
| 1422 | .iter() | 1425 | .iter() |
| 1423 | .enumerate() | 1426 | .enumerate() |
| 1424 | .find(|(_, view)| view.focus_handle(cx).is_focused(cx)) | 1427 | .find(|(_, view)| view.focus_handle(cx).is_focused(cx)) |
| 1425 | { | 1428 | { |
| 1426 | Some((index, _)) => index, | 1429 | Some((index, _)) => index, |
| 1427 | 1430 | None => return, | |
| 1428 | None => { | ||
| 1429 | return; | ||
| 1430 | } | ||
| 1431 | }; | 1431 | }; |
| 1432 | 1432 | ||
| 1433 | let new_index = match direction { | 1433 | let new_index = match direction { |
| @@ -1665,78 +1665,64 @@ impl Render for ProjectSearchBar { · 231 unmodified lines | |||
| 1665 | let search = search.read(cx); | 1665 | let search = search.read(cx); |
| 1666 | let semantic_is_available = SemanticIndex::enabled(cx); | 1666 | let semantic_is_available = SemanticIndex::enabled(cx); |
| 1667 | 1667 | ||
| 1668 | let query_column = v_flex().child( | 1668 | let query_column = h_flex() |
| 1669 | h_flex() | 1669 | .flex_1() |
| 1670 | .min_w(rems(512. / 16.)) | 1670 | .px_2() |
| 1671 | .px_2() | 1671 | .py_1() |
| 1672 | .py_1() | 1672 | .border_1() |
| 1673 | .gap_2() | 1673 | .border_color(search.border_color_for(InputPanel::Query, cx)) |
| 1674 | .bg(cx.theme().colors().editor_background) | 1674 | .rounded_lg() |
| 1675 | .border_1() | 1675 | .min_w(rems(MIN_INPUT_WIDTH_REMS)) |
| 1676 | .border_color(search.border_color_for(InputPanel::Query, cx)) | 1676 | .max_w(rems(MAX_INPUT_WIDTH_REMS)) |
| 1677 | .rounded_lg() | 1677 | .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) |
| 1678 | .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) | 1678 | .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx))) |
| 1679 | .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx))) | 1679 | .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) |
| 1680 | .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) | 1680 | .child(self.render_text_input(&search.query_editor, cx)) |
| 1681 | .child(Icon::new(IconName::MagnifyingGlass)) | 1681 | .child( |
| 1682 | .child(self.render_text_input(&search.query_editor, cx)) | 1682 | h_flex() |
| 1683 | .child( | 1683 | .child( |
| 1684 | h_flex() | 1684 | IconButton::new("project-search-filter-button", IconName::Filter) |
| 1685 | .tooltip(|cx| Tooltip::for_action("Toggle filters", &ToggleFilters, cx)) | ||
| 1686 | .on_click(cx.listener(|this, _, cx| { | ||
| 1687 | this.toggle_filters(cx); | ||
| 1688 | })) | ||
| 1689 | .selected( | ||
| 1690 | self.active_project_search | ||
| 1691 | .as_ref() | ||
| 1692 | .map(|search| search.read(cx).filters_enabled) | ||
| 1693 | .unwrap_or_default(), | ||
| 1694 | ), | ||
| 1695 | ) | ||
| 1696 | .when(search.current_mode != SearchMode::Semantic, |this| { | ||
| 1697 | this.child( | ||
| 1698 | IconButton::new( | ||
| 1699 | "project-search-case-sensitive", | ||
| 1700 | IconName::CaseSensitive, | ||
| 1701 | ) | ||
| 1702 | .tooltip(|cx| { | ||
| 1703 | Tooltip::for_action( | ||
| 1704 | "Toggle case sensitive", | ||
| 1705 | &ToggleCaseSensitive, | ||
| 1706 | cx, | ||
| 1707 | ) | ||
| 1708 | }) | ||
| 1709 | .selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx)) | ||
| 1710 | .on_click(cx.listener(|this, _, cx| { | ||
| 1711 | this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); | ||
| 1712 | })), | ||
| 1713 | ) | ||
| 1685 | .child( | 1714 | .child( |
| 1686 | IconButton::new("project-search-filter-button", IconName::Filter) | 1715 | IconButton::new("project-search-whole-word", IconName::WholeWord) |
| 1687 | .tooltip(|cx| { | 1716 | .tooltip(|cx| { |
| 1688 | Tooltip::for_action("Toggle filters", &ToggleFilters, cx) | 1717 | Tooltip::for_action("Toggle whole word", &ToggleWholeWord, cx) |
| 1689 | }) | 1718 | }) |
| 1719 | .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx)) | ||
| 1690 | .on_click(cx.listener(|this, _, cx| { | 1720 | .on_click(cx.listener(|this, _, cx| { |
| 1691 | this.toggle_filters(cx); | 1721 | this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); |
| 1692 | })) | 1722 | })), |
| 1693 | .selected( | ||
| 1694 | self.active_project_search | ||
| 1695 | .as_ref() | ||
| 1696 | .map(|search| search.read(cx).filters_enabled) | ||
| 1697 | .unwrap_or_default(), | ||
| 1698 | ), | ||
| 1699 | ) | 1723 | ) |
| 1700 | .when(search.current_mode != SearchMode::Semantic, |this| { | 1724 | }), |
| 1701 | this.child( | 1725 | ); |
| 1702 | IconButton::new( | ||
| 1703 | "project-search-case-sensitive", | ||
| 1704 | IconName::CaseSensitive, | ||
| 1705 | ) | ||
| 1706 | .tooltip(|cx| { | ||
| 1707 | Tooltip::for_action( | ||
| 1708 | "Toggle case sensitive", | ||
| 1709 | &ToggleCaseSensitive, | ||
| 1710 | cx, | ||
| 1711 | ) | ||
| 1712 | }) | ||
| 1713 | .selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx)) | ||
| 1714 | .on_click(cx.listener( | ||
| 1715 | |this, _, cx| { | ||
| 1716 | this.toggle_search_option( | ||
| 1717 | SearchOptions::CASE_SENSITIVE, | ||
| 1718 | cx, | ||
| 1719 | ); | ||
| 1720 | }, | ||
| 1721 | )), | ||
| 1722 | ) | ||
| 1723 | .child( | ||
| 1724 | IconButton::new("project-search-whole-word", IconName::WholeWord) | ||
| 1725 | .tooltip(|cx| { | ||
| 1726 | Tooltip::for_action( | ||
| 1727 | "Toggle whole word", | ||
| 1728 | &ToggleWholeWord, | ||
| 1729 | cx, | ||
| 1730 | ) | ||
| 1731 | }) | ||
| 1732 | .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx)) | ||
| 1733 | .on_click(cx.listener(|this, _, cx| { | ||
| 1734 | this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); | ||
| 1735 | })), | ||
| 1736 | ) | ||
| 1737 | }), | ||
| 1738 | ), | ||
| 1739 | ); | ||
| 1740 | 1726 | ||
| 1741 | let mode_column = v_flex().items_start().justify_start().child( | 1727 | let mode_column = v_flex().items_start().justify_start().child( |
| 1742 | h_flex() | 1728 | h_flex() |
| @@ -1807,56 +1793,23 @@ impl Render for ProjectSearchBar { · 64 unmodified lines | |||
| 1807 | .tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)), | 1793 | .tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)), |
| 1808 | ), | 1794 | ), |
| 1809 | ); | 1795 | ); |
| 1810 | let replace_column = if search.replace_enabled { | 1796 | |
| 1811 | h_flex() | 1797 | let match_text = search |
| 1812 | .flex_1() | 1798 | .active_match_index |
| 1813 | .h_full() | 1799 | .and_then(|index| { |
| 1814 | .gap_2() | ||
| 1815 | .px_2() | ||
| 1816 | .py_1() | ||
| 1817 | .border_1() | ||
| 1818 | .border_color(cx.theme().colors().border) | ||
| 1819 | .rounded_lg() | ||
| 1820 | .child(Icon::new(IconName::Replace).size(ui::IconSize::Small)) | ||
| 1821 | .child(self.render_text_input(&search.replacement_editor, cx)) | ||
| 1822 | } else { | ||
| 1823 | // Fill out the space if we don't have a replacement editor. | ||
| 1824 | h_flex().flex_1() | ||
| 1825 | }; | ||
| 1826 | let actions_column = h_flex() | ||
| 1827 | .when(search.replace_enabled, |this| { | ||
| 1828 | this.child( | ||
| 1829 | IconButton::new("project-search-replace-next", IconName::ReplaceNext) | ||
| 1830 | .on_click(cx.listener(|this, _, cx| { | ||
| 1831 | if let Some(search) = this.active_project_search.as_ref() { | ||
| 1832 | search.update(cx, |this, cx| { | ||
| 1833 | this.replace_next(&ReplaceNext, cx); | ||
| 1834 | }) | ||
| 1835 | } | ||
| 1836 | })) | ||
| 1837 | .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)), | ||
| 1838 | ) | ||
| 1839 | .child( | ||
| 1840 | IconButton::new("project-search-replace-all", IconName::ReplaceAll) | ||
| 1841 | .on_click(cx.listener(|this, _, cx| { | ||
| 1842 | if let Some(search) = this.active_project_search.as_ref() { | ||
| 1843 | search.update(cx, |this, cx| { | ||
| 1844 | this.replace_all(&ReplaceAll, cx); | ||
| 1845 | }) | ||
| 1846 | } | ||
| 1847 | })) | ||
| 1848 | .tooltip(|cx| Tooltip::for_action("Replace all matches", &ReplaceAll, cx)), | ||
| 1849 | ) | ||
| 1850 | }) | ||
| 1851 | .when_some(search.active_match_index, |mut this, index| { | ||
| 1852 | let index = index + 1; | 1800 | let index = index + 1; |
| 1853 | let match_quantity = search.model.read(cx).match_ranges.len(); | 1801 | let match_quantity = search.model.read(cx).match_ranges.len(); |
| 1854 | if match_quantity > 0 { | 1802 | if match_quantity > 0 { |
| 1855 | debug_assert!(match_quantity >= index); | 1803 | debug_assert!(match_quantity >= index); |
| 1856 | this = this.child(Label::new(format!("{index}/{match_quantity}"))) | 1804 | Some(format!("{index}/{match_quantity}").to_string()) |
| 1805 | } else { | ||
| 1806 | None | ||
| 1857 | } | 1807 | } |
| 1858 | this | ||
| 1859 | }) | 1808 | }) |
| 1809 | .unwrap_or_else(|| "No matches".to_string()); | ||
| 1810 | |||
| 1811 | let matches_column = h_flex() | ||
| 1812 | .child(div().min_w(rems(6.)).child(Label::new(match_text))) | ||
| 1860 | .child( | 1813 | .child( |
| 1861 | IconButton::new("project-search-prev-match", IconName::ChevronLeft) | 1814 | IconButton::new("project-search-prev-match", IconName::ChevronLeft) |
| 1862 | .disabled(search.active_match_index.is_none()) | 1815 | .disabled(search.active_match_index.is_none()) |
| @@ -1884,10 +1837,104 @@ impl Render for ProjectSearchBar { · 21 unmodified lines | |||
| 1884 | .tooltip(|cx| Tooltip::for_action("Go to next match", &SelectNextMatch, cx)), | 1837 | .tooltip(|cx| Tooltip::for_action("Go to next match", &SelectNextMatch, cx)), |
| 1885 | ); | 1838 | ); |
| 1886 | 1839 | ||
| 1840 | let search_line = h_flex() | ||
| 1841 | .gap_2() | ||
| 1842 | .flex_1() | ||
| 1843 | .child(query_column) | ||
| 1844 | .child(mode_column) | ||
| 1845 | .child(matches_column); | ||
| 1846 | |||
| 1847 | let replace_line = search.replace_enabled.then(|| { | ||
| 1848 | let replace_column = h_flex() | ||
| 1849 | .flex_1() | ||
| 1850 | .min_w(rems(MIN_INPUT_WIDTH_REMS)) | ||
| 1851 | .max_w(rems(MAX_INPUT_WIDTH_REMS)) | ||
| 1852 | .h_8() | ||
| 1853 | .px_2() | ||
| 1854 | .py_1() | ||
| 1855 | .border_1() | ||
| 1856 | .border_color(cx.theme().colors().border) | ||
| 1857 | .rounded_lg() | ||
| 1858 | .child(self.render_text_input(&search.replacement_editor, cx)); | ||
| 1859 | let replace_actions = h_flex().when(search.replace_enabled, |this| { | ||
| 1860 | this.child( | ||
| 1861 | IconButton::new("project-search-replace-next", IconName::ReplaceNext) | ||
| 1862 | .on_click(cx.listener(|this, _, cx| { | ||
| 1863 | if let Some(search) = this.active_project_search.as_ref() { | ||
| 1864 | search.update(cx, |this, cx| { | ||
| 1865 | this.replace_next(&ReplaceNext, cx); | ||
| 1866 | }) | ||
| 1867 | } | ||
| 1868 | })) | ||
| 1869 | .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)), | ||
| 1870 | ) | ||
| 1871 | .child( | ||
| 1872 | IconButton::new("project-search-replace-all", IconName::ReplaceAll) | ||
| 1873 | .on_click(cx.listener(|this, _, cx| { | ||
| 1874 | if let Some(search) = this.active_project_search.as_ref() { | ||
| 1875 | search.update(cx, |this, cx| { | ||
| 1876 | this.replace_all(&ReplaceAll, cx); | ||
| 1877 | }) | ||
| 1878 | } | ||
| 1879 | })) | ||
| 1880 | .tooltip(|cx| Tooltip::for_action("Replace all matches", &ReplaceAll, cx)), | ||
| 1881 | ) | ||
| 1882 | }); | ||
| 1883 | h_flex() | ||
| 1884 | .gap_2() | ||
| 1885 | .child(replace_column) | ||
| 1886 | .child(replace_actions) | ||
| 1887 | }); | ||
| 1888 | |||
| 1889 | let filter_line = search.filters_enabled.then(|| { | ||
| 1890 | h_flex() | ||
| 1891 | .w_full() | ||
| 1892 | .gap_2() | ||
| 1893 | .child( | ||
| 1894 | h_flex() | ||
| 1895 | .flex_1() | ||
| 1896 | .min_w(rems(MIN_INPUT_WIDTH_REMS)) | ||
| 1897 | .max_w(rems(MAX_INPUT_WIDTH_REMS)) | ||
| 1898 | .h_8() | ||
| 1899 | .px_2() | ||
| 1900 | .py_1() | ||
| 1901 | .border_1() | ||
| 1902 | .border_color(search.border_color_for(InputPanel::Include, cx)) | ||
| 1903 | .rounded_lg() | ||
| 1904 | .child(self.render_text_input(&search.included_files_editor, cx)) | ||
| 1905 | .when(search.current_mode != SearchMode::Semantic, |this| { | ||
| 1906 | this.child( | ||
| 1907 | SearchOptions::INCLUDE_IGNORED.as_button( | ||
| 1908 | search | ||
| 1909 | .search_options | ||
| 1910 | .contains(SearchOptions::INCLUDE_IGNORED), | ||
| 1911 | cx.listener(|this, _, cx| { | ||
| 1912 | this.toggle_search_option( | ||
| 1913 | SearchOptions::INCLUDE_IGNORED, | ||
| 1914 | cx, | ||
| 1915 | ); | ||
| 1916 | }), | ||
| 1917 | ), | ||
| 1918 | ) | ||
| 1919 | }), | ||
| 1920 | ) | ||
| 1921 | .child( | ||
| 1922 | h_flex() | ||
| 1923 | .flex_1() | ||
| 1924 | .min_w(rems(MIN_INPUT_WIDTH_REMS)) | ||
| 1925 | .max_w(rems(MAX_INPUT_WIDTH_REMS)) | ||
| 1926 | .h_8() | ||
| 1927 | .px_2() | ||
| 1928 | .py_1() | ||
| 1929 | .border_1() | ||
| 1930 | .border_color(search.border_color_for(InputPanel::Exclude, cx)) | ||
| 1931 | .rounded_lg() | ||
| 1932 | .child(self.render_text_input(&search.excluded_files_editor, cx)), | ||
| 1933 | ) | ||
| 1934 | }); | ||
| 1935 | |||
| 1887 | v_flex() | 1936 | v_flex() |
| 1888 | .key_context(key_context) | 1937 | .key_context(key_context) |
| 1889 | .flex_grow() | ||
| 1890 | .gap_2() | ||
| 1891 | .on_action(cx.listener(|this, _: &ToggleFocus, cx| this.move_focus_to_results(cx))) | 1938 | .on_action(cx.listener(|this, _: &ToggleFocus, cx| this.move_focus_to_results(cx))) |
| 1892 | .on_action(cx.listener(|this, _: &ToggleFilters, cx| { | 1939 | .on_action(cx.listener(|this, _: &ToggleFilters, cx| { |
| 1893 | this.toggle_filters(cx); | 1940 | this.toggle_filters(cx); |
| @@ -1945,60 +1992,11 @@ impl Render for ProjectSearchBar { · 51 unmodified lines | |||
| 1945 | }) | 1992 | }) |
| 1946 | .on_action(cx.listener(Self::select_next_match)) | 1993 | .on_action(cx.listener(Self::select_next_match)) |
| 1947 | .on_action(cx.listener(Self::select_prev_match)) | 1994 | .on_action(cx.listener(Self::select_prev_match)) |
| 1948 | .child( | 1995 | .gap_2() |
| 1949 | h_flex() | 1996 | .w_full() |
| 1950 | .justify_between() | 1997 | .child(search_line) |
| 1951 | .gap_2() | 1998 | .children(replace_line) |
| 1952 | .child(query_column) | 1999 | .children(filter_line) |
| 1953 | .child(mode_column) | ||
| 1954 | .child(replace_column) | ||
| 1955 | .child(actions_column), | ||
| 1956 | ) | ||
| 1957 | .when(search.filters_enabled, |this| { | ||
| 1958 | this.child( | ||
| 1959 | h_flex() | ||
| 1960 | .flex_1() | ||
| 1961 | .gap_2() | ||
| 1962 | .justify_between() | ||
| 1963 | .child( | ||
| 1964 | h_flex() | ||
| 1965 | .flex_1() | ||
| 1966 | .h_full() | ||
| 1967 | .px_2() | ||
| 1968 | .py_1() | ||
| 1969 | .border_1() | ||
| 1970 | .border_color(search.border_color_for(InputPanel::Include, cx)) | ||
| 1971 | .rounded_lg() | ||
| 1972 | .child(self.render_text_input(&search.included_files_editor, cx)) | ||
| 1973 | .when(search.current_mode != SearchMode::Semantic, |this| { | ||
| 1974 | this.child( | ||
| 1975 | SearchOptions::INCLUDE_IGNORED.as_button( | ||
| 1976 | search | ||
| 1977 | .search_options | ||
| 1978 | .contains(SearchOptions::INCLUDE_IGNORED), | ||
| 1979 | cx.listener(|this, _, cx| { | ||
| 1980 | this.toggle_search_option( | ||
| 1981 | SearchOptions::INCLUDE_IGNORED, | ||
| 1982 | cx, | ||
| 1983 | ); | ||
| 1984 | }), | ||
| 1985 | ), | ||
| 1986 | ) | ||
| 1987 | }), | ||
| 1988 | ) | ||
| 1989 | .child( | ||
| 1990 | h_flex() | ||
| 1991 | .flex_1() | ||
| 1992 | .h_full() | ||
| 1993 | .px_2() | ||
| 1994 | .py_1() | ||
| 1995 | .border_1() | ||
| 1996 | .border_color(search.border_color_for(InputPanel::Exclude, cx)) | ||
| 1997 | .rounded_lg() | ||
| 1998 | .child(self.render_text_input(&search.excluded_files_editor, cx)), | ||
| 1999 | ), | ||
| 2000 | ) | ||
| 2001 | }) | ||
| 2002 | } | 2000 | } |
| 2003 | } | 2001 | } |
| 2004 | 2002 | ||