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 | 38 | use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults}; |
| 39 | 39 | |
| 40 | 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 | 43 | #[derive(PartialEq, Clone, Deserialize)] |
| 44 | 44 | pub struct Deploy { |
| @@ -217,7 +217,6 @@ impl Render for BufferSearchBar { · 172 unmodified lines | ||
| 217 | 217 | .flex_1() |
| 218 | 218 | .px_2() |
| 219 | 219 | .py_1() |
| 220 | − .gap_2() | |
| 221 | 220 | .border_1() |
| 222 | 221 | .border_color(editor_border) |
| 223 | 222 | .min_w(rems(MIN_INPUT_WIDTH_REMS)) |
| @@ -50,6 +50,9 @@ use workspace::{ · 49 unmodified lines | ||
| 50 | 50 | WorkspaceId, |
| 51 | 51 | }; |
| 52 | 52 | |
| 53 | +const MIN_INPUT_WIDTH_REMS: f32 = 15.; | |
| 54 | +const MAX_INPUT_WIDTH_REMS: f32 = 30.; | |
| 55 | + | |
| 53 | 56 | actions!( |
| 54 | 57 | project_search, |
| 55 | 58 | [SearchInNew, ToggleFocus, NextField, ToggleFilters] |
| @@ -1409,25 +1412,22 @@ impl ProjectSearchBar { · 1353 unmodified lines | ||
| 1409 | 1412 | |
| 1410 | 1413 | active_project_search.update(cx, |project_view, cx| { |
| 1411 | 1414 | let mut views = vec![&project_view.query_editor]; |
| 1415 | + if project_view.replace_enabled { | |
| 1416 | + views.push(&project_view.replacement_editor); | |
| 1417 | + } | |
| 1412 | 1418 | if project_view.filters_enabled { |
| 1413 | 1419 | views.extend([ |
| 1414 | 1420 | &project_view.included_files_editor, |
| 1415 | 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 | 1424 | let current_index = match views |
| 1422 | 1425 | .iter() |
| 1423 | 1426 | .enumerate() |
| 1424 | 1427 | .find(|(_, view)| view.focus_handle(cx).is_focused(cx)) |
| 1425 | 1428 | { |
| 1426 | 1429 | Some((index, _)) => index, |
| 1427 | − | |
| 1428 | − None => { | |
| 1429 | − return; | |
| 1430 | − } | |
| 1430 | + None => return, | |
| 1431 | 1431 | }; |
| 1432 | 1432 | |
| 1433 | 1433 | let new_index = match direction { |
| @@ -1665,78 +1665,64 @@ impl Render for ProjectSearchBar { · 231 unmodified lines | ||
| 1665 | 1665 | let search = search.read(cx); |
| 1666 | 1666 | let semantic_is_available = SemanticIndex::enabled(cx); |
| 1667 | 1667 | |
| 1668 | − let query_column = v_flex().child( | |
| 1669 | − h_flex() | |
| 1670 | − .min_w(rems(512. / 16.)) | |
| 1671 | − .px_2() | |
| 1672 | − .py_1() | |
| 1673 | − .gap_2() | |
| 1674 | − .bg(cx.theme().colors().editor_background) | |
| 1675 | − .border_1() | |
| 1676 | − .border_color(search.border_color_for(InputPanel::Query, cx)) | |
| 1677 | − .rounded_lg() | |
| 1678 | − .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) | |
| 1679 | − .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx))) | |
| 1680 | − .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) | |
| 1681 | − .child(Icon::new(IconName::MagnifyingGlass)) | |
| 1682 | − .child(self.render_text_input(&search.query_editor, cx)) | |
| 1683 | − .child( | |
| 1684 | − h_flex() | |
| 1668 | + let query_column = h_flex() | |
| 1669 | + .flex_1() | |
| 1670 | + .px_2() | |
| 1671 | + .py_1() | |
| 1672 | + .border_1() | |
| 1673 | + .border_color(search.border_color_for(InputPanel::Query, cx)) | |
| 1674 | + .rounded_lg() | |
| 1675 | + .min_w(rems(MIN_INPUT_WIDTH_REMS)) | |
| 1676 | + .max_w(rems(MAX_INPUT_WIDTH_REMS)) | |
| 1677 | + .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.next_history_query(action, cx))) | |
| 1680 | + .child(self.render_text_input(&search.query_editor, cx)) | |
| 1681 | + .child( | |
| 1682 | + h_flex() | |
| 1683 | + .child( | |
| 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 | 1714 | .child( |
| 1686 | − IconButton::new("project-search-filter-button", IconName::Filter) | |
| 1715 | + IconButton::new("project-search-whole-word", IconName::WholeWord) | |
| 1687 | 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 | 1720 | .on_click(cx.listener(|this, _, cx| { |
| 1691 | − this.toggle_filters(cx); | |
| 1692 | − })) | |
| 1693 | − .selected( | |
| 1694 | − self.active_project_search | |
| 1695 | − .as_ref() | |
| 1696 | − .map(|search| search.read(cx).filters_enabled) | |
| 1697 | − .unwrap_or_default(), | |
| 1698 | − ), | |
| 1721 | + this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); | |
| 1722 | + })), | |
| 1699 | 1723 | ) |
| 1700 | − .when(search.current_mode != SearchMode::Semantic, |this| { | |
| 1701 | − this.child( | |
| 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 | − ); | |
| 1724 | + }), | |
| 1725 | + ); | |
| 1740 | 1726 | |
| 1741 | 1727 | let mode_column = v_flex().items_start().justify_start().child( |
| 1742 | 1728 | h_flex() |
| @@ -1807,56 +1793,23 @@ impl Render for ProjectSearchBar { · 64 unmodified lines | ||
| 1807 | 1793 | .tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)), |
| 1808 | 1794 | ), |
| 1809 | 1795 | ); |
| 1810 | − let replace_column = if search.replace_enabled { | |
| 1811 | − h_flex() | |
| 1812 | − .flex_1() | |
| 1813 | − .h_full() | |
| 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| { | |
| 1796 | + | |
| 1797 | + let match_text = search | |
| 1798 | + .active_match_index | |
| 1799 | + .and_then(|index| { | |
| 1852 | 1800 | let index = index + 1; |
| 1853 | 1801 | let match_quantity = search.model.read(cx).match_ranges.len(); |
| 1854 | 1802 | if match_quantity > 0 { |
| 1855 | 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 | 1813 | .child( |
| 1861 | 1814 | IconButton::new("project-search-prev-match", IconName::ChevronLeft) |
| 1862 | 1815 | .disabled(search.active_match_index.is_none()) |
| @@ -1884,10 +1837,104 @@ impl Render for ProjectSearchBar { · 21 unmodified lines | ||
| 1884 | 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 | 1936 | v_flex() |
| 1888 | 1937 | .key_context(key_context) |
| 1889 | − .flex_grow() | |
| 1890 | − .gap_2() | |
| 1891 | 1938 | .on_action(cx.listener(|this, _: &ToggleFocus, cx| this.move_focus_to_results(cx))) |
| 1892 | 1939 | .on_action(cx.listener(|this, _: &ToggleFilters, cx| { |
| 1893 | 1940 | this.toggle_filters(cx); |
| @@ -1945,60 +1992,11 @@ impl Render for ProjectSearchBar { · 51 unmodified lines | ||
| 1945 | 1992 | }) |
| 1946 | 1993 | .on_action(cx.listener(Self::select_next_match)) |
| 1947 | 1994 | .on_action(cx.listener(Self::select_prev_match)) |
| 1948 | − .child( | |
| 1949 | − h_flex() | |
| 1950 | − .justify_between() | |
| 1951 | − .gap_2() | |
| 1952 | − .child(query_column) | |
| 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 | − }) | |
| 1995 | + .gap_2() | |
| 1996 | + .w_full() | |
| 1997 | + .child(search_line) | |
| 1998 | + .children(replace_line) | |
| 1999 | + .children(filter_line) | |
| 2002 | 2000 | } |
| 2003 | 2001 | } |
| 2004 | 2002 | |