Make BufferSearch less wide (#10459)
This also adds some "responsiveness" so that UI elements are hidden before everything has to be occluded Release Notes: - Improved search UI. It now works in narrower panes, and avoids scrolling the editor on open. <img width="899" alt="Screenshot 2024-04-11 at 21 33 17" src="https://github.com/zed-industries/zed/assets/94272/44b95d4f-08d6-4c40-a175-0e594402ca01"> <img width="508" alt="Screenshot 2024-04-11 at 21 33 45" src="https://github.com/zed-industries/zed/assets/94272/baf4638d-427b-43e6-ad67-13d43f0f18a2"> <img width="361" alt="Screenshot 2024-04-11 at 21 34 00" src="https://github.com/zed-industries/zed/assets/94272/ff60b561-2f77-49c0-9df7-e26227fe9225"> <img width="348" alt="Screenshot 2024-04-11 at 21 37 03" src="https://github.com/zed-industries/zed/assets/94272/a2a700a2-ce99-41bd-bf47-9b14d7082b0e">
This commit is contained in:
parent
f2d61f3ea5
commit
08786fa7bf
18 changed files with 326 additions and 482 deletions
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
Thanks for your interest in contributing to Zed, the collaborative platform that is also a code editor!
|
Thanks for your interest in contributing to Zed, the collaborative platform that is also a code editor!
|
||||||
|
|
||||||
We want to avoid anyone spending time on a pull request that may not be accepted, so we suggest you discuss your ideas with the team and community before starting on major changes. Bug fixes, however, are almost always welcome.
|
|
||||||
|
|
||||||
All activity in Zed forums is subject to our [Code of Conduct](https://zed.dev/docs/code-of-conduct). Additionally, contributors must sign our [Contributor License Agreement](https://zed.dev/cla) before their contributions can be merged.
|
All activity in Zed forums is subject to our [Code of Conduct](https://zed.dev/docs/code-of-conduct). Additionally, contributors must sign our [Contributor License Agreement](https://zed.dev/cla) before their contributions can be merged.
|
||||||
|
|
||||||
## Contribution ideas
|
## Contribution ideas
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Zed
|
# Zed
|
||||||
|
|
||||||
[](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
|
[](https://github.com/zed-industries/ze34actions/workflows/ci.yml)
|
||||||
|
|
||||||
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).
|
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).
|
||||||
|
|
||||||
|
|
4
assets/icons/regex.svg
Normal file
4
assets/icons/regex.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="4" cy="11" r="1" fill="#787D87"/>
|
||||||
|
<path d="M9 2.5V5M9 5V7.5M9 5H11.5M9 5H6.5M9 5L10.6667 3.33333M9 5L7.33333 6.6667M9 5L10.6667 6.6667M9 5L7.33333 3.33333" stroke="#787D87" stroke-width="1.25" stroke-linecap="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 333 B |
|
@ -1,5 +1,5 @@
|
||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M9.5 7V9.5M9.5 12V9.5M12 9.5H9.5M7 9.5H9.5M9.5 9.5L11.1667 7.83333M9.5 9.5L7.83333 11.1667M9.5 9.5L11.1667 11.1667M9.5 9.5L7.83333 7.83333" stroke="#11181C" stroke-width="1.25" stroke-linecap="round"/>
|
<path d="M9.5 7V9.5M9.5 9.5V12M9.5 9.5H12M9.5 9.5H7M9.5 9.5L11.1667 7.83333M9.5 9.5L7.83333 11.1667M9.5 9.5L11.1667 11.1667M9.5 9.5L7.83333 7.83333" stroke="#687076" stroke-width="1.25" stroke-linecap="round"/>
|
||||||
<path d="M2.19366 3.84943C2.19188 4.26418 2.32864 4.59864 2.60673 4.84707C2.88052 5.09166 3.25136 5.26933 3.71609 5.3824C3.71616 5.38242 3.71623 5.38243 3.7163 5.38245L4.30919 5.53134L4.30919 5.53134L4.30965 5.53145C4.50649 5.57891 4.67124 5.63133 4.80447 5.68843L4.80469 5.68852C4.93838 5.74508 5.03564 5.81206 5.10001 5.8877L5.10001 5.8877L5.10041 5.88816C5.16432 5.96142 5.19716 6.05222 5.19716 6.16389C5.19716 6.28412 5.1609 6.38933 5.0882 6.48141C5.01496 6.57418 4.91031 6.64838 4.77141 6.70259L4.77121 6.70266C4.63472 6.75659 4.47185 6.7843 4.28146 6.7843C4.08801 6.7843 3.91607 6.75496 3.76491 6.69726C3.61654 6.6382 3.49924 6.55209 3.41132 6.43942C3.3502 6.35821 3.30747 6.26204 3.28375 6.14992C3.26238 6.04888 3.1772 5.96225 3.06518 5.96225H2.26366C2.14682 5.96225 2.04842 6.05919 2.0592 6.18012C2.08842 6.50802 2.1826 6.79102 2.34331 7.02735L2.34352 7.02767C2.53217 7.30057 2.79377 7.50587 3.12633 7.64399L3.12642 7.64402C3.46009 7.78185 3.84993 7.85 4.29476 7.85C4.74293 7.85 5.12859 7.7828 5.45023 7.64651L5.45036 7.64646C5.77328 7.50857 6.02259 7.31417 6.19551 7.06217C6.37037 6.80817 6.4579 6.50901 6.45972 6.16682L6.45972 6.16616C6.4579 5.9333 6.41513 5.72482 6.33012 5.54178C6.2474 5.35987 6.13061 5.20175 5.98007 5.06773C5.83038 4.93448 5.65389 4.82273 5.4511 4.7322C5.24919 4.64206 5.02795 4.57016 4.78757 4.51632L4.29841 4.39935L4.29841 4.39934L4.29771 4.39919C4.18081 4.37301 4.07116 4.34168 3.9687 4.30523C3.86715 4.26734 3.77847 4.22375 3.70232 4.17471C3.62796 4.12508 3.57037 4.06717 3.52849 4.00124C3.49012 3.93815 3.47157 3.86312 3.47481 3.77407L3.47484 3.77407V3.77225C3.47484 3.66563 3.50527 3.57146 3.56612 3.48808C3.6287 3.40475 3.71977 3.33801 3.84235 3.28931L3.84235 3.28932L3.84289 3.28909C3.96465 3.23906 4.1165 3.21304 4.30008 3.21304C4.57006 3.21304 4.77746 3.27105 4.92754 3.38154C5.04235 3.46608 5.11838 3.57594 5.15673 3.71259C5.18352 3.80802 5.26636 3.89142 5.37611 3.89142H6.17259C6.28852 3.89142 6.38806 3.7953 6.37515 3.67382C6.34686 3.4077 6.26051 3.16831 6.1158 2.95658C5.94159 2.70169 5.6982 2.50368 5.38762 2.36201L5.36687 2.4075M2.19366 3.84943C2.19187 3.51004 2.28242 3.21139 2.46644 2.9556L2.46658 2.9554C2.65148 2.70093 2.90447 2.50326 3.22368 2.36179C3.54316 2.2202 3.90494 2.15 4.30807 2.15C4.71809 2.15 5.07841 2.22014 5.38773 2.36206L5.36687 2.4075M2.19366 3.84943C2.19366 3.84951 2.19366 3.84959 2.19366 3.84967L2.24366 3.8494L2.19366 3.84918C2.19366 3.84926 2.19366 3.84935 2.19366 3.84943ZM5.36687 2.4075C5.06537 2.26917 4.71244 2.2 4.30807 2.2C3.91079 2.2 3.55608 2.26917 3.24394 2.4075C2.93179 2.54584 2.68616 2.73827 2.50703 2.9848L3.82389 3.24285L3.82389 3.24285C3.95336 3.18964 4.11209 3.16304 4.30008 3.16304C4.57676 3.16304 4.79579 3.22245 4.95718 3.34128C5.08094 3.43239 5.1635 3.55166 5.20487 3.69908C5.2271 3.77827 5.29386 3.84142 5.37611 3.84142H6.17259C6.26198 3.84142 6.33488 3.76799 6.32543 3.6791C6.29797 3.4208 6.21433 3.18936 6.07452 2.9848C5.90603 2.73827 5.67015 2.54584 5.36687 2.4075ZM4.78958 6.74917C4.64593 6.80592 4.47655 6.8343 4.28146 6.8343C4.08283 6.8343 3.90458 6.80415 3.74674 6.74384C3.59067 6.68177 3.46563 6.59043 3.37163 6.46983L4.78958 6.74917ZM4.78958 6.74917C4.93502 6.69241 5.04764 6.61349 5.12745 6.5124M4.78958 6.74917L5.12745 6.5124M5.12745 6.5124C5.20726 6.4113 5.24716 6.29514 5.24716 6.16389M5.12745 6.5124L5.24716 6.16389M5.24716 6.16389C5.24716 6.04152 5.2108 5.93865 5.13809 5.85529L5.24716 6.16389Z" fill="#687076" stroke="#687076" stroke-width="0.1"/>
|
<path d="M2.19368 3.84945C2.1919 4.2642 2.32866 4.59866 2.60675 4.84709C2.88054 5.09168 3.25138 5.26935 3.71611 5.38242L4.30921 5.53136C4.50605 5.57882 4.67126 5.63135 4.80449 5.68845C4.93818 5.74501 5.03566 5.81208 5.10003 5.88772C5.16394 5.96098 5.19718 6.05224 5.19718 6.16391C5.19718 6.28414 5.16092 6.38935 5.08822 6.48143C5.01498 6.5742 4.91033 6.6484 4.77143 6.70261C4.63494 6.75654 4.47187 6.78432 4.28148 6.78432C4.08803 6.78432 3.91609 6.75498 3.76493 6.69728C3.61656 6.63822 3.49926 6.55211 3.41134 6.43944C3.35022 6.35823 3.30749 6.26206 3.28377 6.14994C3.2624 6.0489 3.17722 5.96227 3.0652 5.96227H2.26368C2.14684 5.96227 2.04844 6.05921 2.05922 6.18014C2.08844 6.50804 2.18262 6.79104 2.34333 7.02737C2.53198 7.30027 2.79379 7.50589 3.12635 7.64401C3.46002 7.78184 3.84995 7.85002 4.29478 7.85002C4.74295 7.85002 5.12861 7.78282 5.45025 7.64653C5.77317 7.50864 6.02261 7.31419 6.19553 7.06219C6.37039 6.80819 6.45792 6.50903 6.45974 6.16684C6.45792 5.93398 6.41515 5.72484 6.33014 5.5418C6.24742 5.35989 6.13063 5.20177 5.98009 5.06775C5.8304 4.9345 5.65391 4.82275 5.45112 4.73222C5.24921 4.64208 5.02797 4.57018 4.78759 4.51634L4.29843 4.39937C4.18153 4.37319 4.07118 4.3417 3.96872 4.30525C3.86717 4.26736 3.77849 4.22377 3.70234 4.17473C3.62798 4.1251 3.57039 4.06719 3.52851 4.00126C3.49014 3.93817 3.47159 3.86314 3.47483 3.77409L3.47486 3.77227C3.47486 3.66565 3.50529 3.57148 3.56614 3.4881C3.62872 3.40477 3.71979 3.33803 3.84237 3.28933C3.96413 3.2393 4.11652 3.21306 4.3001 3.21306C4.57008 3.21306 4.77748 3.27107 4.92756 3.38156C5.04237 3.4661 5.1184 3.57596 5.15675 3.71261C5.18354 3.80804 5.26638 3.89144 5.37613 3.89144H6.17261C6.28854 3.89144 6.38808 3.79532 6.37517 3.67384C6.34688 3.40772 6.26053 3.16833 6.11582 2.9566C5.94161 2.70171 5.69822 2.5037 5.38764 2.36203L5.36689 2.40752M2.19368 3.84945C2.19189 3.51006 2.28244 3.21141 2.46646 2.95562C2.65136 2.70115 2.90449 2.50328 3.2237 2.36181C3.54318 2.22022 3.90496 2.15002 4.30809 2.15002C4.71811 2.15002 5.07832 2.22011 5.38764 2.36203L5.36689 2.40752M4.7896 6.74919C4.93504 6.69243 5.04766 6.61351 5.12747 6.51242ZM4.7896 6.74919L5.12747 6.51242ZM5.12747 6.51242C5.20728 6.41132 5.24718 6.29516 5.24718 6.16391ZM5.12747 6.51242L5.24718 6.16391ZM5.24718 6.16391C5.24718 6.04154 5.21082 5.93867 5.13811 5.85531L5.24718 6.16391Z" fill="#687076"/>
|
||||||
|
<path d="M2.19368 3.84945C2.1919 4.2642 2.32866 4.59866 2.60675 4.84709C2.88054 5.09168 3.25138 5.26935 3.71611 5.38242L4.30921 5.53136C4.50605 5.57882 4.67126 5.63135 4.80449 5.68845C4.93818 5.74501 5.03566 5.81208 5.10003 5.88772C5.16394 5.96098 5.19718 6.05224 5.19718 6.16391C5.19718 6.28414 5.16092 6.38935 5.08822 6.48143C5.01498 6.5742 4.91033 6.6484 4.77143 6.70261C4.63494 6.75654 4.47187 6.78432 4.28148 6.78432C4.08803 6.78432 3.91609 6.75498 3.76493 6.69728C3.61656 6.63822 3.49926 6.55211 3.41134 6.43944C3.35022 6.35823 3.30749 6.26206 3.28377 6.14994C3.2624 6.0489 3.17722 5.96227 3.0652 5.96227H2.26368C2.14684 5.96227 2.04844 6.05921 2.05922 6.18014C2.08844 6.50804 2.18262 6.79104 2.34333 7.02737C2.53198 7.30027 2.79379 7.50589 3.12635 7.64401C3.46002 7.78184 3.84995 7.85002 4.29478 7.85002C4.74295 7.85002 5.12861 7.78282 5.45025 7.64653C5.77317 7.50864 6.02261 7.31419 6.19553 7.06219C6.37039 6.80819 6.45792 6.50903 6.45974 6.16684C6.45792 5.93398 6.41515 5.72484 6.33014 5.5418C6.24742 5.35989 6.13063 5.20177 5.98009 5.06775C5.8304 4.9345 5.65391 4.82275 5.45112 4.73222C5.24921 4.64208 5.02797 4.57018 4.78759 4.51634L4.29843 4.39937C4.18153 4.37319 4.07118 4.3417 3.96872 4.30525C3.86717 4.26736 3.77849 4.22377 3.70234 4.17473C3.62798 4.1251 3.57039 4.06719 3.52851 4.00126C3.49014 3.93817 3.47159 3.86314 3.47483 3.77409L3.47486 3.77227C3.47486 3.66565 3.50529 3.57148 3.56614 3.4881C3.62872 3.40477 3.71979 3.33803 3.84237 3.28933C3.96413 3.2393 4.11652 3.21306 4.3001 3.21306C4.57008 3.21306 4.77748 3.27107 4.92756 3.38156C5.04237 3.4661 5.1184 3.57596 5.15675 3.71261C5.18354 3.80804 5.26638 3.89144 5.37613 3.89144H6.17261C6.28854 3.89144 6.38808 3.79532 6.37517 3.67384C6.34688 3.40772 6.26053 3.16833 6.11582 2.9566C5.94161 2.70171 5.69822 2.5037 5.38764 2.36203M2.19368 3.84945C2.19189 3.51006 2.28244 3.21141 2.46646 2.95562C2.65136 2.70115 2.90449 2.50328 3.2237 2.36181C3.54318 2.22022 3.90496 2.15002 4.30809 2.15002C4.71811 2.15002 5.07832 2.22011 5.38764 2.36203M2.19368 3.84945L2.24368 3.84942M5.38764 2.36203L5.36689 2.40752M5.36689 2.40752C5.06539 2.26919 4.71246 2.20002 4.30809 2.20002C3.91081 2.20002 3.5561 2.26919 3.24396 2.40752C2.93181 2.54586 2.68618 2.73829 2.50705 2.98482M5.36689 2.40752C5.67017 2.54586 5.90605 2.73829 6.07454 2.98482C6.21435 3.18938 6.29799 3.42082 6.32545 3.67912C6.3349 3.76801 6.262 3.84144 6.17261 3.84144H5.37613C5.29388 3.84144 5.22712 3.77829 5.20489 3.6991C5.16352 3.55168 5.08096 3.43241 4.9572 3.3413C4.79581 3.22247 4.57678 3.16306 4.3001 3.16306M4.7896 6.74919C4.64595 6.80594 4.47657 6.83432 4.28148 6.83432C4.08285 6.83432 3.9046 6.80417 3.74676 6.74386C3.59069 6.68179 3.46565 6.59045 3.37165 6.46985M4.7896 6.74919C4.93504 6.69243 5.04766 6.61351 5.12747 6.51242M4.7896 6.74919L5.12747 6.51242M5.12747 6.51242C5.20728 6.41132 5.24718 6.29516 5.24718 6.16391M5.12747 6.51242L5.24718 6.16391M5.24718 6.16391C5.24718 6.04154 5.21082 5.93867 5.13811 5.85531L5.24718 6.16391Z" stroke="#687076" stroke-width="0.1"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 5.5 KiB |
|
@ -212,7 +212,6 @@
|
||||||
"enter": "search::SelectNextMatch",
|
"enter": "search::SelectNextMatch",
|
||||||
"shift-enter": "search::SelectPrevMatch",
|
"shift-enter": "search::SelectPrevMatch",
|
||||||
"alt-enter": "search::SelectAllMatches",
|
"alt-enter": "search::SelectAllMatches",
|
||||||
"alt-tab": "search::CycleMode",
|
|
||||||
"ctrl-f": "search::FocusSearch",
|
"ctrl-f": "search::FocusSearch",
|
||||||
"ctrl-h": "search::ToggleReplace"
|
"ctrl-h": "search::ToggleReplace"
|
||||||
}
|
}
|
||||||
|
@ -235,11 +234,10 @@
|
||||||
"context": "ProjectSearchBar",
|
"context": "ProjectSearchBar",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "project_search::ToggleFocus",
|
"escape": "project_search::ToggleFocus",
|
||||||
"alt-tab": "search::CycleMode",
|
|
||||||
"ctrl-shift-f": "search::FocusSearch",
|
"ctrl-shift-f": "search::FocusSearch",
|
||||||
"ctrl-shift-h": "search::ToggleReplace",
|
"ctrl-shift-h": "search::ToggleReplace",
|
||||||
"alt-ctrl-g": "search::ActivateRegexMode",
|
"alt-ctrl-g": "search::ToggleRegex",
|
||||||
"alt-ctrl-x": "search::ActivateTextMode"
|
"alt-ctrl-x": "search::ToggleRegex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -260,10 +258,9 @@
|
||||||
"context": "ProjectSearchView",
|
"context": "ProjectSearchView",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "project_search::ToggleFocus",
|
"escape": "project_search::ToggleFocus",
|
||||||
"alt-tab": "search::CycleMode",
|
|
||||||
"ctrl-shift-h": "search::ToggleReplace",
|
"ctrl-shift-h": "search::ToggleReplace",
|
||||||
"alt-ctrl-g": "search::ActivateRegexMode",
|
"alt-ctrl-g": "search::ToggleRegex",
|
||||||
"alt-ctrl-x": "search::ActivateTextMode"
|
"alt-ctrl-x": "search::ToggleRegex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -283,10 +280,10 @@
|
||||||
"alt-enter": "search::SelectAllMatches",
|
"alt-enter": "search::SelectAllMatches",
|
||||||
"alt-c": "search::ToggleCaseSensitive",
|
"alt-c": "search::ToggleCaseSensitive",
|
||||||
"alt-w": "search::ToggleWholeWord",
|
"alt-w": "search::ToggleWholeWord",
|
||||||
"alt-r": "search::CycleMode",
|
"alt-r": "search::ToggleRegex",
|
||||||
"alt-ctrl-f": "project_search::ToggleFilters",
|
"alt-ctrl-f": "project_search::ToggleFilters",
|
||||||
"ctrl-alt-shift-r": "search::ActivateRegexMode",
|
"ctrl-alt-shift-r": "search::ToggleRegex",
|
||||||
"ctrl-alt-shift-x": "search::ActivateTextMode"
|
"ctrl-alt-shift-x": "search::ToggleRegex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Bindings from VS Code
|
// Bindings from VS Code
|
||||||
|
|
|
@ -233,7 +233,6 @@
|
||||||
"enter": "search::SelectNextMatch",
|
"enter": "search::SelectNextMatch",
|
||||||
"shift-enter": "search::SelectPrevMatch",
|
"shift-enter": "search::SelectPrevMatch",
|
||||||
"alt-enter": "search::SelectAllMatches",
|
"alt-enter": "search::SelectAllMatches",
|
||||||
"alt-tab": "search::CycleMode",
|
|
||||||
"cmd-f": "search::FocusSearch",
|
"cmd-f": "search::FocusSearch",
|
||||||
"cmd-alt-f": "search::ToggleReplace"
|
"cmd-alt-f": "search::ToggleReplace"
|
||||||
}
|
}
|
||||||
|
@ -256,11 +255,10 @@
|
||||||
"context": "ProjectSearchBar",
|
"context": "ProjectSearchBar",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "project_search::ToggleFocus",
|
"escape": "project_search::ToggleFocus",
|
||||||
"alt-tab": "search::CycleMode",
|
|
||||||
"cmd-shift-f": "search::FocusSearch",
|
"cmd-shift-f": "search::FocusSearch",
|
||||||
"cmd-shift-h": "search::ToggleReplace",
|
"cmd-shift-h": "search::ToggleReplace",
|
||||||
"alt-cmd-g": "search::ActivateRegexMode",
|
"alt-cmd-g": "search::ToggleRegex",
|
||||||
"alt-cmd-x": "search::ActivateTextMode"
|
"alt-cmd-x": "search::ToggleRegex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -281,10 +279,9 @@
|
||||||
"context": "ProjectSearchView",
|
"context": "ProjectSearchView",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "project_search::ToggleFocus",
|
"escape": "project_search::ToggleFocus",
|
||||||
"alt-tab": "search::CycleMode",
|
|
||||||
"cmd-shift-h": "search::ToggleReplace",
|
"cmd-shift-h": "search::ToggleReplace",
|
||||||
"alt-cmd-g": "search::ActivateRegexMode",
|
"alt-cmd-g": "search::ToggleRegex",
|
||||||
"alt-cmd-x": "search::ActivateTextMode"
|
"alt-cmd-x": "search::ToggleRegex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -306,10 +303,9 @@
|
||||||
"alt-enter": "search::SelectAllMatches",
|
"alt-enter": "search::SelectAllMatches",
|
||||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||||
"alt-cmd-w": "search::ToggleWholeWord",
|
"alt-cmd-w": "search::ToggleWholeWord",
|
||||||
"alt-tab": "search::CycleMode",
|
|
||||||
"alt-cmd-f": "project_search::ToggleFilters",
|
"alt-cmd-f": "project_search::ToggleFilters",
|
||||||
"alt-cmd-g": "search::ActivateRegexMode",
|
"alt-cmd-g": "search::ToggleRegex",
|
||||||
"alt-cmd-x": "search::ActivateTextMode"
|
"alt-cmd-x": "search::ToggleRegex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Bindings from VS Code
|
// Bindings from VS Code
|
||||||
|
|
|
@ -471,6 +471,8 @@ pub struct Editor {
|
||||||
+ Fn(&mut Self, DisplayPoint, &mut ViewContext<Self>) -> Option<View<ui::ContextMenu>>,
|
+ Fn(&mut Self, DisplayPoint, &mut ViewContext<Self>) -> Option<View<ui::ContextMenu>>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
|
last_bounds: Option<Bounds<Pixels>>,
|
||||||
|
expect_bounds_change: Option<Bounds<Pixels>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -1485,6 +1487,8 @@ impl Editor {
|
||||||
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
||||||
gutter_hovered: false,
|
gutter_hovered: false,
|
||||||
pixel_position_of_newest_cursor: None,
|
pixel_position_of_newest_cursor: None,
|
||||||
|
last_bounds: None,
|
||||||
|
expect_bounds_change: None,
|
||||||
gutter_width: Default::default(),
|
gutter_width: Default::default(),
|
||||||
style: None,
|
style: None,
|
||||||
show_cursor_names: false,
|
show_cursor_names: false,
|
||||||
|
|
|
@ -3371,6 +3371,7 @@ impl Element for EditorElement {
|
||||||
let overscroll = size(em_width, px(0.));
|
let overscroll = size(em_width, px(0.));
|
||||||
|
|
||||||
snapshot = self.editor.update(cx, |editor, cx| {
|
snapshot = self.editor.update(cx, |editor, cx| {
|
||||||
|
editor.last_bounds = Some(bounds);
|
||||||
editor.gutter_width = gutter_dimensions.width;
|
editor.gutter_width = gutter_dimensions.width;
|
||||||
editor.set_visible_line_count(bounds.size.height / line_height, cx);
|
editor.set_visible_line_count(bounds.size.height / line_height, cx);
|
||||||
|
|
||||||
|
@ -3419,7 +3420,7 @@ impl Element for EditorElement {
|
||||||
|
|
||||||
let autoscroll_horizontally = self.editor.update(cx, |editor, cx| {
|
let autoscroll_horizontally = self.editor.update(cx, |editor, cx| {
|
||||||
let autoscroll_horizontally =
|
let autoscroll_horizontally =
|
||||||
editor.autoscroll_vertically(bounds.size.height, line_height, cx);
|
editor.autoscroll_vertically(bounds, line_height, cx);
|
||||||
snapshot = editor.snapshot(cx);
|
snapshot = editor.snapshot(cx);
|
||||||
autoscroll_horizontally
|
autoscroll_horizontally
|
||||||
});
|
});
|
||||||
|
|
|
@ -1167,6 +1167,10 @@ impl SearchableItem for Editor {
|
||||||
&self.buffer().read(cx).snapshot(cx),
|
&self.buffer().read(cx).snapshot(cx),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_bar_visibility_changed(&mut self, _visible: bool, _cx: &mut ViewContext<Self>) {
|
||||||
|
self.expect_bounds_change = self.last_bounds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_match_index(
|
pub fn active_match_index(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{cmp, f32};
|
use std::{cmp, f32};
|
||||||
|
|
||||||
use gpui::{px, Pixels, ViewContext};
|
use gpui::{px, Bounds, Pixels, ViewContext};
|
||||||
use language::Point;
|
use language::Point;
|
||||||
|
|
||||||
use crate::{display_map::ToDisplayPoint, Editor, EditorMode, LineWithInvisibles};
|
use crate::{display_map::ToDisplayPoint, Editor, EditorMode, LineWithInvisibles};
|
||||||
|
@ -63,13 +63,23 @@ impl AutoscrollStrategy {
|
||||||
impl Editor {
|
impl Editor {
|
||||||
pub fn autoscroll_vertically(
|
pub fn autoscroll_vertically(
|
||||||
&mut self,
|
&mut self,
|
||||||
viewport_height: Pixels,
|
bounds: Bounds<Pixels>,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
let viewport_height = bounds.size.height;
|
||||||
let visible_lines = viewport_height / line_height;
|
let visible_lines = viewport_height / line_height;
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
|
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
|
||||||
|
let original_y = scroll_position.y;
|
||||||
|
if let Some(last_bounds) = self.expect_bounds_change.take() {
|
||||||
|
if scroll_position.y != 0. {
|
||||||
|
scroll_position.y += (bounds.top() - last_bounds.top()) / line_height;
|
||||||
|
if scroll_position.y < 0. {
|
||||||
|
scroll_position.y = 0.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
||||||
(display_map.max_point().row() as f32 - visible_lines + 1.).max(0.)
|
(display_map.max_point().row() as f32 - visible_lines + 1.).max(0.)
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,6 +87,9 @@ impl Editor {
|
||||||
};
|
};
|
||||||
if scroll_position.y > max_scroll_top {
|
if scroll_position.y > max_scroll_top {
|
||||||
scroll_position.y = max_scroll_top;
|
scroll_position.y = max_scroll_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
if original_y != scroll_position.y {
|
||||||
self.set_scroll_position(scroll_position, cx);
|
self.set_scroll_position(scroll_position, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2381,6 +2381,11 @@ impl ScrollHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the bounds into which this child is painted
|
||||||
|
pub fn bounds(&self) -> Bounds<Pixels> {
|
||||||
|
self.0.borrow().bounds
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the bounds for a specific child.
|
/// Get the bounds for a specific child.
|
||||||
pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
|
pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
|
||||||
self.0.borrow().child_bounds.get(ix).cloned()
|
self.0.borrow().child_bounds.get(ix).cloned()
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
mod registrar;
|
mod registrar;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
mode::{next_mode, SearchMode},
|
search_bar::render_nav_button, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll,
|
||||||
search_bar::render_nav_button,
|
ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch,
|
||||||
ActivateRegexMode, ActivateTextMode, CycleMode, FocusSearch, NextHistoryQuery,
|
ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleWholeWord,
|
||||||
PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches,
|
|
||||||
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
|
|
||||||
};
|
};
|
||||||
use any_vec::AnyVec;
|
use any_vec::AnyVec;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{
|
use editor::{
|
||||||
actions::{Tab, TabPrev},
|
actions::{Tab, TabPrev},
|
||||||
Editor, EditorElement, EditorStyle,
|
DisplayPoint, Editor, EditorElement, EditorStyle,
|
||||||
};
|
};
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView,
|
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView,
|
||||||
FontStyle, FontWeight, Hsla, InteractiveElement as _, IntoElement, KeyContext,
|
FontStyle, FontWeight, Hsla, InteractiveElement as _, IntoElement, KeyContext,
|
||||||
ParentElement as _, Render, Styled, Subscription, Task, TextStyle, View, ViewContext,
|
ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task, TextStyle, View,
|
||||||
VisualContext as _, WhiteSpace, WindowContext,
|
ViewContext, VisualContext as _, WhiteSpace, WindowContext,
|
||||||
};
|
};
|
||||||
use project::{
|
use project::{
|
||||||
search::SearchQuery,
|
search::SearchQuery,
|
||||||
|
@ -29,7 +27,7 @@ use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
|
|
||||||
use ui::{h_flex, prelude::*, IconButton, IconName, ToggleButton, Tooltip};
|
use ui::{h_flex, prelude::*, IconButton, IconName, Tooltip, BASE_REM_SIZE_IN_PX};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::ItemHandle,
|
item::ItemHandle,
|
||||||
|
@ -40,7 +38,7 @@ use workspace::{
|
||||||
pub use registrar::DivRegistrar;
|
pub use registrar::DivRegistrar;
|
||||||
use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
|
use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
|
||||||
|
|
||||||
const MIN_INPUT_WIDTH_REMS: f32 = 15.;
|
const MIN_INPUT_WIDTH_REMS: f32 = 10.;
|
||||||
const MAX_INPUT_WIDTH_REMS: f32 = 30.;
|
const MAX_INPUT_WIDTH_REMS: f32 = 30.;
|
||||||
const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
|
const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
|
||||||
|
|
||||||
|
@ -95,8 +93,10 @@ pub struct BufferSearchBar {
|
||||||
dismissed: bool,
|
dismissed: bool,
|
||||||
search_history: SearchHistory,
|
search_history: SearchHistory,
|
||||||
search_history_cursor: SearchHistoryCursor,
|
search_history_cursor: SearchHistoryCursor,
|
||||||
current_mode: SearchMode,
|
|
||||||
replace_enabled: bool,
|
replace_enabled: bool,
|
||||||
|
scroll_handle: ScrollHandle,
|
||||||
|
editor_scroll_handle: ScrollHandle,
|
||||||
|
editor_needed_width: Pixels,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferSearchBar {
|
impl BufferSearchBar {
|
||||||
|
@ -142,61 +142,29 @@ impl EventEmitter<workspace::ToolbarItemEvent> for BufferSearchBar {}
|
||||||
impl Render for BufferSearchBar {
|
impl Render for BufferSearchBar {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
if self.dismissed {
|
if self.dismissed {
|
||||||
return div();
|
return div().id("search_bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let narrow_mode =
|
||||||
|
self.scroll_handle.bounds().size.width / cx.rem_size() < 340. / BASE_REM_SIZE_IN_PX;
|
||||||
|
let hide_inline_icons = self.editor_needed_width
|
||||||
|
> self.editor_scroll_handle.bounds().size.width - cx.rem_size() * 6.;
|
||||||
|
|
||||||
let supported_options = self.supported_options();
|
let supported_options = self.supported_options();
|
||||||
|
|
||||||
if self.query_editor.update(cx, |query_editor, cx| {
|
if self.query_editor.update(cx, |query_editor, cx| {
|
||||||
query_editor.placeholder_text(cx).is_none()
|
query_editor.placeholder_text(cx).is_none()
|
||||||
}) {
|
}) {
|
||||||
let query_focus_handle = self.query_editor.focus_handle(cx);
|
self.query_editor.update(cx, |editor, cx| {
|
||||||
let up_keystrokes = cx
|
editor.set_placeholder_text("Search", cx);
|
||||||
.bindings_for_action_in(&PreviousHistoryQuery {}, &query_focus_handle)
|
});
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.map(|binding| {
|
|
||||||
binding
|
|
||||||
.keystrokes()
|
|
||||||
.iter()
|
|
||||||
.map(|k| k.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
let down_keystrokes = cx
|
|
||||||
.bindings_for_action_in(&NextHistoryQuery {}, &query_focus_handle)
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.map(|binding| {
|
|
||||||
binding
|
|
||||||
.keystrokes()
|
|
||||||
.iter()
|
|
||||||
.map(|k| k.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
|
|
||||||
let placeholder_text =
|
|
||||||
up_keystrokes
|
|
||||||
.zip(down_keystrokes)
|
|
||||||
.map(|(up_keystrokes, down_keystrokes)| {
|
|
||||||
Arc::from(format!(
|
|
||||||
"Search ({}/{} for previous/next query)",
|
|
||||||
up_keystrokes.join(" "),
|
|
||||||
down_keystrokes.join(" ")
|
|
||||||
))
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(placeholder_text) = placeholder_text {
|
|
||||||
self.query_editor.update(cx, |editor, cx| {
|
|
||||||
editor.set_placeholder_text(placeholder_text, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.replacement_editor.update(cx, |editor, cx| {
|
self.replacement_editor.update(cx, |editor, cx| {
|
||||||
editor.set_placeholder_text("Replace with...", cx);
|
editor.set_placeholder_text("Replace with...", cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut match_color = Color::Default;
|
let mut text_color = Color::Default;
|
||||||
let match_text = self
|
let match_text = self
|
||||||
.active_searchable_item
|
.active_searchable_item
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -212,12 +180,11 @@ impl Render for BufferSearchBar {
|
||||||
if let Some(match_ix) = self.active_match_index {
|
if let Some(match_ix) = self.active_match_index {
|
||||||
Some(format!("{}/{}", match_ix + 1, matches_count))
|
Some(format!("{}/{}", match_ix + 1, matches_count))
|
||||||
} else {
|
} else {
|
||||||
match_color = Color::Error; // No matches found
|
text_color = Color::Error; // No matches found
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| "No matches".to_string());
|
.unwrap_or_else(|| "0/0".to_string());
|
||||||
let match_count = Label::new(match_text).color(match_color);
|
|
||||||
let should_show_replace_input = self.replace_enabled && supported_options.replacement;
|
let should_show_replace_input = self.replace_enabled && supported_options.replacement;
|
||||||
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
|
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
|
||||||
|
|
||||||
|
@ -233,111 +200,94 @@ impl Render for BufferSearchBar {
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_line = h_flex()
|
let search_line = h_flex()
|
||||||
.gap_2()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.id("editor-scroll")
|
||||||
|
.track_scroll(&self.editor_scroll_handle)
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
.h_8()
|
||||||
.px_2()
|
.px_2()
|
||||||
|
.mr_2()
|
||||||
.py_1()
|
.py_1()
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(editor_border)
|
.border_color(editor_border)
|
||||||
.min_w(rems(MIN_INPUT_WIDTH_REMS))
|
.min_w(rems(MIN_INPUT_WIDTH_REMS))
|
||||||
.max_w(rems(MAX_INPUT_WIDTH_REMS))
|
.max_w(rems(MAX_INPUT_WIDTH_REMS))
|
||||||
.rounded_lg()
|
.rounded_lg()
|
||||||
.child(self.render_text_input(&self.query_editor, match_color.color(cx), cx))
|
.child(self.render_text_input(&self.query_editor, text_color.color(cx), cx))
|
||||||
.children(supported_options.case.then(|| {
|
.when(!hide_inline_icons, |div| {
|
||||||
self.render_search_option_button(
|
div.children(supported_options.case.then(|| {
|
||||||
SearchOptions::CASE_SENSITIVE,
|
self.render_search_option_button(
|
||||||
cx.listener(|this, _, cx| {
|
SearchOptions::CASE_SENSITIVE,
|
||||||
this.toggle_case_sensitive(&ToggleCaseSensitive, cx)
|
cx.listener(|this, _, cx| {
|
||||||
}),
|
this.toggle_case_sensitive(&ToggleCaseSensitive, cx)
|
||||||
)
|
}),
|
||||||
}))
|
|
||||||
.children(supported_options.word.then(|| {
|
|
||||||
self.render_search_option_button(
|
|
||||||
SearchOptions::WHOLE_WORD,
|
|
||||||
cx.listener(|this, _, cx| this.toggle_whole_word(&ToggleWholeWord, cx)),
|
|
||||||
)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.flex_none()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.child(
|
|
||||||
ToggleButton::new("search-mode-text", SearchMode::Text.label())
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.size(ButtonSize::Large)
|
|
||||||
.selected(self.current_mode == SearchMode::Text)
|
|
||||||
.on_click(cx.listener(move |_, _event, cx| {
|
|
||||||
cx.dispatch_action(SearchMode::Text.action())
|
|
||||||
}))
|
|
||||||
.tooltip(|cx| {
|
|
||||||
Tooltip::for_action(
|
|
||||||
SearchMode::Text.tooltip(),
|
|
||||||
&*SearchMode::Text.action(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.first(),
|
|
||||||
)
|
)
|
||||||
.child(
|
}))
|
||||||
ToggleButton::new("search-mode-regex", SearchMode::Regex.label())
|
.children(supported_options.word.then(|| {
|
||||||
.style(ButtonStyle::Filled)
|
self.render_search_option_button(
|
||||||
.size(ButtonSize::Large)
|
SearchOptions::WHOLE_WORD,
|
||||||
.selected(self.current_mode == SearchMode::Regex)
|
cx.listener(|this, _, cx| {
|
||||||
.on_click(cx.listener(move |_, _event, cx| {
|
this.toggle_whole_word(&ToggleWholeWord, cx)
|
||||||
cx.dispatch_action(SearchMode::Regex.action())
|
}),
|
||||||
}))
|
|
||||||
.tooltip(|cx| {
|
|
||||||
Tooltip::for_action(
|
|
||||||
SearchMode::Regex.tooltip(),
|
|
||||||
&*SearchMode::Regex.action(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.last(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.when(supported_options.replacement, |this| {
|
|
||||||
this.child(
|
|
||||||
IconButton::new(
|
|
||||||
"buffer-search-bar-toggle-replace-button",
|
|
||||||
IconName::Replace,
|
|
||||||
)
|
)
|
||||||
.style(ButtonStyle::Subtle)
|
}))
|
||||||
.when(self.replace_enabled, |button| {
|
.children(supported_options.word.then(|| {
|
||||||
button.style(ButtonStyle::Filled)
|
self.render_search_option_button(
|
||||||
})
|
SearchOptions::REGEX,
|
||||||
.on_click(cx.listener(|this, _: &ClickEvent, cx| {
|
cx.listener(|this, _, cx| this.toggle_regex(&ToggleRegex, cx)),
|
||||||
this.toggle_replace(&ToggleReplace, cx);
|
)
|
||||||
}))
|
}))
|
||||||
.tooltip(|cx| {
|
|
||||||
Tooltip::for_action("Toggle replace", &ToggleReplace, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.when(supported_options.replacement, |this| {
|
||||||
|
this.child(
|
||||||
|
IconButton::new("buffer-search-bar-toggle-replace-button", IconName::Replace)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.when(self.replace_enabled, |button| {
|
||||||
|
button.style(ButtonStyle::Filled)
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(|this, _: &ClickEvent, cx| {
|
||||||
|
this.toggle_replace(&ToggleReplace, cx);
|
||||||
|
}))
|
||||||
|
.selected(self.replace_enabled)
|
||||||
|
.size(ButtonSize::Compact)
|
||||||
|
.tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)),
|
||||||
|
)
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("select-all", ui::IconName::SelectAll)
|
IconButton::new("select-all", ui::IconName::SelectAll)
|
||||||
.on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
|
.on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
|
||||||
|
.size(ButtonSize::Compact)
|
||||||
.tooltip(|cx| {
|
.tooltip(|cx| {
|
||||||
Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
|
Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(div().min_w(rems(6.)).child(match_count))
|
|
||||||
.child(render_nav_button(
|
.child(render_nav_button(
|
||||||
ui::IconName::ChevronLeft,
|
ui::IconName::ChevronLeft,
|
||||||
self.active_match_index.is_some(),
|
self.active_match_index.is_some(),
|
||||||
"Select previous match",
|
"Select previous match",
|
||||||
&SelectPrevMatch,
|
&SelectPrevMatch,
|
||||||
))
|
))
|
||||||
|
.when(!narrow_mode, |this| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.mx(rems_from_px(-4.0))
|
||||||
|
.min_w(rems_from_px(40.))
|
||||||
|
.justify_center()
|
||||||
|
.items_center()
|
||||||
|
.child(Label::new(match_text).color(
|
||||||
|
if self.active_match_index.is_some() {
|
||||||
|
Color::Default
|
||||||
|
} else {
|
||||||
|
Color::Disabled
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
})
|
||||||
.child(render_nav_button(
|
.child(render_nav_button(
|
||||||
ui::IconName::ChevronRight,
|
ui::IconName::ChevronRight,
|
||||||
self.active_match_index.is_some(),
|
self.active_match_index.is_some(),
|
||||||
|
@ -394,6 +344,8 @@ impl Render for BufferSearchBar {
|
||||||
});
|
});
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.id("buffer_search")
|
||||||
|
.track_scroll(&self.scroll_handle)
|
||||||
.key_context(key_context)
|
.key_context(key_context)
|
||||||
.capture_action(cx.listener(Self::tab))
|
.capture_action(cx.listener(Self::tab))
|
||||||
.capture_action(cx.listener(Self::tab_prev))
|
.capture_action(cx.listener(Self::tab_prev))
|
||||||
|
@ -402,12 +354,6 @@ impl Render for BufferSearchBar {
|
||||||
.on_action(cx.listener(Self::dismiss))
|
.on_action(cx.listener(Self::dismiss))
|
||||||
.on_action(cx.listener(Self::select_next_match))
|
.on_action(cx.listener(Self::select_next_match))
|
||||||
.on_action(cx.listener(Self::select_prev_match))
|
.on_action(cx.listener(Self::select_prev_match))
|
||||||
.on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
|
|
||||||
this.activate_search_mode(SearchMode::Regex, cx);
|
|
||||||
}))
|
|
||||||
.on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
|
|
||||||
this.activate_search_mode(SearchMode::Text, cx);
|
|
||||||
}))
|
|
||||||
.when(self.supported_options().replacement, |this| {
|
.when(self.supported_options().replacement, |this| {
|
||||||
this.on_action(cx.listener(Self::toggle_replace))
|
this.on_action(cx.listener(Self::toggle_replace))
|
||||||
.when(in_replace, |this| {
|
.when(in_replace, |this| {
|
||||||
|
@ -421,15 +367,24 @@ impl Render for BufferSearchBar {
|
||||||
.when(self.supported_options().word, |this| {
|
.when(self.supported_options().word, |this| {
|
||||||
this.on_action(cx.listener(Self::toggle_whole_word))
|
this.on_action(cx.listener(Self::toggle_whole_word))
|
||||||
})
|
})
|
||||||
|
.when(self.supported_options().regex, |this| {
|
||||||
|
this.on_action(cx.listener(Self::toggle_regex))
|
||||||
|
})
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
h_flex().child(search_line.w_full()).child(
|
h_flex()
|
||||||
IconButton::new(SharedString::from("Close"), IconName::Close)
|
.child(search_line.w_full())
|
||||||
.tooltip(move |cx| Tooltip::for_action("Close search bar", &Dismiss, cx))
|
.when(!narrow_mode, |div| {
|
||||||
.on_click(
|
div.child(
|
||||||
cx.listener(|this, _: &ClickEvent, cx| this.dismiss(&Dismiss, cx)),
|
IconButton::new(SharedString::from("Close"), IconName::Close)
|
||||||
),
|
.tooltip(move |cx| {
|
||||||
),
|
Tooltip::for_action("Close search bar", &Dismiss, cx)
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(|this, _: &ClickEvent, cx| {
|
||||||
|
this.dismiss(&Dismiss, cx)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.children(replace_line)
|
.children(replace_line)
|
||||||
}
|
}
|
||||||
|
@ -504,21 +459,6 @@ impl BufferSearchBar {
|
||||||
this.toggle_replace(action, cx);
|
this.toggle_replace(action, cx);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
registrar.register_handler(ForDeployed(|this, _: &ActivateRegexMode, cx| {
|
|
||||||
if this.supported_options().regex {
|
|
||||||
this.activate_search_mode(SearchMode::Regex, cx);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
registrar.register_handler(ForDeployed(|this, _: &ActivateTextMode, cx| {
|
|
||||||
this.activate_search_mode(SearchMode::Text, cx);
|
|
||||||
}));
|
|
||||||
registrar.register_handler(ForDeployed(|this, action: &CycleMode, cx| {
|
|
||||||
if this.supported_options().regex {
|
|
||||||
// If regex is not supported then search has just one mode (text) - in that case there's no point in supporting
|
|
||||||
// cycling.
|
|
||||||
this.cycle_mode(action, cx)
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
registrar.register_handler(WithResults(|this, action: &SelectNextMatch, cx| {
|
registrar.register_handler(WithResults(|this, action: &SelectNextMatch, cx| {
|
||||||
this.select_next_match(action, cx);
|
this.select_next_match(action, cx);
|
||||||
}));
|
}));
|
||||||
|
@ -569,9 +509,11 @@ impl BufferSearchBar {
|
||||||
project::search_history::QueryInsertionBehavior::ReplacePreviousIfContains,
|
project::search_history::QueryInsertionBehavior::ReplacePreviousIfContains,
|
||||||
),
|
),
|
||||||
search_history_cursor: Default::default(),
|
search_history_cursor: Default::default(),
|
||||||
current_mode: SearchMode::default(),
|
|
||||||
active_search: None,
|
active_search: None,
|
||||||
replace_enabled: false,
|
replace_enabled: false,
|
||||||
|
scroll_handle: ScrollHandle::new(),
|
||||||
|
editor_scroll_handle: ScrollHandle::new(),
|
||||||
|
editor_needed_width: px(0.),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,6 +531,7 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(active_editor) = self.active_searchable_item.as_ref() {
|
if let Some(active_editor) = self.active_searchable_item.as_ref() {
|
||||||
|
active_editor.search_bar_visibility_changed(false, cx);
|
||||||
let handle = active_editor.focus_handle(cx);
|
let handle = active_editor.focus_handle(cx);
|
||||||
cx.focus(&handle);
|
cx.focus(&handle);
|
||||||
}
|
}
|
||||||
|
@ -630,10 +573,12 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
if self.active_searchable_item.is_none() {
|
let Some(handle) = self.active_searchable_item.as_ref() else {
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
self.dismissed = false;
|
self.dismissed = false;
|
||||||
|
handle.search_bar_visibility_changed(true, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
cx.emit(Event::UpdateLocation);
|
cx.emit(Event::UpdateLocation);
|
||||||
cx.emit(ToolbarItemEvent::ChangeLocation(
|
cx.emit(ToolbarItemEvent::ChangeLocation(
|
||||||
|
@ -740,14 +685,6 @@ impl BufferSearchBar {
|
||||||
let is_active = self.search_options.contains(option);
|
let is_active = self.search_options.contains(option);
|
||||||
option.as_button(is_active, action)
|
option.as_button(is_active, action)
|
||||||
}
|
}
|
||||||
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
|
||||||
if mode == self.current_mode {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.current_mode = mode;
|
|
||||||
let _ = self.update_matches(cx);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext<Self>) {
|
pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(active_editor) = self.active_searchable_item.as_ref() {
|
if let Some(active_editor) = self.active_searchable_item.as_ref() {
|
||||||
|
@ -763,6 +700,16 @@ impl BufferSearchBar {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn enable_search_option(
|
||||||
|
&mut self,
|
||||||
|
search_option: SearchOptions,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if !self.search_options.contains(search_option) {
|
||||||
|
self.toggle_search_option(search_option, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_search_options(
|
pub fn set_search_options(
|
||||||
&mut self,
|
&mut self,
|
||||||
search_options: SearchOptions,
|
search_options: SearchOptions,
|
||||||
|
@ -829,7 +776,7 @@ impl BufferSearchBar {
|
||||||
|
|
||||||
fn on_query_editor_event(
|
fn on_query_editor_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: View<Editor>,
|
editor: View<Editor>,
|
||||||
event: &editor::EditorEvent,
|
event: &editor::EditorEvent,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
@ -839,6 +786,17 @@ impl BufferSearchBar {
|
||||||
editor::EditorEvent::Edited => {
|
editor::EditorEvent::Edited => {
|
||||||
self.clear_matches(cx);
|
self.clear_matches(cx);
|
||||||
let search = self.update_matches(cx);
|
let search = self.update_matches(cx);
|
||||||
|
|
||||||
|
let width = editor.update(cx, |editor, cx| {
|
||||||
|
let text_layout_details = editor.text_layout_details(cx);
|
||||||
|
let snapshot = editor.snapshot(cx).display_snapshot;
|
||||||
|
|
||||||
|
snapshot.x_for_display_point(snapshot.max_point(), &text_layout_details)
|
||||||
|
- snapshot.x_for_display_point(DisplayPoint::zero(), &text_layout_details)
|
||||||
|
});
|
||||||
|
self.editor_needed_width = width;
|
||||||
|
cx.notify();
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
search.await?;
|
search.await?;
|
||||||
this.update(&mut cx, |this, cx| this.activate_current_match(cx))
|
this.update(&mut cx, |this, cx| this.activate_current_match(cx))
|
||||||
|
@ -874,10 +832,15 @@ impl BufferSearchBar {
|
||||||
fn toggle_case_sensitive(&mut self, _: &ToggleCaseSensitive, cx: &mut ViewContext<Self>) {
|
fn toggle_case_sensitive(&mut self, _: &ToggleCaseSensitive, cx: &mut ViewContext<Self>) {
|
||||||
self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
|
self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {
|
fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {
|
||||||
self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
|
self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_regex(&mut self, _: &ToggleRegex, cx: &mut ViewContext<Self>) {
|
||||||
|
self.toggle_search_option(SearchOptions::REGEX, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn clear_active_searchable_item_matches(&mut self, cx: &mut WindowContext) {
|
fn clear_active_searchable_item_matches(&mut self, cx: &mut WindowContext) {
|
||||||
if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
|
if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
|
||||||
self.active_match_index = None;
|
self.active_match_index = None;
|
||||||
|
@ -917,7 +880,7 @@ impl BufferSearchBar {
|
||||||
let _ = done_tx.send(());
|
let _ = done_tx.send(());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
} else {
|
} else {
|
||||||
let query: Arc<_> = if self.current_mode == SearchMode::Regex {
|
let query: Arc<_> = if self.search_options.contains(SearchOptions::REGEX) {
|
||||||
match SearchQuery::regex(
|
match SearchQuery::regex(
|
||||||
query,
|
query,
|
||||||
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
|
@ -1065,9 +1028,7 @@ impl BufferSearchBar {
|
||||||
let _ = self.search(&new_query, Some(self.search_options), cx);
|
let _ = self.search(&new_query, Some(self.search_options), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn cycle_mode(&mut self, _: &CycleMode, cx: &mut ViewContext<Self>) {
|
|
||||||
self.activate_search_mode(next_mode(&self.current_mode), cx);
|
|
||||||
}
|
|
||||||
fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
|
fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(_) = &self.active_searchable_item {
|
if let Some(_) = &self.active_searchable_item {
|
||||||
self.replace_enabled = !self.replace_enabled;
|
self.replace_enabled = !self.replace_enabled;
|
||||||
|
@ -1910,8 +1871,7 @@ mod tests {
|
||||||
// Let's turn on regex mode.
|
// Let's turn on regex mode.
|
||||||
search_bar
|
search_bar
|
||||||
.update(cx, |search_bar, cx| {
|
.update(cx, |search_bar, cx| {
|
||||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
search_bar.search("\\[([^\\]]+)\\]", Some(SearchOptions::REGEX), cx)
|
||||||
search_bar.search("\\[([^\\]]+)\\]", None, cx)
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1934,8 +1894,11 @@ mod tests {
|
||||||
// Now with a whole-word twist.
|
// Now with a whole-word twist.
|
||||||
search_bar
|
search_bar
|
||||||
.update(cx, |search_bar, cx| {
|
.update(cx, |search_bar, cx| {
|
||||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
search_bar.search(
|
||||||
search_bar.search("a\\w+s", Some(SearchOptions::WHOLE_WORD), cx)
|
"a\\w+s",
|
||||||
|
Some(SearchOptions::REGEX | SearchOptions::WHOLE_WORD),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1963,7 +1926,6 @@ mod tests {
|
||||||
editor: &'a View<Editor>,
|
editor: &'a View<Editor>,
|
||||||
search_bar: &'a View<BufferSearchBar>,
|
search_bar: &'a View<BufferSearchBar>,
|
||||||
cx: &'a mut VisualTestContext,
|
cx: &'a mut VisualTestContext,
|
||||||
search_mode: SearchMode,
|
|
||||||
search_text: &'static str,
|
search_text: &'static str,
|
||||||
search_options: Option<SearchOptions>,
|
search_options: Option<SearchOptions>,
|
||||||
replacement_text: &'static str,
|
replacement_text: &'static str,
|
||||||
|
@ -1975,7 +1937,9 @@ mod tests {
|
||||||
options
|
options
|
||||||
.search_bar
|
.search_bar
|
||||||
.update(options.cx, |search_bar, cx| {
|
.update(options.cx, |search_bar, cx| {
|
||||||
search_bar.activate_search_mode(options.search_mode, cx);
|
if let Some(options) = options.search_options {
|
||||||
|
search_bar.set_search_options(options, cx);
|
||||||
|
}
|
||||||
search_bar.search(options.search_text, options.search_options, cx)
|
search_bar.search(options.search_text, options.search_options, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -2009,7 +1973,6 @@ mod tests {
|
||||||
editor: &editor,
|
editor: &editor,
|
||||||
search_bar: &search_bar,
|
search_bar: &search_bar,
|
||||||
cx,
|
cx,
|
||||||
search_mode: SearchMode::Text,
|
|
||||||
search_text: "expression",
|
search_text: "expression",
|
||||||
search_options: None,
|
search_options: None,
|
||||||
replacement_text: r"\n",
|
replacement_text: r"\n",
|
||||||
|
@ -2028,9 +1991,8 @@ mod tests {
|
||||||
editor: &editor,
|
editor: &editor,
|
||||||
search_bar: &search_bar,
|
search_bar: &search_bar,
|
||||||
cx,
|
cx,
|
||||||
search_mode: SearchMode::Regex,
|
|
||||||
search_text: "or",
|
search_text: "or",
|
||||||
search_options: Some(SearchOptions::WHOLE_WORD),
|
search_options: Some(SearchOptions::WHOLE_WORD | SearchOptions::REGEX),
|
||||||
replacement_text: r"\\\n\\\\",
|
replacement_text: r"\\\n\\\\",
|
||||||
replace_all: false,
|
replace_all: false,
|
||||||
expected_text: r#"
|
expected_text: r#"
|
||||||
|
@ -2048,9 +2010,8 @@ mod tests {
|
||||||
editor: &editor,
|
editor: &editor,
|
||||||
search_bar: &search_bar,
|
search_bar: &search_bar,
|
||||||
cx,
|
cx,
|
||||||
search_mode: SearchMode::Regex,
|
|
||||||
search_text: r"(that|used) ",
|
search_text: r"(that|used) ",
|
||||||
search_options: None,
|
search_options: Some(SearchOptions::REGEX),
|
||||||
replacement_text: r"$1\n",
|
replacement_text: r"$1\n",
|
||||||
replace_all: true,
|
replace_all: true,
|
||||||
expected_text: r#"
|
expected_text: r#"
|
||||||
|
@ -2079,7 +2040,7 @@ mod tests {
|
||||||
// Search using valid regexp
|
// Search using valid regexp
|
||||||
search_bar
|
search_bar
|
||||||
.update(cx, |search_bar, cx| {
|
.update(cx, |search_bar, cx| {
|
||||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
search_bar.enable_search_option(SearchOptions::REGEX, cx);
|
||||||
search_bar.search("expression", None, cx)
|
search_bar.search("expression", None, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
mode::SearchMode, ActivateRegexMode, ActivateTextMode, CycleMode, FocusSearch,
|
FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
|
||||||
NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
|
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex,
|
||||||
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleIncludeIgnored, ToggleReplace,
|
ToggleReplace, ToggleWholeWord,
|
||||||
ToggleWholeWord,
|
|
||||||
};
|
};
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
|
@ -15,7 +14,7 @@ use editor::{
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId,
|
actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId,
|
||||||
EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global, Hsla,
|
EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global, Hsla,
|
||||||
InteractiveElement, IntoElement, KeyContext, Model, ModelContext, ParentElement, Point, Render,
|
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Point, Render,
|
||||||
SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext,
|
SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext,
|
||||||
WeakModel, WeakView, WhiteSpace, WindowContext,
|
WeakModel, WeakView, WhiteSpace, WindowContext,
|
||||||
};
|
};
|
||||||
|
@ -32,7 +31,7 @@ use std::{
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
h_flex, prelude::*, v_flex, Icon, IconButton, IconName, Label, LabelCommon, LabelSize,
|
h_flex, prelude::*, v_flex, Icon, IconButton, IconName, Label, LabelCommon, LabelSize,
|
||||||
Selectable, ToggleButton, Tooltip,
|
Selectable, Tooltip,
|
||||||
};
|
};
|
||||||
use util::paths::PathMatcher;
|
use util::paths::PathMatcher;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
@ -72,18 +71,12 @@ pub fn init(cx: &mut AppContext) {
|
||||||
register_workspace_action(workspace, move |search_bar, _: &ToggleWholeWord, cx| {
|
register_workspace_action(workspace, move |search_bar, _: &ToggleWholeWord, cx| {
|
||||||
search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
||||||
});
|
});
|
||||||
|
register_workspace_action(workspace, move |search_bar, _: &ToggleRegex, cx| {
|
||||||
|
search_bar.toggle_search_option(SearchOptions::REGEX, cx);
|
||||||
|
});
|
||||||
register_workspace_action(workspace, move |search_bar, action: &ToggleReplace, cx| {
|
register_workspace_action(workspace, move |search_bar, action: &ToggleReplace, cx| {
|
||||||
search_bar.toggle_replace(action, cx)
|
search_bar.toggle_replace(action, cx)
|
||||||
});
|
});
|
||||||
register_workspace_action(workspace, move |search_bar, _: &ActivateRegexMode, cx| {
|
|
||||||
search_bar.activate_search_mode(SearchMode::Regex, cx)
|
|
||||||
});
|
|
||||||
register_workspace_action(workspace, move |search_bar, _: &ActivateTextMode, cx| {
|
|
||||||
search_bar.activate_search_mode(SearchMode::Text, cx)
|
|
||||||
});
|
|
||||||
register_workspace_action(workspace, move |search_bar, action: &CycleMode, cx| {
|
|
||||||
search_bar.cycle_mode(action, cx)
|
|
||||||
});
|
|
||||||
register_workspace_action(
|
register_workspace_action(
|
||||||
workspace,
|
workspace,
|
||||||
move |search_bar, action: &SelectPrevMatch, cx| {
|
move |search_bar, action: &SelectPrevMatch, cx| {
|
||||||
|
@ -158,7 +151,6 @@ pub struct ProjectSearchView {
|
||||||
excluded_files_editor: View<Editor>,
|
excluded_files_editor: View<Editor>,
|
||||||
filters_enabled: bool,
|
filters_enabled: bool,
|
||||||
replace_enabled: bool,
|
replace_enabled: bool,
|
||||||
current_mode: SearchMode,
|
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +158,6 @@ pub struct ProjectSearchView {
|
||||||
struct ProjectSearchSettings {
|
struct ProjectSearchSettings {
|
||||||
search_options: SearchOptions,
|
search_options: SearchOptions,
|
||||||
filters_enabled: bool,
|
filters_enabled: bool,
|
||||||
current_mode: SearchMode,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProjectSearchBar {
|
pub struct ProjectSearchBar {
|
||||||
|
@ -302,7 +293,7 @@ impl Render for ProjectSearchView {
|
||||||
} else if has_no_results {
|
} else if has_no_results {
|
||||||
Label::new("No results")
|
Label::new("No results")
|
||||||
} else {
|
} else {
|
||||||
Label::new(format!("{} search all files", self.current_mode.label()))
|
Label::new("Search all files")
|
||||||
};
|
};
|
||||||
|
|
||||||
let major_text = div().justify_center().max_w_96().child(major_text);
|
let major_text = div().justify_center().max_w_96().child(major_text);
|
||||||
|
@ -549,7 +540,6 @@ impl ProjectSearchView {
|
||||||
ProjectSearchSettings {
|
ProjectSearchSettings {
|
||||||
search_options: self.search_options,
|
search_options: self.search_options,
|
||||||
filters_enabled: self.filters_enabled,
|
filters_enabled: self.filters_enabled,
|
||||||
current_mode: self.current_mode,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn toggle_search_option(&mut self, option: SearchOptions, cx: &mut ViewContext<Self>) {
|
fn toggle_search_option(&mut self, option: SearchOptions, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -562,39 +552,6 @@ impl ProjectSearchView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_search(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
self.model.update(cx, |model, cx| {
|
|
||||||
model.pending_search = None;
|
|
||||||
model.no_results = None;
|
|
||||||
model.limit_reached = false;
|
|
||||||
model.match_ranges.clear();
|
|
||||||
|
|
||||||
model.excerpts.update(cx, |excerpts, cx| {
|
|
||||||
excerpts.clear(cx);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
|
||||||
let previous_mode = self.current_mode;
|
|
||||||
if previous_mode == mode {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.clear_search(cx);
|
|
||||||
self.current_mode = mode;
|
|
||||||
self.active_match_index = None;
|
|
||||||
self.search(cx);
|
|
||||||
|
|
||||||
cx.update_global(|state: &mut ActiveSettings, cx| {
|
|
||||||
state.0.insert(
|
|
||||||
self.model.read(cx).project.downgrade(),
|
|
||||||
self.current_settings(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
|
fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
|
||||||
if self.model.read(cx).match_ranges.is_empty() {
|
if self.model.read(cx).match_ranges.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
@ -658,14 +615,10 @@ impl ProjectSearchView {
|
||||||
let mut subscriptions = Vec::new();
|
let mut subscriptions = Vec::new();
|
||||||
|
|
||||||
// Read in settings if available
|
// Read in settings if available
|
||||||
let (mut options, current_mode, filters_enabled) = if let Some(settings) = settings {
|
let (mut options, filters_enabled) = if let Some(settings) = settings {
|
||||||
(
|
(settings.search_options, settings.filters_enabled)
|
||||||
settings.search_options,
|
|
||||||
settings.current_mode,
|
|
||||||
settings.filters_enabled,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(SearchOptions::NONE, Default::default(), false)
|
(SearchOptions::NONE, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -682,7 +635,7 @@ impl ProjectSearchView {
|
||||||
|
|
||||||
let query_editor = cx.new_view(|cx| {
|
let query_editor = cx.new_view(|cx| {
|
||||||
let mut editor = Editor::single_line(cx);
|
let mut editor = Editor::single_line(cx);
|
||||||
editor.set_placeholder_text("Text search all files", cx);
|
editor.set_placeholder_text("Search all files..", cx);
|
||||||
editor.set_text(query_text, cx);
|
editor.set_text(query_text, cx);
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
@ -769,7 +722,6 @@ impl ProjectSearchView {
|
||||||
included_files_editor,
|
included_files_editor,
|
||||||
excluded_files_editor,
|
excluded_files_editor,
|
||||||
filters_enabled,
|
filters_enabled,
|
||||||
current_mode,
|
|
||||||
replace_enabled: false,
|
replace_enabled: false,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
};
|
};
|
||||||
|
@ -946,37 +898,8 @@ impl ProjectSearchView {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_mode = self.current_mode;
|
let query = if self.search_options.contains(SearchOptions::REGEX) {
|
||||||
let query = match current_mode {
|
match SearchQuery::regex(
|
||||||
SearchMode::Regex => {
|
|
||||||
match SearchQuery::regex(
|
|
||||||
text,
|
|
||||||
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
|
||||||
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
|
||||||
self.search_options.contains(SearchOptions::INCLUDE_IGNORED),
|
|
||||||
included_files,
|
|
||||||
excluded_files,
|
|
||||||
) {
|
|
||||||
Ok(query) => {
|
|
||||||
let should_unmark_error =
|
|
||||||
self.panels_with_errors.remove(&InputPanel::Query);
|
|
||||||
if should_unmark_error {
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(query)
|
|
||||||
}
|
|
||||||
Err(_e) => {
|
|
||||||
let should_mark_error = self.panels_with_errors.insert(InputPanel::Query);
|
|
||||||
if should_mark_error {
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => match SearchQuery::text(
|
|
||||||
text,
|
text,
|
||||||
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
||||||
|
@ -1000,7 +923,33 @@ impl ProjectSearchView {
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
} else {
|
||||||
|
match SearchQuery::text(
|
||||||
|
text,
|
||||||
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
|
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
||||||
|
self.search_options.contains(SearchOptions::INCLUDE_IGNORED),
|
||||||
|
included_files,
|
||||||
|
excluded_files,
|
||||||
|
) {
|
||||||
|
Ok(query) => {
|
||||||
|
let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Query);
|
||||||
|
if should_unmark_error {
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(query)
|
||||||
|
}
|
||||||
|
Err(_e) => {
|
||||||
|
let should_mark_error = self.panels_with_errors.insert(InputPanel::Query);
|
||||||
|
if should_mark_error {
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if !self.panels_with_errors.is_empty() {
|
if !self.panels_with_errors.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
|
@ -1116,10 +1065,9 @@ impl ProjectSearchView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn landing_text_minor(&self) -> SharedString {
|
fn landing_text_minor(&self) -> SharedString {
|
||||||
match self.current_mode {
|
"Include/exclude specific paths with the filter option. Matching exact word and/or casing is available too.".into()
|
||||||
SearchMode::Text | SearchMode::Regex => "Include/exclude specific paths with the filter option. Matching exact word and/or casing is available too.".into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn border_color_for(&self, panel: InputPanel, cx: &WindowContext) -> Hsla {
|
fn border_color_for(&self, panel: InputPanel, cx: &WindowContext) -> Hsla {
|
||||||
if self.panels_with_errors.contains(&panel) {
|
if self.panels_with_errors.contains(&panel) {
|
||||||
Color::Error.color(cx)
|
Color::Error.color(cx)
|
||||||
|
@ -1127,6 +1075,7 @@ impl ProjectSearchView {
|
||||||
cx.theme().colors().border
|
cx.theme().colors().border
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_focus_to_results(&mut self, cx: &mut ViewContext<Self>) {
|
fn move_focus_to_results(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
if !self.results_editor.focus_handle(cx).is_focused(cx)
|
if !self.results_editor.focus_handle(cx).is_focused(cx)
|
||||||
&& !self.model.read(cx).match_ranges.is_empty()
|
&& !self.model.read(cx).match_ranges.is_empty()
|
||||||
|
@ -1145,17 +1094,6 @@ impl ProjectSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cycle_mode(&self, _: &CycleMode, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some(view) = self.active_project_search.as_ref() {
|
|
||||||
view.update(cx, |this, cx| {
|
|
||||||
let new_mode = crate::mode::next_mode(&this.current_mode);
|
|
||||||
this.activate_search_mode(new_mode, cx);
|
|
||||||
let editor_handle = this.query_editor.focus_handle(cx);
|
|
||||||
cx.focus(&editor_handle);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(search_view) = self.active_project_search.as_ref() {
|
if let Some(search_view) = self.active_project_search.as_ref() {
|
||||||
search_view.update(cx, |search_view, cx| {
|
search_view.update(cx, |search_view, cx| {
|
||||||
|
@ -1285,16 +1223,6 @@ impl ProjectSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate_search_mode(&self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
|
||||||
// Update Current Mode
|
|
||||||
if let Some(search_view) = self.active_project_search.as_ref() {
|
|
||||||
search_view.update(cx, |search_view, cx| {
|
|
||||||
search_view.activate_search_mode(mode, cx);
|
|
||||||
});
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_option_enabled(&self, option: SearchOptions, cx: &AppContext) -> bool {
|
fn is_option_enabled(&self, option: SearchOptions, cx: &AppContext) -> bool {
|
||||||
if let Some(search) = self.active_project_search.as_ref() {
|
if let Some(search) = self.active_project_search.as_ref() {
|
||||||
search.read(cx).search_options.contains(option)
|
search.read(cx).search_options.contains(option)
|
||||||
|
@ -1372,48 +1300,6 @@ impl ProjectSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_placeholder_text(&self, cx: &mut ViewContext<Self>) -> Option<String> {
|
|
||||||
let previous_query_keystrokes = cx
|
|
||||||
.bindings_for_action(&PreviousHistoryQuery {})
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.map(|binding| {
|
|
||||||
binding
|
|
||||||
.keystrokes()
|
|
||||||
.iter()
|
|
||||||
.map(|k| k.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
let next_query_keystrokes = cx
|
|
||||||
.bindings_for_action(&NextHistoryQuery {})
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.map(|binding| {
|
|
||||||
binding
|
|
||||||
.keystrokes()
|
|
||||||
.iter()
|
|
||||||
.map(|k| k.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
|
|
||||||
(Some(previous_query_keystrokes), Some(next_query_keystrokes)) => Some(format!(
|
|
||||||
"Search ({}/{} for previous/next query)",
|
|
||||||
previous_query_keystrokes.join(" "),
|
|
||||||
next_query_keystrokes.join(" ")
|
|
||||||
)),
|
|
||||||
(None, Some(next_query_keystrokes)) => Some(format!(
|
|
||||||
"Search ({} for next query)",
|
|
||||||
next_query_keystrokes.join(" ")
|
|
||||||
)),
|
|
||||||
(Some(previous_query_keystrokes), None) => Some(format!(
|
|
||||||
"Search ({} for previous query)",
|
|
||||||
previous_query_keystrokes.join(" ")
|
|
||||||
)),
|
|
||||||
(None, None) => None,
|
|
||||||
};
|
|
||||||
new_placeholder_text
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
|
fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||||
let settings = ThemeSettings::get_global(cx);
|
let settings = ThemeSettings::get_global(cx);
|
||||||
let text_style = TextStyle {
|
let text_style = TextStyle {
|
||||||
|
@ -1451,19 +1337,12 @@ impl Render for ProjectSearchBar {
|
||||||
let Some(search) = self.active_project_search.clone() else {
|
let Some(search) = self.active_project_search.clone() else {
|
||||||
return div();
|
return div();
|
||||||
};
|
};
|
||||||
let mut key_context = KeyContext::default();
|
|
||||||
key_context.add("ProjectSearchBar");
|
|
||||||
if let Some(placeholder_text) = self.new_placeholder_text(cx) {
|
|
||||||
search.update(cx, |search, cx| {
|
|
||||||
search.query_editor.update(cx, |this, cx| {
|
|
||||||
this.set_placeholder_text(placeholder_text, cx)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let search = search.read(cx);
|
let search = search.read(cx);
|
||||||
|
|
||||||
let query_column = h_flex()
|
let query_column = h_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
.h_8()
|
||||||
|
.mr_2()
|
||||||
.px_2()
|
.px_2()
|
||||||
.py_1()
|
.py_1()
|
||||||
.border_1()
|
.border_1()
|
||||||
|
@ -1477,79 +1356,39 @@ impl Render for ProjectSearchBar {
|
||||||
.child(self.render_text_input(&search.query_editor, cx))
|
.child(self.render_text_input(&search.query_editor, cx))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.child(
|
.child(SearchOptions::CASE_SENSITIVE.as_button(
|
||||||
IconButton::new("project-search-filter-button", IconName::Filter)
|
self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx),
|
||||||
.tooltip(|cx| Tooltip::for_action("Toggle filters", &ToggleFilters, cx))
|
cx.listener(|this, _, cx| {
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
||||||
this.toggle_filters(cx);
|
}),
|
||||||
}))
|
))
|
||||||
.selected(
|
.child(SearchOptions::WHOLE_WORD.as_button(
|
||||||
self.active_project_search
|
self.is_option_enabled(SearchOptions::WHOLE_WORD, cx),
|
||||||
.as_ref()
|
cx.listener(|this, _, cx| {
|
||||||
.map(|search| search.read(cx).filters_enabled)
|
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
||||||
.unwrap_or_default(),
|
}),
|
||||||
),
|
))
|
||||||
)
|
.child(SearchOptions::REGEX.as_button(
|
||||||
.child(
|
self.is_option_enabled(SearchOptions::REGEX, cx),
|
||||||
IconButton::new("project-search-case-sensitive", IconName::CaseSensitive)
|
cx.listener(|this, _, cx| {
|
||||||
.tooltip(|cx| {
|
this.toggle_search_option(SearchOptions::REGEX, cx);
|
||||||
Tooltip::for_action(
|
}),
|
||||||
"Toggle case sensitive",
|
)),
|
||||||
&ToggleCaseSensitive,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx))
|
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
|
||||||
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("project-search-whole-word", IconName::WholeWord)
|
|
||||||
.tooltip(|cx| {
|
|
||||||
Tooltip::for_action("Toggle whole word", &ToggleWholeWord, cx)
|
|
||||||
})
|
|
||||||
.selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx))
|
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
|
||||||
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let mode_column = v_flex().items_start().justify_start().child(
|
let mode_column = v_flex().items_start().justify_start().child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
IconButton::new("project-search-filter-button", IconName::Filter)
|
||||||
.child(
|
.tooltip(|cx| Tooltip::for_action("Toggle filters", &ToggleFilters, cx))
|
||||||
ToggleButton::new("project-search-text-button", "Text")
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
.style(ButtonStyle::Filled)
|
this.toggle_filters(cx);
|
||||||
.size(ButtonSize::Large)
|
}))
|
||||||
.selected(search.current_mode == SearchMode::Text)
|
.selected(
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
self.active_project_search
|
||||||
this.activate_search_mode(SearchMode::Text, cx)
|
.as_ref()
|
||||||
}))
|
.map(|search| search.read(cx).filters_enabled)
|
||||||
.tooltip(|cx| {
|
.unwrap_or_default(),
|
||||||
Tooltip::for_action("Toggle text search", &ActivateTextMode, cx)
|
|
||||||
})
|
|
||||||
.first(),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
ToggleButton::new("project-search-regex-button", "Regex")
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.size(ButtonSize::Large)
|
|
||||||
.selected(search.current_mode == SearchMode::Regex)
|
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
|
||||||
this.activate_search_mode(SearchMode::Regex, cx)
|
|
||||||
}))
|
|
||||||
.tooltip(|cx| {
|
|
||||||
Tooltip::for_action(
|
|
||||||
"Toggle regular expression search",
|
|
||||||
&ActivateRegexMode,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.last(),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
@ -1557,6 +1396,12 @@ impl Render for ProjectSearchBar {
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
this.toggle_replace(&ToggleReplace, cx);
|
this.toggle_replace(&ToggleReplace, cx);
|
||||||
}))
|
}))
|
||||||
|
.selected(
|
||||||
|
self.active_project_search
|
||||||
|
.as_ref()
|
||||||
|
.map(|search| search.read(cx).replace_enabled)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
.tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)),
|
.tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1573,12 +1418,11 @@ impl Render for ProjectSearchBar {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| "No matches".to_string());
|
.unwrap_or_else(|| "0/0".to_string());
|
||||||
|
|
||||||
let limit_reached = search.model.read(cx).limit_reached;
|
let limit_reached = search.model.read(cx).limit_reached;
|
||||||
|
|
||||||
let matches_column = h_flex()
|
let matches_column = h_flex()
|
||||||
.child(div().min_w(rems(6.)).child(Label::new(match_text)))
|
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("project-search-prev-match", IconName::ChevronLeft)
|
IconButton::new("project-search-prev-match", IconName::ChevronLeft)
|
||||||
.disabled(search.active_match_index.is_none())
|
.disabled(search.active_match_index.is_none())
|
||||||
|
@ -1593,6 +1437,20 @@ impl Render for ProjectSearchBar {
|
||||||
Tooltip::for_action("Go to previous match", &SelectPrevMatch, cx)
|
Tooltip::for_action("Go to previous match", &SelectPrevMatch, cx)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.mx(rems_from_px(-4.0))
|
||||||
|
.min_w(rems_from_px(40.))
|
||||||
|
.justify_center()
|
||||||
|
.items_center()
|
||||||
|
.child(
|
||||||
|
Label::new(match_text).color(if search.active_match_index.is_some() {
|
||||||
|
Color::Default
|
||||||
|
} else {
|
||||||
|
Color::Disabled
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("project-search-next-match", IconName::ChevronRight)
|
IconButton::new("project-search-next-match", IconName::ChevronRight)
|
||||||
.disabled(search.active_match_index.is_none())
|
.disabled(search.active_match_index.is_none())
|
||||||
|
@ -1614,7 +1472,6 @@ impl Render for ProjectSearchBar {
|
||||||
});
|
});
|
||||||
|
|
||||||
let search_line = h_flex()
|
let search_line = h_flex()
|
||||||
.gap_2()
|
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.child(query_column)
|
.child(query_column)
|
||||||
.child(mode_column)
|
.child(mode_column)
|
||||||
|
@ -1705,17 +1562,11 @@ impl Render for ProjectSearchBar {
|
||||||
});
|
});
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context(key_context)
|
.key_context("ProjectSearchBar")
|
||||||
.on_action(cx.listener(|this, _: &ToggleFocus, cx| this.move_focus_to_results(cx)))
|
.on_action(cx.listener(|this, _: &ToggleFocus, cx| this.move_focus_to_results(cx)))
|
||||||
.on_action(cx.listener(|this, _: &ToggleFilters, cx| {
|
.on_action(cx.listener(|this, _: &ToggleFilters, cx| {
|
||||||
this.toggle_filters(cx);
|
this.toggle_filters(cx);
|
||||||
}))
|
}))
|
||||||
.on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
|
|
||||||
this.activate_search_mode(SearchMode::Text, cx)
|
|
||||||
}))
|
|
||||||
.on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
|
|
||||||
this.activate_search_mode(SearchMode::Regex, cx)
|
|
||||||
}))
|
|
||||||
.capture_action(cx.listener(|this, action, cx| {
|
.capture_action(cx.listener(|this, action, cx| {
|
||||||
this.tab(action, cx);
|
this.tab(action, cx);
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
|
@ -1725,9 +1576,6 @@ impl Render for ProjectSearchBar {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
}))
|
}))
|
||||||
.on_action(cx.listener(|this, action, cx| this.confirm(action, cx)))
|
.on_action(cx.listener(|this, action, cx| this.confirm(action, cx)))
|
||||||
.on_action(cx.listener(|this, action, cx| {
|
|
||||||
this.cycle_mode(action, cx);
|
|
||||||
}))
|
|
||||||
.on_action(cx.listener(|this, action, cx| {
|
.on_action(cx.listener(|this, action, cx| {
|
||||||
this.toggle_replace(action, cx);
|
this.toggle_replace(action, cx);
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
pub use buffer_search::BufferSearchBar;
|
pub use buffer_search::BufferSearchBar;
|
||||||
use gpui::{actions, Action, AppContext, IntoElement};
|
use gpui::{actions, Action, AppContext, IntoElement};
|
||||||
pub use mode::SearchMode;
|
|
||||||
use project::search::SearchQuery;
|
use project::search::SearchQuery;
|
||||||
pub use project_search::ProjectSearchView;
|
pub use project_search::ProjectSearchView;
|
||||||
use ui::{prelude::*, Tooltip};
|
use ui::{prelude::*, Tooltip};
|
||||||
use ui::{ButtonStyle, IconButton};
|
use ui::{ButtonStyle, IconButton};
|
||||||
|
|
||||||
pub mod buffer_search;
|
pub mod buffer_search;
|
||||||
mod mode;
|
|
||||||
pub mod project_search;
|
pub mod project_search;
|
||||||
pub(crate) mod search_bar;
|
pub(crate) mod search_bar;
|
||||||
|
|
||||||
|
@ -21,19 +19,17 @@ pub fn init(cx: &mut AppContext) {
|
||||||
actions!(
|
actions!(
|
||||||
search,
|
search,
|
||||||
[
|
[
|
||||||
CycleMode,
|
|
||||||
FocusSearch,
|
FocusSearch,
|
||||||
ToggleWholeWord,
|
ToggleWholeWord,
|
||||||
ToggleCaseSensitive,
|
ToggleCaseSensitive,
|
||||||
ToggleIncludeIgnored,
|
ToggleIncludeIgnored,
|
||||||
|
ToggleRegex,
|
||||||
ToggleReplace,
|
ToggleReplace,
|
||||||
SelectNextMatch,
|
SelectNextMatch,
|
||||||
SelectPrevMatch,
|
SelectPrevMatch,
|
||||||
SelectAllMatches,
|
SelectAllMatches,
|
||||||
NextHistoryQuery,
|
NextHistoryQuery,
|
||||||
PreviousHistoryQuery,
|
PreviousHistoryQuery,
|
||||||
ActivateTextMode,
|
|
||||||
ActivateRegexMode,
|
|
||||||
ReplaceAll,
|
ReplaceAll,
|
||||||
ReplaceNext,
|
ReplaceNext,
|
||||||
]
|
]
|
||||||
|
@ -46,15 +42,17 @@ bitflags! {
|
||||||
const WHOLE_WORD = 0b001;
|
const WHOLE_WORD = 0b001;
|
||||||
const CASE_SENSITIVE = 0b010;
|
const CASE_SENSITIVE = 0b010;
|
||||||
const INCLUDE_IGNORED = 0b100;
|
const INCLUDE_IGNORED = 0b100;
|
||||||
|
const REGEX = 0b1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchOptions {
|
impl SearchOptions {
|
||||||
pub fn label(&self) -> &'static str {
|
pub fn label(&self) -> &'static str {
|
||||||
match *self {
|
match *self {
|
||||||
SearchOptions::WHOLE_WORD => "Match Whole Word",
|
SearchOptions::WHOLE_WORD => "whole word",
|
||||||
SearchOptions::CASE_SENSITIVE => "Match Case",
|
SearchOptions::CASE_SENSITIVE => "match case",
|
||||||
SearchOptions::INCLUDE_IGNORED => "Include ignored",
|
SearchOptions::INCLUDE_IGNORED => "include Ignored",
|
||||||
|
SearchOptions::REGEX => "regular expression",
|
||||||
_ => panic!("{:?} is not a named SearchOption", self),
|
_ => panic!("{:?} is not a named SearchOption", self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +62,7 @@ impl SearchOptions {
|
||||||
SearchOptions::WHOLE_WORD => ui::IconName::WholeWord,
|
SearchOptions::WHOLE_WORD => ui::IconName::WholeWord,
|
||||||
SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive,
|
SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive,
|
||||||
SearchOptions::INCLUDE_IGNORED => ui::IconName::FileGit,
|
SearchOptions::INCLUDE_IGNORED => ui::IconName::FileGit,
|
||||||
|
SearchOptions::REGEX => ui::IconName::Regex,
|
||||||
_ => panic!("{:?} is not a named SearchOption", self),
|
_ => panic!("{:?} is not a named SearchOption", self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,6 +72,7 @@ impl SearchOptions {
|
||||||
SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
|
SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
|
||||||
SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
|
SearchOptions::CASE_SENSITIVE => Box::new(ToggleCaseSensitive),
|
||||||
SearchOptions::INCLUDE_IGNORED => Box::new(ToggleIncludeIgnored),
|
SearchOptions::INCLUDE_IGNORED => Box::new(ToggleIncludeIgnored),
|
||||||
|
SearchOptions::REGEX => Box::new(ToggleRegex),
|
||||||
_ => panic!("{:?} is not a named SearchOption", self),
|
_ => panic!("{:?} is not a named SearchOption", self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ impl SearchOptions {
|
||||||
options.set(SearchOptions::WHOLE_WORD, query.whole_word());
|
options.set(SearchOptions::WHOLE_WORD, query.whole_word());
|
||||||
options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
|
options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
|
||||||
options.set(SearchOptions::INCLUDE_IGNORED, query.include_ignored());
|
options.set(SearchOptions::INCLUDE_IGNORED, query.include_ignored());
|
||||||
|
options.set(SearchOptions::REGEX, query.is_regex());
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,7 @@ pub enum IconName {
|
||||||
Plus,
|
Plus,
|
||||||
Public,
|
Public,
|
||||||
Quote,
|
Quote,
|
||||||
|
Regex,
|
||||||
Replace,
|
Replace,
|
||||||
ReplaceAll,
|
ReplaceAll,
|
||||||
ReplaceNext,
|
ReplaceNext,
|
||||||
|
@ -196,6 +197,7 @@ impl IconName {
|
||||||
IconName::Plus => "icons/plus.svg",
|
IconName::Plus => "icons/plus.svg",
|
||||||
IconName::Public => "icons/public.svg",
|
IconName::Public => "icons/public.svg",
|
||||||
IconName::Quote => "icons/quote.svg",
|
IconName::Quote => "icons/quote.svg",
|
||||||
|
IconName::Regex => "icons/regex.svg",
|
||||||
IconName::Replace => "icons/replace.svg",
|
IconName::Replace => "icons/replace.svg",
|
||||||
IconName::ReplaceAll => "icons/replace_all.svg",
|
IconName::ReplaceAll => "icons/replace_all.svg",
|
||||||
IconName::ReplaceNext => "icons/replace_next.svg",
|
IconName::ReplaceNext => "icons/replace_next.svg",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use gpui::{rems, Length, Rems, WindowContext};
|
use gpui::{rems, Length, Rems, WindowContext};
|
||||||
|
|
||||||
/// The base size of a rem, in pixels.
|
/// The base size of a rem, in pixels.
|
||||||
pub(crate) const BASE_REM_SIZE_IN_PX: f32 = 16.;
|
pub const BASE_REM_SIZE_IN_PX: f32 = 16.;
|
||||||
|
|
||||||
/// Returns a rem value derived from the provided pixel value and the base rem size (16px).
|
/// Returns a rem value derived from the provided pixel value and the base rem size (16px).
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use gpui::{actions, impl_actions, ViewContext};
|
use gpui::{actions, impl_actions, ViewContext};
|
||||||
use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
|
use search::{buffer_search, BufferSearchBar, SearchOptions};
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
use workspace::{searchable::Direction, Workspace};
|
use workspace::{searchable::Direction, Workspace};
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
|
||||||
|
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
search_bar.set_replacement(None, cx);
|
search_bar.set_replacement(None, cx);
|
||||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
search_bar.set_search_options(SearchOptions::REGEX, cx);
|
||||||
}
|
}
|
||||||
vim.workspace_state.search = SearchState {
|
vim.workspace_state.search = SearchState {
|
||||||
direction,
|
direction,
|
||||||
|
@ -228,7 +228,7 @@ pub fn move_to_internal(
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||||
let search = search_bar.update(cx, |search_bar, cx| {
|
let search = search_bar.update(cx, |search_bar, cx| {
|
||||||
let options = SearchOptions::CASE_SENSITIVE;
|
let options = SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX;
|
||||||
if !search_bar.show(cx) {
|
if !search_bar.show(cx) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,6 @@ pub fn move_to_internal(
|
||||||
if whole_word {
|
if whole_word {
|
||||||
query = format!(r"\b{}\b", query);
|
query = format!(r"\b{}\b", query);
|
||||||
}
|
}
|
||||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
|
||||||
Some(search_bar.search(&query, Some(options), cx))
|
Some(search_bar.search(&query, Some(options), cx))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -288,8 +287,11 @@ fn find_command(workspace: &mut Workspace, action: &FindCommand, cx: &mut ViewCo
|
||||||
query = search_bar.query(cx);
|
query = search_bar.query(cx);
|
||||||
};
|
};
|
||||||
|
|
||||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
Some(search_bar.search(
|
||||||
Some(search_bar.search(&query, Some(SearchOptions::CASE_SENSITIVE), cx))
|
&query,
|
||||||
|
Some(SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX),
|
||||||
|
cx,
|
||||||
|
))
|
||||||
});
|
});
|
||||||
let Some(search) = search else { return };
|
let Some(search) = search else { return };
|
||||||
let search_bar = search_bar.downgrade();
|
let search_bar = search_bar.downgrade();
|
||||||
|
@ -326,7 +328,7 @@ fn replace_command(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut options = SearchOptions::default();
|
let mut options = SearchOptions::REGEX;
|
||||||
if replacement.is_case_sensitive {
|
if replacement.is_case_sensitive {
|
||||||
options.set(SearchOptions::CASE_SENSITIVE, true)
|
options.set(SearchOptions::CASE_SENSITIVE, true)
|
||||||
}
|
}
|
||||||
|
@ -337,7 +339,6 @@ fn replace_command(
|
||||||
};
|
};
|
||||||
|
|
||||||
search_bar.set_replacement(Some(&replacement.replacement), cx);
|
search_bar.set_replacement(Some(&replacement.replacement), cx);
|
||||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
|
||||||
Some(search_bar.search(&search, Some(options), cx))
|
Some(search_bar.search(&search, Some(options), cx))
|
||||||
});
|
});
|
||||||
let Some(search) = search else { return };
|
let Some(search) = search else { return };
|
||||||
|
|
|
@ -55,6 +55,8 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_bar_visibility_changed(&mut self, _visible: bool, _cx: &mut ViewContext<Self>) {}
|
||||||
|
|
||||||
fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
|
fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
|
||||||
fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
|
fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
|
||||||
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
|
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
|
||||||
|
@ -131,6 +133,7 @@ pub trait SearchableItemHandle: ItemHandle {
|
||||||
matches: &AnyVec<dyn Send>,
|
matches: &AnyVec<dyn Send>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Option<usize>;
|
) -> Option<usize>;
|
||||||
|
fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: SearchableItem> SearchableItemHandle for View<T> {
|
impl<T: SearchableItem> SearchableItemHandle for View<T> {
|
||||||
|
@ -227,6 +230,12 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
|
||||||
let mat = mat.downcast_ref().unwrap();
|
let mat = mat.downcast_ref().unwrap();
|
||||||
self.update(cx, |this, cx| this.replace(mat, query, cx))
|
self.update(cx, |this, cx| this.replace(mat, query, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext) {
|
||||||
|
self.update(cx, |this, cx| {
|
||||||
|
this.search_bar_visibility_changed(visible, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Box<dyn SearchableItemHandle>> for AnyView {
|
impl From<Box<dyn SearchableItemHandle>> for AnyView {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue