Vscode merge (#4582)

* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd

* fix issues with merges

* bump node version in azpipe

* replace license headers

* remove duplicate launch task

* fix build errors

* fix build errors

* fix tslint issues

* working through package and linux build issues

* more work

* wip

* fix packaged builds

* working through linux build errors

* wip

* wip

* wip

* fix mac and linux file limits

* iterate linux pipeline

* disable editor typing

* revert series to parallel

* remove optimize vscode from linux

* fix linting issues

* revert testing change

* add work round for new node

* readd packaging for extensions

* fix issue with angular not resolving decorator dependencies
This commit is contained in:
Anthony Dresser
2019-03-19 17:44:35 -07:00
committed by GitHub
parent 833d197412
commit 87765e8673
1879 changed files with 54505 additions and 38058 deletions

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 0 16 16" enable-background="new -1 0 16 16"><path fill="#424242" d="M14 1v9h-1v-8h-8v-1h9zm-11 2v1h8v8h1v-9h-9zm7 2v9h-9v-9h9zm-2 2h-5v5h5v-5z"/><rect x="4" y="9" fill="#00539C" width="3" height="1"/></svg>

After

Width:  |  Height:  |  Size: 281 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 0 16 16" enable-background="new -1 0 16 16"><path fill="#C5C5C5" d="M14 1v9h-1v-8h-8v-1h9zm-11 2v1h8v8h1v-9h-9zm7 2v9h-9v-9h9zm-2 2h-5v5h5v-5z"/><rect x="4" y="9" fill="#75BEFF" width="3" height="1"/></svg>

After

Width:  |  Height:  |  Size: 281 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#F6F6F6"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#2D2D30"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#C5C5C5"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#F48771"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#424242"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#A1260D"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>Ellipsis_bold_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M6,7.5A2.5,2.5,0,1,1,3.5,5,2.5,2.5,0,0,1,6,7.5ZM8.5,5A2.5,2.5,0,1,0,11,7.5,2.5,2.5,0,0,0,8.5,5Zm5,0A2.5,2.5,0,1,0,16,7.5,2.5,2.5,0,0,0,13.5,5Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M5,7.5A1.5,1.5,0,1,1,3.5,6,1.5,1.5,0,0,1,5,7.5ZM8.5,6A1.5,1.5,0,1,0,10,7.5,1.5,1.5,0,0,0,8.5,6Zm5,0A1.5,1.5,0,1,0,15,7.5,1.5,1.5,0,0,0,13.5,6Z"/></g></svg>

After

Width:  |  Height:  |  Size: 748 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>Ellipsis_bold_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M6,7.5A2.5,2.5,0,1,1,3.5,5,2.5,2.5,0,0,1,6,7.5ZM8.5,5A2.5,2.5,0,1,0,11,7.5,2.5,2.5,0,0,0,8.5,5Zm5,0A2.5,2.5,0,1,0,16,7.5,2.5,2.5,0,0,0,13.5,5Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M5,7.5A1.5,1.5,0,1,1,3.5,6,1.5,1.5,0,0,1,5,7.5ZM8.5,6A1.5,1.5,0,1,0,10,7.5,1.5,1.5,0,0,0,8.5,6Zm5,0A1.5,1.5,0,1,0,15,7.5,1.5,1.5,0,0,0,13.5,6Z"/></g></svg>

After

Width:  |  Height:  |  Size: 748 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 10.015l-.258.043c.155.455.258.935.258 1.442 0 2.481-2.019 4.5-4.5 4.5-.508 0-.988-.103-1.444-.259l-.043.259H5.986l-.373-2.237-1.846 1.317-2.848-2.847 1.319-1.847L0 10.013V5.986l2.238-.373L.919 3.767 3.768.919l1.846 1.319L5.986 0h4.028l.372 2.238L12.233.919l2.847 2.848-1.318 1.846L16 5.986v4.029z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M7 11.5c0-.501.101-.975.253-1.426A2.207 2.207 0 0 1 8 5.788c.958 0 1.767.613 2.074 1.466A4.417 4.417 0 0 1 11.5 7c1.421 0 2.675.675 3.5 1.706V6.834l-2.121-.354a5.14 5.14 0 0 0-.354-.854l1.25-1.75-1.65-1.65-1.752 1.251c-.137-.072-.262-.159-.408-.219-.147-.061-.296-.088-.444-.134L9.167 1H6.834L6.48 3.121c-.295.092-.581.21-.854.354l-1.75-1.25-1.65 1.65 1.252 1.752c-.073.137-.16.263-.221.409-.06.145-.087.295-.133.443L1 6.833v2.333l2.121.354c.092.295.21.581.354.854l-1.25 1.75 1.65 1.65 1.752-1.251c.137.072.262.159.408.219.146.06.296.087.444.133L6.833 15h1.873C7.675 14.175 7 12.921 7 11.5z" id="iconBg"/><path class="icon-vs-bg" d="M11.5 8a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7zM9 12v-1h5v1H9z" id="notificationBg"/><g id="notificationFg"><path class="icon-vs-fg" d="M9 11h5v1H9z" style="display: none;"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 10.015l-.258.043c.155.455.258.935.258 1.442 0 2.481-2.019 4.5-4.5 4.5-.508 0-.988-.103-1.444-.259l-.043.259H5.986l-.373-2.237-1.846 1.317-2.848-2.847 1.319-1.847L0 10.013V5.986l2.238-.373L.919 3.767 3.768.919l1.846 1.319L5.986 0h4.028l.372 2.238L12.233.919l2.847 2.848-1.318 1.846L16 5.986v4.029z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M7 11.5c0-.501.101-.975.253-1.426A2.207 2.207 0 0 1 8 5.788c.958 0 1.767.613 2.074 1.466A4.417 4.417 0 0 1 11.5 7c1.421 0 2.675.675 3.5 1.706V6.834l-2.121-.354a5.14 5.14 0 0 0-.354-.854l1.25-1.75-1.65-1.65-1.752 1.251c-.137-.072-.262-.159-.408-.219-.147-.061-.296-.088-.444-.134L9.167 1H6.834L6.48 3.121c-.295.092-.581.21-.854.354l-1.75-1.25-1.65 1.65 1.252 1.752c-.073.137-.16.263-.221.409-.06.145-.087.295-.133.443L1 6.833v2.333l2.121.354c.092.295.21.581.354.854l-1.25 1.75 1.65 1.65 1.752-1.251c.137.072.262.159.408.219.146.06.296.087.444.133L6.833 15h1.873C7.675 14.175 7 12.921 7 11.5z" id="iconBg"/><path class="icon-vs-bg" d="M11.5 8a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7zM9 12v-1h5v1H9z" id="notificationBg"/><g id="notificationFg"><path class="icon-vs-fg" d="M9 11h5v1H9z" style="display: none;"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>

After

Width:  |  Height:  |  Size: 151 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>

After

Width:  |  Height:  |  Size: 151 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>

After

Width:  |  Height:  |  Size: 131 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>

After

Width:  |  Height:  |  Size: 131 B

View File

@@ -0,0 +1,11 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px"
height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="icon_x5F_bg">
<path fill="#C5C5C5" d="M11,15V9H1v6H11z M2,14v-2h1v-1H2v-1h3v4H2z M10,11H8v2h2v1H7v-4h3V11z M3,13v-1h1v1H3z M13,7v6h-1V8H5V7
H13z M13,2V1h-1v5h3V2H13z M14,5h-1V3h1V5z M11,2v4H8V4h1v1h1V4H9V3H8V2H11z"/>
</g>
<g id="color_x5F_action">
<path fill="#75BEFF" d="M1.979,3.5L2,6L1,5v1.5L2.5,8L4,6.5V5L3,6L2.979,3.5c0-0.275,0.225-0.5,0.5-0.5H7V2H3.479
C2.651,2,1.979,2.673,1.979,3.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@@ -0,0 +1,11 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px"
height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="icon_x5F_bg">
<path fill="#424242" d="M11,15V9H1v6H11z M2,14v-2h1v-1H2v-1h3v4H2z M10,11H8v2h2v1H7v-4h3V11z M3,13v-1h1v1H3z M13,7v6h-1V8H5V7
H13z M13,2V1h-1v5h3V2H13z M14,5h-1V3h1V5z M11,2v4H8V4h1v1h1V4H9V3H8V2H11z"/>
</g>
<g id="color_x5F_action">
<path fill="#00539C" d="M1.979,3.5L2,6L1,5v1.5L2.5,8L4,6.5V5L3,6L2.979,3.5c0-0.275,0.225-0.5,0.5-0.5H7V2H3.479
C2.651,2,1.979,2.673,1.979,3.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@@ -0,0 +1,13 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px"
height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="icon_x5F_bg">
<g>
<path fill="#C5C5C5" d="M11,3V1h-1v5v1h1h2h1V4V3H11z M13,6h-2V4h2V6z"/>
<path fill="#C5C5C5" d="M2,15h7V9H2V15z M4,10h3v1H5v2h2v1H4V10z"/>
</g>
</g>
<g id="color_x5F_importance">
<path fill="#75BEFF" d="M3.979,3.5L4,6L3,5v1.5L4.5,8L6,6.5V5L5,6L4.979,3.5c0-0.275,0.225-0.5,0.5-0.5H9V2H5.479
C4.651,2,3.979,2.673,3.979,3.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 589 B

View File

@@ -0,0 +1,13 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px"
height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="icon_x5F_bg">
<g>
<path fill="#424242" d="M11,3V1h-1v5v1h1h2h1V4V3H11z M13,6h-2V4h2V6z"/>
<path fill="#424242" d="M2,15h7V9H2V15z M4,10h3v1H5v2h2v1H4V10z"/>
</g>
</g>
<g id="color_x5F_importance">
<path fill="#00539C" d="M3.979,3.5L4,6L3,5v1.5L4.5,8L6,6.5V5L5,6L4.979,3.5c0-0.275,0.225-0.5,0.5-0.5H9V2H5.479
C4.651,2,3.979,2.673,3.979,3.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 589 B

View File

@@ -0,0 +1 @@
<svg fill="none" height="28" viewBox="0 0 28 28" width="28" xmlns="http://www.w3.org/2000/svg"><path d="m17.1249 2c-4.9127 0-8.89701 3.98533-8.89701 8.899 0 1.807.54686 3.4801 1.47014 4.8853 0 0-5.562 5.5346-7.20564 7.2056-1.644662 1.6701 1.0156 4.1304 2.63997 2.4442 1.62538-1.6832 7.10824-7.1072 7.10824-7.1072 1.4042.9243 3.0793 1.4711 4.8843 1.4711 4.9157 0 8.9-3.9873 8.9-8.899.001-4.91469-3.9843-8.899-8.9-8.899zm0 15.2544c-3.5095 0-6.3565-2.8449-6.3565-6.3554 0-3.51049 2.846-6.35643 6.3565-6.35643 3.5125 0 6.3574 2.84493 6.3574 6.35643 0 3.5105-2.8449 6.3554-6.3574 6.3554z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 603 B

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Activity Bar */
.monaco-workbench .activitybar .monaco-action-bar .action-label.search {
-webkit-mask: url('search-dark.svg') no-repeat 50% 50%;
}

View File

@@ -0,0 +1,398 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.search-view .search-widgets-container {
margin: 0px 9px 0 2px;
padding-top: 6px;
}
.search-view .search-widget .toggle-replace-button {
position: absolute;
top: 0;
left: 0;
width: 16px;
height: 100%;
box-sizing: border-box;
background-position: center center;
background-repeat: no-repeat;
cursor: pointer;
}
.search-view .search-widget .search-container,
.search-view .search-widget .replace-container {
margin-left: 17px;
}
.search-view .search-widget .monaco-inputbox > .wrapper {
height: 100%;
}
.search-view .search-widget .monaco-inputbox > .wrapper > .mirror,
.search-view .search-widget .monaco-inputbox > .wrapper > textarea.input {
padding: 3px;
padding-left: 4px;
}
.search-view .search-widget .monaco-inputbox > .wrapper > .mirror {
max-height: 134px;
}
.search-view .search-widget .monaco-inputbox > .wrapper > textarea.input {
overflow: initial;
height: 24px; /* set initial height before measure */
}
.search-view .monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
display: none;
}
.search-view .search-widget .monaco-findInput {
display: inline-block;
vertical-align: middle;
width: 100%;
}
.search-view .search-widget .replace-container {
margin-top: 6px;
position: relative;
display: inline-flex;
}
.search-view .search-widget .replace-container.disabled {
display: none;
}
.search-view .search-widget .replace-container .monaco-action-bar {
margin-left: 3px;
}
.search-view .search-widget .replace-container .monaco-action-bar {
height: 25px;
}
.search-view .search-widget .replace-container .monaco-action-bar .action-item .icon {
background-repeat: no-repeat;
width: 20px;
height: 25px;
}
.search-view .query-details {
min-height: 1em;
position: relative;
margin: 0 0 0 17px;
}
.search-view .query-details .more {
position: absolute;
margin-right: 0.3em;
right: 0;
cursor: pointer;
width: 16px;
height: 13px;
z-index: 2; /* Force it above the search results message, which has a negative top margin */
}
.hc-black .monaco-workbench .search-view .query-details .more,
.vs-dark .monaco-workbench .search-view .query-details .more {
background: url('ellipsis-inverse.svg') top center no-repeat;
}
.vs .monaco-workbench .search-view .query-details .more {
background: url('ellipsis.svg') top center no-repeat;
}
.search-view .query-details .file-types {
display: none;
}
.search-view .query-details .file-types > .monaco-inputbox {
width: 100%;
height: 25px;
}
.search-view .query-details.more .file-types {
display: inherit;
}
.search-view .query-details.more .file-types:last-child {
padding-bottom: 10px;
}
.search-view .query-details.more h4 {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding: 4px 0 0;
margin: 0;
font-size: 11px;
font-weight: normal;
}
.search-view .messages {
margin-top: -5px;
cursor: default;
}
.search-view .message {
padding-left: 22px;
padding-right: 22px;
padding-top: 0px;
}
.search-view .message p:first-child {
margin-top: 0px;
margin-bottom: 0px;
padding-bottom: 4px;
user-select: text;
}
.search-view .foldermatch,
.search-view .filematch {
display: flex;
position: relative;
line-height: 22px;
padding: 0;
}
.search-view:not(.wide) .foldermatch .monaco-icon-label,
.search-view:not(.wide) .filematch .monaco-icon-label {
flex: 1;
}
.search-view:not(.wide) .monaco-list .monaco-list-row:hover:not(.highlighted) .foldermatch .monaco-icon-label,
.search-view:not(.wide) .monaco-list .monaco-list-row.focused .foldermatch .monaco-icon-label,
.search-view:not(.wide) .monaco-list .monaco-list-row:hover:not(.highlighted) .filematch .monaco-icon-label,
.search-view:not(.wide) .monaco-list .monaco-list-row.focused .filematch .monaco-icon-label {
flex: 1;
}
.search-view.wide .foldermatch .badge,
.search-view.wide .filematch .badge {
margin-left: 10px;
}
.search-view .linematch {
position: relative;
line-height: 22px;
display: flex;
overflow: hidden;
}
.search-view .linematch > .match {
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
}
.search-view .linematch .matchLineNum {
margin-left: 7px;
margin-right: 4px;
opacity: .7;
font-size: 0.9em;
display: none;
}
.search-view .linematch .matchLineNum.show {
display: block;
}
.search-view.wide .monaco-list .monaco-list-row .foldermatch .actionBarContainer,
.search-view.wide .monaco-list .monaco-list-row .filematch .actionBarContainer,
.search-view .monaco-list .monaco-list-row .linematch .actionBarContainer {
flex: 1 0 auto;
}
.search-view:not(.wide) .monaco-list .monaco-list-row .foldermatch .actionBarContainer,
.search-view:not(.wide) .monaco-list .monaco-list-row .filematch .actionBarContainer {
flex: 0 0 auto;
}
.search-view.actions-right .monaco-list .monaco-list-row .foldermatch .actionBarContainer,
.search-view.actions-right .monaco-list .monaco-list-row .filematch .actionBarContainer,
.search-view.actions-right .monaco-list .monaco-list-row .linematch .actionBarContainer,
.search-view:not(.wide) .monaco-list .monaco-list-row .linematch .actionBarContainer {
text-align: right;
}
.search-view .monaco-list .monaco-list-row .monaco-action-bar {
line-height: 1em;
display: none;
padding: 0 0.8em 0 0.4em;
}
.search-view .monaco-list .monaco-list-row .monaco-action-bar .action-item {
margin: 0;
}
.search-view .monaco-list .monaco-list-row:hover:not(.highlighted) .monaco-action-bar,
.search-view .monaco-list .monaco-list-row.focused .monaco-action-bar {
display: inline-block;
}
.search-view .monaco-list .monaco-list-row .monaco-action-bar .action-label {
margin-right: 0.2em;
margin-top: 4px;
background-repeat: no-repeat;
width: 16px;
height: 16px;
}
/* Adjusts spacing in high contrast mode so that actions are vertically centered */
.hc-black .monaco-list .monaco-list-row .monaco-action-bar .action-label {
margin-top: 2px;
}
.search-view .action-remove {
background: url("action-remove.svg") center center no-repeat;
}
.search-view .action-replace {
background-image: url('replace.svg');
}
.search-view .action-replace-all {
background: url('replace-all.svg') center center no-repeat;
}
.hc-black .search-view .action-replace,
.vs-dark .search-view .action-replace {
background-image: url('replace-inverse.svg');
}
.hc-black .search-view .action-replace-all,
.vs-dark .search-view .action-replace-all {
background: url('replace-all-inverse.svg') center center no-repeat;
}
.search-view .monaco-count-badge {
margin-right: 12px;
}
.search-view:not(.wide) > .results > .monaco-list .monaco-list-row:hover .filematch .monaco-count-badge,
.search-view:not(.wide) > .results > .monaco-list .monaco-list-row:hover .foldermatch .monaco-count-badge,
.search-view:not(.wide) > .results > .monaco-list .monaco-list-row:hover .linematch .monaco-count-badge,
.search-view:not(.wide) > .results > .monaco-list .monaco-list-row.focused .filematch .monaco-count-badge,
.search-view:not(.wide) > .results > .monaco-list .monaco-list-row.focused .foldermatch .monaco-count-badge,
.search-view:not(.wide) > .results > .monaco-list .monaco-list-row.focused .linematch .monaco-count-badge {
display: none;
}
.monaco-workbench .search-action.refresh {
background: url('Refresh.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .search-action.refresh,
.hc-black .monaco-workbench .search-action.refresh {
background: url('Refresh_inverse.svg') center center no-repeat;
}
.monaco-workbench .search-action.collapse {
background: url('CollapseAll.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .search-action.collapse,
.hc-black .monaco-workbench .search-action.collapse {
background: url('CollapseAll_inverse.svg') center center no-repeat;
}
.monaco-workbench .search-action.clear-search-results {
background: url('clear-search-results.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .search-action.clear-search-results,
.hc-black .monaco-workbench .search-action.clear-search-results {
background: url('clear-search-results-dark.svg') center center no-repeat;
}
.monaco-workbench .search-action.cancel-search {
background: url('stop.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .search-action.cancel-search,
.hc-black .monaco-workbench .search-action.cancel-search {
background: url('stop-inverse.svg') center center no-repeat;
}
.vs .monaco-workbench .search-view .query-details .file-types .controls>.monaco-custom-checkbox.useExcludesAndIgnoreFiles {
background: url('excludeSettings.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .search-view .query-details .file-types .controls>.monaco-custom-checkbox.useExcludesAndIgnoreFiles,
.hc-black .monaco-workbench .search-view .query-details .file-types .controls>.monaco-custom-checkbox.useExcludesAndIgnoreFiles {
background: url('excludeSettings-dark.svg') center center no-repeat;
}
.search-view .replace.findInFileMatch {
text-decoration: line-through;
}
.search-view .findInFileMatch,
.search-view .replaceMatch {
white-space: pre;
}
.hc-black .monaco-workbench .search-view .replaceMatch,
.hc-black .monaco-workbench .search-view .findInFileMatch {
background: none !important;
box-sizing: border-box;
}
.monaco-workbench .search-view a.prominent {
text-decoration: underline;
}
/* Theming */
.vs .search-view .search-widget .toggle-replace-button:hover {
background-color: rgba(0, 0, 0, 0.1) !important;
}
.vs-dark .search-view .search-widget .toggle-replace-button:hover {
background-color: rgba(255, 255, 255, 0.1) !important;
}
.vs .search-view .search-widget .toggle-replace-button.collapse {
background-image: url('expando-collapsed.svg');
}
.vs .search-view .search-widget .toggle-replace-button.expand {
background-image: url('expando-expanded.svg');
}
.vs-dark .search-view .action-remove,
.hc-black .search-view .action-remove {
background: url("action-remove-dark.svg") center center no-repeat;
}
.vs-dark .search-view .message {
opacity: .5;
}
.vs-dark .search-view .foldermatch,
.vs-dark .search-view .filematch {
padding: 0;
}
.vs-dark .search-view .search-widget .toggle-replace-button.expand,
.hc-black .search-view .search-widget .toggle-replace-button.expand {
background-image: url('expando-expanded-dark.svg');
}
.vs-dark .search-view .search-widget .toggle-replace-button.collapse,
.hc-black .search-view .search-widget .toggle-replace-button.collapse {
background-image: url('expando-collapsed-dark.svg');
}
/* High Contrast Theming */
.hc-black .monaco-workbench .search-view .foldermatch,
.hc-black .monaco-workbench .search-view .filematch,
.hc-black .monaco-workbench .search-view .linematch {
line-height: 20px;
}
.vs .panel .search-view .monaco-inputbox {
border: 1px solid #ddd;
}

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 11H3V3H11V11Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 147 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 11H3V3H11V11Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 147 B

View File

@@ -0,0 +1,249 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from 'vs/base/common/arrays';
import * as nls from 'vs/nls';
import { ThrottledDelayer } from 'vs/base/common/async';
import * as types from 'vs/base/common/types';
import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenEntry, QuickOpenModel, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { FileEntry, OpenFileHandler, FileQuickOpenModel } from 'vs/workbench/contrib/search/browser/openFileHandler';
import * as openSymbolHandler from 'vs/workbench/contrib/search/browser/openSymbolHandler';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchSearchConfiguration } from 'vs/workbench/contrib/search/common/search';
import { IRange } from 'vs/editor/common/core/range';
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { CancellationToken } from 'vs/base/common/cancellation';
export import OpenSymbolHandler = openSymbolHandler.OpenSymbolHandler; // OpenSymbolHandler is used from an extension and must be in the main bundle file so it can load
interface ISearchWithRange {
search: string;
range: IRange;
}
export class OpenAnythingHandler extends QuickOpenHandler {
static readonly ID = 'workbench.picker.anything';
private static readonly LINE_COLON_PATTERN = /[#:\(](\d*)([#:,](\d*))?\)?$/;
private static readonly TYPING_SEARCH_DELAY = 200; // This delay accommodates for the user typing a word and then stops typing to start searching
private static readonly MAX_DISPLAYED_RESULTS = 512;
private openSymbolHandler: OpenSymbolHandler;
private openFileHandler: OpenFileHandler;
private searchDelayer: ThrottledDelayer<QuickOpenModel>;
private isClosed: boolean;
private scorerCache: ScorerCache;
private includeSymbols: boolean;
constructor(
@INotificationService private readonly notificationService: INotificationService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
this.scorerCache = Object.create(null);
this.searchDelayer = new ThrottledDelayer<QuickOpenModel>(OpenAnythingHandler.TYPING_SEARCH_DELAY);
this.openSymbolHandler = instantiationService.createInstance(OpenSymbolHandler);
this.openFileHandler = instantiationService.createInstance(OpenFileHandler);
this.updateHandlers(this.configurationService.getValue<IWorkbenchSearchConfiguration>());
this.registerListeners();
}
private registerListeners(): void {
this.configurationService.onDidChangeConfiguration(e => this.updateHandlers(this.configurationService.getValue<IWorkbenchSearchConfiguration>()));
}
private updateHandlers(configuration: IWorkbenchSearchConfiguration): void {
this.includeSymbols = configuration && configuration.search && configuration.search.quickOpen && configuration.search.quickOpen.includeSymbols;
// Files
this.openFileHandler.setOptions({
forceUseIcons: this.includeSymbols // only need icons for file results if we mix with symbol results
});
// Symbols
this.openSymbolHandler.setOptions({
skipDelay: true, // we have our own delay
skipLocalSymbols: true, // we only want global symbols
skipSorting: true // we sort combined with file results
});
}
getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
this.isClosed = false; // Treat this call as the handler being in use
// Find a suitable range from the pattern looking for ":" and "#"
const searchWithRange = this.extractRange(searchValue);
if (searchWithRange) {
searchValue = searchWithRange.search; // ignore range portion in query
}
// Prepare search for scoring
const query = prepareQuery(searchValue);
if (!query.value) {
return Promise.resolve(new QuickOpenModel()); // Respond directly to empty search
}
// The throttler needs a factory for its promises
const resultsPromise = () => {
const resultPromises: Promise<QuickOpenModel | FileQuickOpenModel>[] = [];
// File Results
const filePromise = this.openFileHandler.getResults(query.original, token, OpenAnythingHandler.MAX_DISPLAYED_RESULTS);
resultPromises.push(filePromise);
// Symbol Results (unless disabled or a range or absolute path is specified)
if (this.includeSymbols && !searchWithRange) {
resultPromises.push(this.openSymbolHandler.getResults(query.original, token));
}
// Join and sort unified
return Promise.all(resultPromises).then(results => {
// If the quick open widget has been closed meanwhile, ignore the result
if (this.isClosed || token.isCancellationRequested) {
return Promise.resolve<QuickOpenModel>(new QuickOpenModel());
}
// Combine results.
const mergedResults: QuickOpenEntry[] = ([] as QuickOpenEntry[]).concat(...results.map(r => r.entries));
// Sort
const compare = (elementA: QuickOpenEntry, elementB: QuickOpenEntry) => compareItemsByScore(elementA, elementB, query, true, QuickOpenItemAccessor, this.scorerCache);
const viewResults = arrays.top(mergedResults, compare, OpenAnythingHandler.MAX_DISPLAYED_RESULTS);
// Apply range and highlights to file entries
viewResults.forEach(entry => {
if (entry instanceof FileEntry) {
entry.setRange(searchWithRange ? searchWithRange.range : null);
const itemScore = scoreItem(entry, query, true, QuickOpenItemAccessor, this.scorerCache);
entry.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch);
}
});
return Promise.resolve<QuickOpenModel>(new QuickOpenModel(viewResults));
}, error => {
if (!isPromiseCanceledError(error)) {
let message: Error | string;
if (error.message) {
message = error.message.replace(/[\*_\[\]]/g, '\\$&');
} else {
message = error;
}
this.notificationService.error(message);
}
return null;
});
};
// Trigger through delayer to prevent accumulation while the user is typing (except when expecting results to come from cache)
return this.hasShortResponseTime() ? resultsPromise() : this.searchDelayer.trigger(resultsPromise, OpenAnythingHandler.TYPING_SEARCH_DELAY);
}
hasShortResponseTime(): boolean {
if (!this.includeSymbols) {
return this.openFileHandler.hasShortResponseTime();
}
return this.openFileHandler.hasShortResponseTime() && this.openSymbolHandler.hasShortResponseTime();
}
private extractRange(value: string): ISearchWithRange | null {
if (!value) {
return null;
}
let range: IRange | null = null;
// Find Line/Column number from search value using RegExp
const patternMatch = OpenAnythingHandler.LINE_COLON_PATTERN.exec(value);
if (patternMatch && patternMatch.length > 1) {
const startLineNumber = parseInt(patternMatch[1], 10);
// Line Number
if (types.isNumber(startLineNumber)) {
range = {
startLineNumber: startLineNumber,
startColumn: 1,
endLineNumber: startLineNumber,
endColumn: 1
};
// Column Number
if (patternMatch.length > 3) {
const startColumn = parseInt(patternMatch[3], 10);
if (types.isNumber(startColumn)) {
range = {
startLineNumber: range.startLineNumber,
startColumn: startColumn,
endLineNumber: range.endLineNumber,
endColumn: startColumn
};
}
}
}
// User has typed "something:" or "something#" without a line number, in this case treat as start of file
else if (patternMatch[1] === '') {
range = {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 1
};
}
}
if (patternMatch && range) {
return {
search: value.substr(0, patternMatch.index), // clear range suffix from search value
range: range
};
}
return null;
}
getGroupLabel(): string {
return this.includeSymbols ? nls.localize('fileAndTypeResults', "file and symbol results") : nls.localize('fileResults', "file results");
}
getAutoFocus(searchValue: string): IAutoFocus {
return {
autoFocusFirstEntry: true
};
}
onOpen(): void {
this.openSymbolHandler.onOpen();
this.openFileHandler.onOpen();
}
onClose(canceled: boolean): void {
this.isClosed = true;
// Clear Cache
this.scorerCache = Object.create(null);
// Propagate
this.openSymbolHandler.onClose(canceled);
this.openFileHandler.onClose(canceled);
}
}

View File

@@ -0,0 +1,334 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as errors from 'vs/base/common/errors';
import * as nls from 'vs/nls';
import { isAbsolute } from 'vs/base/common/path';
import * as objects from 'vs/base/common/objects';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import { URI } from 'vs/base/common/uri';
import { basename, dirname } from 'vs/base/common/resources';
import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { IModeService } from 'vs/editor/common/services/modeService';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { QuickOpenHandler, EditorQuickOpenEntry } from 'vs/workbench/browser/quickopen';
import { QueryBuilder, IFileQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { IResourceInput } from 'vs/platform/editor/common/editor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ISearchService, IFileSearchStats, IFileQuery, ISearchComplete } from 'vs/workbench/services/search/common/search';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IRange } from 'vs/editor/common/core/range';
import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { prepareQuery, IPreparedQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { IFileService } from 'vs/platform/files/common/files';
import { ILabelService } from 'vs/platform/label/common/label';
import { untildify } from 'vs/base/common/labels';
import { CancellationToken } from 'vs/base/common/cancellation';
export class FileQuickOpenModel extends QuickOpenModel {
constructor(entries: QuickOpenEntry[], stats?: IFileSearchStats) {
super(entries);
}
}
export class FileEntry extends EditorQuickOpenEntry {
private range: IRange | null;
constructor(
private resource: URI,
private name: string,
private description: string,
private icon: string,
@IEditorService editorService: IEditorService,
@IModeService private readonly modeService: IModeService,
@IModelService private readonly modelService: IModelService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceContextService contextService: IWorkspaceContextService
) {
super(editorService);
}
getLabel(): string {
return this.name;
}
getLabelOptions(): IIconLabelValueOptions {
return {
extraClasses: getIconClasses(this.modelService, this.modeService, this.resource)
};
}
getAriaLabel(): string {
return nls.localize('entryAriaLabel', "{0}, file picker", this.getLabel());
}
getDescription(): string {
return this.description;
}
getIcon(): string {
return this.icon;
}
getResource(): URI {
return this.resource;
}
setRange(range: IRange | null): void {
this.range = range;
}
mergeWithEditorHistory(): boolean {
return true;
}
getInput(): IResourceInput | EditorInput {
const input: IResourceInput = {
resource: this.resource,
options: {
pinned: !this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench.editor.enablePreviewFromQuickOpen,
selection: this.range ? this.range : undefined
}
};
return input;
}
}
export interface IOpenFileOptions {
forceUseIcons: boolean;
}
export class OpenFileHandler extends QuickOpenHandler {
private options: IOpenFileOptions;
private queryBuilder: QueryBuilder;
private cacheState: CacheState;
constructor(
@IEditorService private readonly editorService: IEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@ISearchService private readonly searchService: ISearchService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IFileService private readonly fileService: IFileService,
@ILabelService private readonly labelService: ILabelService
) {
super();
this.queryBuilder = this.instantiationService.createInstance(QueryBuilder);
}
setOptions(options: IOpenFileOptions) {
this.options = options;
}
getResults(searchValue: string, token: CancellationToken, maxSortedResults?: number): Promise<FileQuickOpenModel> {
const query = prepareQuery(searchValue);
// Respond directly to empty search
if (!query.value) {
return Promise.resolve(new FileQuickOpenModel([]));
}
// Untildify file pattern
query.value = untildify(query.value, this.environmentService.userHome);
// Do find results
return this.doFindResults(query, token, this.cacheState.cacheKey, maxSortedResults);
}
private doFindResults(query: IPreparedQuery, token: CancellationToken, cacheKey?: string, maxSortedResults?: number): Promise<FileQuickOpenModel> {
const queryOptions = this.doResolveQueryOptions(query, cacheKey, maxSortedResults);
let iconClass: string;
if (this.options && this.options.forceUseIcons && !this.themeService.getFileIconTheme()) {
iconClass = 'file'; // only use a generic file icon if we are forced to use an icon and have no icon theme set otherwise
}
return this.getAbsolutePathResult(query).then(result => {
if (token.isCancellationRequested) {
return Promise.resolve(<ISearchComplete>{ results: [] });
}
// If the original search value is an existing file on disk, return it immediately and bypass the search service
if (result) {
return Promise.resolve(<ISearchComplete>{ results: [{ resource: result }] });
}
return this.searchService.fileSearch(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions), token);
}).then(complete => {
const results: QuickOpenEntry[] = [];
if (!token.isCancellationRequested) {
for (const fileMatch of complete.results) {
const label = basename(fileMatch.resource);
const description = this.labelService.getUriLabel(dirname(fileMatch.resource), { relative: true });
results.push(this.instantiationService.createInstance(FileEntry, fileMatch.resource, label, description, iconClass));
}
}
return new FileQuickOpenModel(results, <IFileSearchStats>complete.stats);
});
}
private getAbsolutePathResult(query: IPreparedQuery): Promise<URI | undefined> {
if (isAbsolute(query.original)) {
const resource = URI.file(query.original);
return this.fileService.resolveFile(resource).then(stat => stat.isDirectory ? undefined : resource, error => undefined);
}
return Promise.resolve(undefined);
}
private doResolveQueryOptions(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): IFileQueryBuilderOptions {
const queryOptions: IFileQueryBuilderOptions = {
_reason: 'openFileHandler',
extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService),
filePattern: query.value,
cacheKey
};
if (typeof maxSortedResults === 'number') {
queryOptions.maxResults = maxSortedResults;
queryOptions.sortByScore = true;
}
return queryOptions;
}
hasShortResponseTime(): boolean {
return this.isCacheLoaded;
}
onOpen(): void {
this.cacheState = new CacheState(cacheKey => this.cacheQuery(cacheKey), query => this.searchService.fileSearch(query), cacheKey => this.searchService.clearCache(cacheKey), this.cacheState);
this.cacheState.load();
}
private cacheQuery(cacheKey: string): IFileQuery {
const options: IFileQueryBuilderOptions = {
_reason: 'openFileHandler',
extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService),
filePattern: '',
cacheKey: cacheKey,
maxResults: 0,
sortByScore: true,
};
const folderResources = this.contextService.getWorkspace().folders.map(folder => folder.uri);
const query = this.queryBuilder.file(folderResources, options);
return query;
}
get isCacheLoaded(): boolean {
return this.cacheState && this.cacheState.isLoaded;
}
getGroupLabel(): string {
return nls.localize('searchResults', "search results");
}
getAutoFocus(searchValue: string): IAutoFocus {
return {
autoFocusFirstEntry: true
};
}
}
enum LoadingPhase {
Created = 1,
Loading,
Loaded,
Errored,
Disposed
}
/**
* Exported for testing.
*/
export class CacheState {
private _cacheKey = defaultGenerator.nextId();
private query: IFileQuery;
private loadingPhase = LoadingPhase.Created;
private promise: Promise<void>;
constructor(cacheQuery: (cacheKey: string) => IFileQuery, private doLoad: (query: IFileQuery) => Promise<any>, private doDispose: (cacheKey: string) => Promise<void>, private previous: CacheState | null) {
this.query = cacheQuery(this._cacheKey);
if (this.previous) {
const current = objects.assign({}, this.query, { cacheKey: null });
const previous = objects.assign({}, this.previous.query, { cacheKey: null });
if (!objects.equals(current, previous)) {
this.previous.dispose();
this.previous = null;
}
}
}
get cacheKey(): string {
return this.loadingPhase === LoadingPhase.Loaded || !this.previous ? this._cacheKey : this.previous.cacheKey;
}
get isLoaded(): boolean {
const isLoaded = this.loadingPhase === LoadingPhase.Loaded;
return isLoaded || !this.previous ? isLoaded : this.previous.isLoaded;
}
get isUpdating(): boolean {
const isUpdating = this.loadingPhase === LoadingPhase.Loading;
return isUpdating || !this.previous ? isUpdating : this.previous.isUpdating;
}
load(): void {
if (this.isUpdating) {
return;
}
this.loadingPhase = LoadingPhase.Loading;
this.promise = this.doLoad(this.query)
.then(() => {
this.loadingPhase = LoadingPhase.Loaded;
if (this.previous) {
this.previous.dispose();
this.previous = null;
}
}, err => {
this.loadingPhase = LoadingPhase.Errored;
errors.onUnexpectedError(err);
});
}
dispose(): void {
if (this.promise) {
this.promise.then(undefined, () => { })
.then(() => {
this.loadingPhase = LoadingPhase.Disposed;
return this.doDispose(this._cacheKey);
}).then(undefined, err => {
errors.onUnexpectedError(err);
});
} else {
this.loadingPhase = LoadingPhase.Disposed;
}
if (this.previous) {
this.previous.dispose();
this.previous = null;
}
}
}

View File

@@ -0,0 +1,231 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ThrottledDelayer } from 'vs/base/common/async';
import { QuickOpenHandler, EditorQuickOpenEntry } from 'vs/workbench/browser/quickopen';
import { QuickOpenModel, QuickOpenEntry, compareEntries } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen';
import * as filters from 'vs/base/common/filters';
import * as strings from 'vs/base/common/strings';
import { Range } from 'vs/editor/common/core/range';
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { symbolKindToCssClass } from 'vs/editor/common/modes';
import { IResourceInput } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceSymbolProvider, getWorkspaceSymbols, IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search';
import { basename } from 'vs/base/common/resources';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ILabelService } from 'vs/platform/label/common/label';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Schemas } from 'vs/base/common/network';
import { IOpenerService } from 'vs/platform/opener/common/opener';
class SymbolEntry extends EditorQuickOpenEntry {
private bearingResolve: Promise<this | undefined>;
constructor(
private bearing: IWorkspaceSymbol,
private provider: IWorkspaceSymbolProvider,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorService editorService: IEditorService,
@ILabelService private readonly labelService: ILabelService,
@IOpenerService private readonly openerService: IOpenerService
) {
super(editorService);
}
getLabel(): string {
return this.bearing.name;
}
getAriaLabel(): string {
return nls.localize('entryAriaLabel', "{0}, symbols picker", this.getLabel());
}
getDescription(): string | null {
const containerName = this.bearing.containerName;
if (this.bearing.location.uri) {
if (containerName) {
return `${containerName}${basename(this.bearing.location.uri)}`;
}
return this.labelService.getUriLabel(this.bearing.location.uri, { relative: true });
}
return containerName || null;
}
getIcon(): string {
return symbolKindToCssClass(this.bearing.kind);
}
getResource(): URI {
return this.bearing.location.uri;
}
run(mode: Mode, context: IEntryRunContext): boolean {
// resolve this type bearing if neccessary
if (!this.bearingResolve && typeof this.provider.resolveWorkspaceSymbol === 'function' && !this.bearing.location.range) {
this.bearingResolve = Promise.resolve(this.provider.resolveWorkspaceSymbol(this.bearing, CancellationToken.None)).then(result => {
this.bearing = result || this.bearing;
return this;
}, onUnexpectedError);
}
// open after resolving
Promise.resolve(this.bearingResolve).then(() => {
const scheme = this.bearing.location.uri ? this.bearing.location.uri.scheme : undefined;
if (scheme === Schemas.http || scheme === Schemas.https) {
if (mode === Mode.OPEN || mode === Mode.OPEN_IN_BACKGROUND) {
this.openerService.open(this.bearing.location.uri); // support http/https resources (https://github.com/Microsoft/vscode/issues/58924))
}
} else {
super.run(mode, context);
}
});
// hide if OPEN
return mode === Mode.OPEN;
}
getInput(): IResourceInput {
const input: IResourceInput = {
resource: this.bearing.location.uri,
options: {
pinned: !this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench.editor.enablePreviewFromQuickOpen
}
};
if (this.bearing.location.range) {
input.options!.selection = Range.collapseToStart(this.bearing.location.range);
}
return input;
}
static compare(elementA: SymbolEntry, elementB: SymbolEntry, searchValue: string): number {
// Sort by Type if name is identical
const elementAName = elementA.getLabel().toLowerCase();
const elementBName = elementB.getLabel().toLowerCase();
if (elementAName === elementBName) {
let elementAType = symbolKindToCssClass(elementA.bearing.kind);
let elementBType = symbolKindToCssClass(elementB.bearing.kind);
return elementAType.localeCompare(elementBType);
}
return compareEntries(elementA, elementB, searchValue);
}
}
export interface IOpenSymbolOptions {
skipSorting: boolean;
skipLocalSymbols: boolean;
skipDelay: boolean;
}
export class OpenSymbolHandler extends QuickOpenHandler {
static readonly ID = 'workbench.picker.symbols';
private static readonly TYPING_SEARCH_DELAY = 200; // This delay accommodates for the user typing a word and then stops typing to start searching
private delayer: ThrottledDelayer<QuickOpenEntry[]>;
private options: IOpenSymbolOptions;
constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) {
super();
this.delayer = new ThrottledDelayer<QuickOpenEntry[]>(OpenSymbolHandler.TYPING_SEARCH_DELAY);
this.options = Object.create(null);
}
setOptions(options: IOpenSymbolOptions) {
this.options = options;
}
canRun(): boolean | string {
return true;
}
getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
searchValue = searchValue.trim();
let promise: Promise<QuickOpenEntry[]>;
if (!this.options.skipDelay) {
promise = this.delayer.trigger(() => {
if (token.isCancellationRequested) {
return Promise.resolve([]);
}
return this.doGetResults(searchValue, token);
});
} else {
promise = this.doGetResults(searchValue, token);
}
return promise.then(e => new QuickOpenModel(e));
}
private doGetResults(searchValue: string, token: CancellationToken): Promise<SymbolEntry[]> {
return getWorkspaceSymbols(searchValue, token).then(tuples => {
if (token.isCancellationRequested) {
return [];
}
const result: SymbolEntry[] = [];
for (let tuple of tuples) {
const [provider, bearings] = tuple;
this.fillInSymbolEntries(result, provider, bearings, searchValue);
}
// Sort (Standalone only)
if (!this.options.skipSorting) {
searchValue = searchValue ? strings.stripWildcards(searchValue.toLowerCase()) : searchValue;
return result.sort((a, b) => SymbolEntry.compare(a, b, searchValue));
}
return result;
});
}
private fillInSymbolEntries(bucket: SymbolEntry[], provider: IWorkspaceSymbolProvider, types: IWorkspaceSymbol[], searchValue: string): void {
// Convert to Entries
for (let element of types) {
if (this.options.skipLocalSymbols && !!element.containerName) {
continue; // ignore local symbols if we are told so
}
const entry = this.instantiationService.createInstance(SymbolEntry, element, provider);
entry.setHighlights(filters.matchesFuzzy2(searchValue, entry.getLabel()) || []);
bucket.push(entry);
}
}
getGroupLabel(): string {
return nls.localize('symbols', "symbol results");
}
getEmptyLabel(searchString: string): string {
if (searchString.length > 0) {
return nls.localize('noSymbolsMatching', "No symbols matching");
}
return nls.localize('noSymbolsWithoutInput', "Type to search for symbols");
}
getAutoFocus(searchValue: string): IAutoFocus {
return {
autoFocusFirstEntry: true,
autoFocusPrefixMatch: searchValue.trim()
};
}
}

View File

@@ -0,0 +1,212 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { Widget } from 'vs/base/browser/ui/widget';
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { IInputValidator, HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler';
import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
export interface IOptions {
placeholder?: string;
width?: number;
validation?: IInputValidator;
ariaLabel?: string;
history?: string[];
}
export class PatternInputWidget extends Widget {
static OPTION_CHANGE: string = 'optionChange';
inputFocusTracker: dom.IFocusTracker;
private width: number;
private placeholder: string;
private ariaLabel: string;
private domNode: HTMLElement;
protected inputBox: HistoryInputBox;
private _onSubmit = this._register(new Emitter<boolean>());
onSubmit: CommonEvent<boolean> = this._onSubmit.event;
private _onCancel = this._register(new Emitter<boolean>());
onCancel: CommonEvent<boolean> = this._onCancel.event;
constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null),
@IThemeService protected themeService: IThemeService,
@IContextKeyService private readonly contextKeyService: IContextKeyService
) {
super();
this.width = options.width || 100;
this.placeholder = options.placeholder || '';
this.ariaLabel = options.ariaLabel || nls.localize('defaultLabel', "input");
this.render(options);
parent.appendChild(this.domNode);
}
dispose(): void {
super.dispose();
if (this.inputFocusTracker) {
this.inputFocusTracker.dispose();
}
}
setWidth(newWidth: number): void {
this.width = newWidth;
this.domNode.style.width = this.width + 'px';
this.contextViewProvider.layout();
this.setInputWidth();
}
getValue(): string {
return this.inputBox.value;
}
setValue(value: string): void {
if (this.inputBox.value !== value) {
this.inputBox.value = value;
}
}
select(): void {
this.inputBox.select();
}
focus(): void {
this.inputBox.focus();
}
inputHasFocus(): boolean {
return this.inputBox.hasFocus();
}
private setInputWidth(): void {
this.inputBox.width = this.width - this.getSubcontrolsWidth() - 2; // 2 for input box border
}
protected getSubcontrolsWidth(): number {
return 0;
}
getHistory(): string[] {
return this.inputBox.getHistory();
}
clearHistory(): void {
this.inputBox.clearHistory();
}
onSearchSubmit(): void {
this.inputBox.addToHistory();
}
showNextTerm() {
this.inputBox.showNextValue();
}
showPreviousTerm() {
this.inputBox.showPreviousValue();
}
private render(options: IOptions): void {
this.domNode = document.createElement('div');
this.domNode.style.width = this.width + 'px';
dom.addClass(this.domNode, 'monaco-findInput');
this.inputBox = new ContextScopedHistoryInputBox(this.domNode, this.contextViewProvider, {
placeholder: this.placeholder || '',
ariaLabel: this.ariaLabel || '',
validationOptions: {
validation: undefined
},
history: options.history || []
}, this.contextKeyService);
this._register(attachInputBoxStyler(this.inputBox, this.themeService));
this.inputFocusTracker = dom.trackFocus(this.inputBox.inputElement);
this.onkeyup(this.inputBox.inputElement, (keyboardEvent) => this.onInputKeyUp(keyboardEvent));
const controls = document.createElement('div');
controls.className = 'controls';
this.renderSubcontrols(controls);
this.domNode.appendChild(controls);
this.setInputWidth();
}
protected renderSubcontrols(controlsDiv: HTMLDivElement): void {
}
private onInputKeyUp(keyboardEvent: IKeyboardEvent) {
switch (keyboardEvent.keyCode) {
case KeyCode.Enter:
this._onSubmit.fire(false);
return;
case KeyCode.Escape:
this._onCancel.fire(false);
return;
default:
return;
}
}
}
export class ExcludePatternInputWidget extends PatternInputWidget {
constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null),
@IThemeService themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super(parent, contextViewProvider, options, themeService, contextKeyService);
}
private useExcludesAndIgnoreFilesBox: Checkbox;
dispose(): void {
super.dispose();
this.useExcludesAndIgnoreFilesBox.dispose();
}
useExcludesAndIgnoreFiles(): boolean {
return this.useExcludesAndIgnoreFilesBox.checked;
}
setUseExcludesAndIgnoreFiles(value: boolean) {
this.useExcludesAndIgnoreFilesBox.checked = value;
}
protected getSubcontrolsWidth(): number {
return super.getSubcontrolsWidth() + this.useExcludesAndIgnoreFilesBox.width();
}
protected renderSubcontrols(controlsDiv: HTMLDivElement): void {
this.useExcludesAndIgnoreFilesBox = this._register(new Checkbox({
actionClassName: 'useExcludesAndIgnoreFiles',
title: nls.localize('useExcludesAndIgnoreFilesDescription', "Use Exclude Settings and Ignore Files"),
isChecked: true,
}));
this._register(this.useExcludesAndIgnoreFilesBox.onChange(viaKeyboard => {
if (!viaKeyboard) {
this.inputBox.focus();
}
}));
this._register(attachCheckboxStyler(this.useExcludesAndIgnoreFilesBox, this.themeService));
controlsDiv.appendChild(this.useExcludesAndIgnoreFilesBox.domNode);
super.renderSubcontrols(controlsDiv);
}
}

View File

@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IReplaceService } from 'vs/workbench/contrib/search/common/replace';
import { ReplaceService, ReplacePreviewContentProvider } from 'vs/workbench/contrib/search/browser/replaceService';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
export function registerContributions(): void {
registerSingleton(IReplaceService, ReplaceService, true);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ReplacePreviewContentProvider, LifecyclePhase.Starting);
}

View File

@@ -0,0 +1,213 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as errors from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import * as network from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { IReplaceService } from 'vs/workbench/contrib/search/common/replace';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel';
import { IProgressRunner } from 'vs/platform/progress/common/progress';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { ITextModel, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ResourceTextEdit } from 'vs/editor/common/modes';
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { Range } from 'vs/editor/common/core/range';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { mergeSort } from 'vs/base/common/arrays';
const REPLACE_PREVIEW = 'replacePreview';
const toReplaceResource = (fileResource: URI): URI => {
return fileResource.with({ scheme: network.Schemas.internal, fragment: REPLACE_PREVIEW, query: JSON.stringify({ scheme: fileResource.scheme }) });
};
const toFileResource = (replaceResource: URI): URI => {
return replaceResource.with({ scheme: JSON.parse(replaceResource.query)['scheme'], fragment: '', query: '' });
};
export class ReplacePreviewContentProvider implements ITextModelContentProvider, IWorkbenchContribution {
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ITextModelService private readonly textModelResolverService: ITextModelService
) {
this.textModelResolverService.registerTextModelContentProvider(network.Schemas.internal, this);
}
provideTextContent(uri: URI): Promise<ITextModel> | null {
if (uri.fragment === REPLACE_PREVIEW) {
return this.instantiationService.createInstance(ReplacePreviewModel).resolve(uri);
}
return null;
}
}
class ReplacePreviewModel extends Disposable {
constructor(
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService,
@ITextModelService private readonly textModelResolverService: ITextModelService,
@IReplaceService private readonly replaceService: IReplaceService,
@ISearchWorkbenchService private readonly searchWorkbenchService: ISearchWorkbenchService
) {
super();
}
resolve(replacePreviewUri: URI): Promise<ITextModel> {
const fileResource = toFileResource(replacePreviewUri);
const fileMatch = <FileMatch>this.searchWorkbenchService.searchModel.searchResult.matches().filter(match => match.resource().toString() === fileResource.toString())[0];
return this.textModelResolverService.createModelReference(fileResource).then(ref => {
ref = this._register(ref);
const sourceModel = ref.object.textEditorModel;
const sourceModelModeId = sourceModel.getLanguageIdentifier().language;
const replacePreviewModel = this.modelService.createModel(createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), this.modeService.create(sourceModelModeId), replacePreviewUri);
this._register(fileMatch.onChange(modelChange => this.update(sourceModel, replacePreviewModel, fileMatch, modelChange)));
this._register(this.searchWorkbenchService.searchModel.onReplaceTermChanged(() => this.update(sourceModel, replacePreviewModel, fileMatch)));
this._register(fileMatch.onDispose(() => replacePreviewModel.dispose())); // TODO@Sandeep we should not dispose a model directly but rather the reference (depends on https://github.com/Microsoft/vscode/issues/17073)
this._register(replacePreviewModel.onWillDispose(() => this.dispose()));
this._register(sourceModel.onWillDispose(() => this.dispose()));
return replacePreviewModel;
});
}
private update(sourceModel: ITextModel, replacePreviewModel: ITextModel, fileMatch: FileMatch, override: boolean = false): void {
if (!sourceModel.isDisposed() && !replacePreviewModel.isDisposed()) {
this.replaceService.updateReplacePreview(fileMatch, override);
}
}
}
export class ReplaceService implements IReplaceService {
_serviceBrand: any;
constructor(
@ITextFileService private readonly textFileService: ITextFileService,
@IEditorService private readonly editorService: IEditorService,
@ITextModelService private readonly textModelResolverService: ITextModelService,
@IBulkEditService private readonly bulkEditorService: IBulkEditService
) { }
replace(match: Match): Promise<any>;
replace(files: FileMatch[], progress?: IProgressRunner): Promise<any>;
replace(match: FileMatchOrMatch, progress?: IProgressRunner, resource?: URI): Promise<any>;
replace(arg: any, progress: IProgressRunner | undefined = undefined, resource: URI | null = null): Promise<any> {
const edits: ResourceTextEdit[] = this.createEdits(arg, resource);
return this.bulkEditorService.apply({ edits }, { progress }).then(() => this.textFileService.saveAll(edits.map(e => e.resource)));
}
openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {
const fileMatch = element instanceof Match ? element.parent() : element;
return this.editorService.openEditor({
leftResource: fileMatch.resource(),
rightResource: toReplaceResource(fileMatch.resource()),
label: nls.localize('fileReplaceChanges', "{0} ↔ {1} (Replace Preview)", fileMatch.name(), fileMatch.name()),
options: {
preserveFocus,
pinned,
revealIfVisible: true
}
}).then(editor => {
const disposable = fileMatch.onDispose(() => {
if (editor && editor.input) {
editor.input.dispose();
}
disposable.dispose();
});
this.updateReplacePreview(fileMatch).then(() => {
if (editor) {
const editorControl = editor.getControl();
if (element instanceof Match) {
editorControl.revealLineInCenter(element.range().startLineNumber, ScrollType.Immediate);
}
}
});
}, errors.onUnexpectedError);
}
updateReplacePreview(fileMatch: FileMatch, override: boolean = false): Promise<void> {
const replacePreviewUri = toReplaceResource(fileMatch.resource());
return Promise.all([this.textModelResolverService.createModelReference(fileMatch.resource()), this.textModelResolverService.createModelReference(replacePreviewUri)])
.then(([sourceModelRef, replaceModelRef]) => {
const sourceModel = sourceModelRef.object.textEditorModel;
const replaceModel = replaceModelRef.object.textEditorModel;
const returnValue = Promise.resolve(null);
// If model is disposed do not update
if (sourceModel && replaceModel) {
if (override) {
replaceModel.setValue(sourceModel.getValue());
} else {
replaceModel.undo();
}
this.applyEditsToPreview(fileMatch, replaceModel);
}
return returnValue.then(() => {
sourceModelRef.dispose();
replaceModelRef.dispose();
});
});
}
private applyEditsToPreview(fileMatch: FileMatch, replaceModel: ITextModel): void {
const resourceEdits = this.createEdits(fileMatch, replaceModel.uri);
const modelEdits: IIdentifiedSingleEditOperation[] = [];
for (const resourceEdit of resourceEdits) {
for (const edit of resourceEdit.edits) {
const range = Range.lift(edit.range);
modelEdits.push(EditOperation.replaceMove(range, edit.text));
}
}
replaceModel.pushEditOperations([], mergeSort(modelEdits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)), () => []);
}
private createEdits(arg: FileMatchOrMatch | FileMatch[], resource: URI | null = null): ResourceTextEdit[] {
const edits: ResourceTextEdit[] = [];
if (arg instanceof Match) {
const match = <Match>arg;
edits.push(this.createEdit(match, match.replaceString, resource));
}
if (arg instanceof FileMatch) {
arg = [arg];
}
if (arg instanceof Array) {
arg.forEach(element => {
const fileMatch = <FileMatch>element;
if (fileMatch.count() > 0) {
edits.push(...fileMatch.matches().map(match => this.createEdit(match, match.replaceString, resource)));
}
});
}
return edits;
}
private createEdit(match: Match, text: string, resource: URI | null = null): ResourceTextEdit {
const fileMatch: FileMatch = match.parent();
const resourceEdit: ResourceTextEdit = {
resource: resource !== null ? resource : fileMatch.resource(),
edits: [{
range: match.range(),
text: text
}]
};
return resourceEdit;
}
}

View File

@@ -0,0 +1,774 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/search.contribution';
import { Action } from 'vs/base/common/actions';
import { distinct } from 'vs/base/common/arrays';
import { illegalArgument } from 'vs/base/common/errors';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Schemas } from 'vs/base/common/network';
import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import { dirname } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { getSelectionSearchString } from 'vs/editor/contrib/find/findController';
import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel';
import * as nls from 'vs/nls';
import { ICommandAction, MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IFileService } from 'vs/platform/files/common/files';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IListService, WorkbenchListFocusContextKey, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { Registry } from 'vs/platform/registry/common/platform';
import { ISearchConfigurationProperties, ISearchConfiguration, VIEWLET_ID, PANEL_ID, VIEW_ID, VIEW_CONTAINER } from 'vs/workbench/services/search/common/search';
import { defaultQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen';
import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files';
import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition } from 'vs/workbench/contrib/files/common/files';
import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler';
import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler';
import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions';
import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FindInFilesAction, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand } from 'vs/workbench/contrib/search/browser/searchActions';
import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget';
import * as Constants from 'vs/workbench/contrib/search/common/constants';
import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search';
import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel';
import { ViewletDescriptor, ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet';
import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService';
import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet';
import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel';
import { IViewsRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views';
import { SearchView } from 'vs/workbench/contrib/search/browser/searchView';
registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true);
registerSingleton(ISearchHistoryService, SearchHistoryService, true);
replaceContributions();
searchWidgetContributions();
const category = nls.localize('search', "Search");
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.search.toggleQueryDetails',
weight: KeybindingWeight.WorkbenchContrib,
when: Constants.SearchViewVisibleKey,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_J,
handler: accessor => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
searchView.toggleQueryDetails();
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.FocusSearchFromResults,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FirstMatchFocusKey),
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
handler: (accessor, args: any) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
searchView.focusPreviousInputBox();
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.OpenMatchToSide,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey),
primary: KeyMod.CtrlCmd | KeyCode.Enter,
mac: {
primary: KeyMod.WinCtrl | KeyCode.Enter
},
handler: (accessor, args: any) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
const tree: WorkbenchObjectTree<RenderableMatch> = searchView.getControl();
searchView.open(<FileMatchOrMatch>tree.getFocus()[0], false, true, true);
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.CancelActionId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, WorkbenchListFocusContextKey),
primary: KeyCode.Escape,
handler: (accessor, args: any) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
searchView.cancelSearch();
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.RemoveActionId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey),
primary: KeyCode.Delete,
mac: {
primary: KeyMod.CtrlCmd | KeyCode.Backspace,
},
handler: (accessor, args: any) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
const tree: WorkbenchObjectTree<RenderableMatch> = searchView.getControl();
accessor.get(IInstantiationService).createInstance(RemoveAction, tree, tree.getFocus()[0]).run();
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.ReplaceActionId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.MatchFocusKey),
primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.KEY_1,
handler: (accessor, args: any) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
const tree: WorkbenchObjectTree<RenderableMatch> = searchView.getControl();
accessor.get(IInstantiationService).createInstance(ReplaceAction, tree, tree.getFocus()[0], searchView).run();
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.ReplaceAllInFileActionId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.FileFocusKey),
primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.KEY_1,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter],
handler: (accessor, args: any) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
const tree: WorkbenchObjectTree<RenderableMatch> = searchView.getControl();
accessor.get(IInstantiationService).createInstance(ReplaceAllAction, searchView, tree.getFocus()[0]).run();
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.ReplaceAllInFolderActionId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.FolderFocusKey),
primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.KEY_1,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter],
handler: (accessor, args: any) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
const tree: WorkbenchObjectTree<RenderableMatch> = searchView.getControl();
accessor.get(IInstantiationService).createInstance(ReplaceAllInFolderAction, tree, tree.getFocus()[0]).run();
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.CloseReplaceWidgetActionId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceInputBoxFocusedKey),
primary: KeyCode.Escape,
handler: (accessor, args: any) => {
accessor.get(IInstantiationService).createInstance(CloseReplaceAction, Constants.CloseReplaceWidgetActionId, '').run();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: FocusNextInputAction.ID,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey),
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
handler: (accessor, args: any) => {
accessor.get(IInstantiationService).createInstance(FocusNextInputAction, FocusNextInputAction.ID, '').run();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: FocusPreviousInputAction.ID,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey, Constants.SearchInputBoxFocusedKey.toNegated()),
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
handler: (accessor, args: any) => {
accessor.get(IInstantiationService).createInstance(FocusPreviousInputAction, FocusPreviousInputAction.ID, '').run();
}
});
MenuRegistry.appendMenuItem(MenuId.SearchContext, {
command: {
id: Constants.ReplaceActionId,
title: ReplaceAction.LABEL
},
when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.MatchFocusKey),
group: 'search',
order: 1
});
MenuRegistry.appendMenuItem(MenuId.SearchContext, {
command: {
id: Constants.ReplaceAllInFolderActionId,
title: ReplaceAllInFolderAction.LABEL
},
when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FolderFocusKey),
group: 'search',
order: 1
});
MenuRegistry.appendMenuItem(MenuId.SearchContext, {
command: {
id: Constants.ReplaceAllInFileActionId,
title: ReplaceAllAction.LABEL
},
when: ContextKeyExpr.and(Constants.ReplaceActiveKey, Constants.FileFocusKey),
group: 'search',
order: 1
});
MenuRegistry.appendMenuItem(MenuId.SearchContext, {
command: {
id: Constants.RemoveActionId,
title: RemoveAction.LABEL
},
when: Constants.FileMatchOrMatchFocusKey,
group: 'search',
order: 2
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.CopyMatchCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: Constants.FileMatchOrMatchFocusKey,
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
handler: copyMatchCommand
});
MenuRegistry.appendMenuItem(MenuId.SearchContext, {
command: {
id: Constants.CopyMatchCommandId,
title: nls.localize('copyMatchLabel', "Copy")
},
when: Constants.FileMatchOrMatchFocusKey,
group: 'search_2',
order: 1
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: Constants.CopyPathCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: Constants.FileMatchOrFolderMatchFocusKey,
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C,
win: {
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_C
},
handler: copyPathCommand
});
MenuRegistry.appendMenuItem(MenuId.SearchContext, {
command: {
id: Constants.CopyPathCommandId,
title: nls.localize('copyPathLabel', "Copy Path")
},
when: Constants.FileMatchOrFolderMatchFocusKey,
group: 'search_2',
order: 2
});
MenuRegistry.appendMenuItem(MenuId.SearchContext, {
command: {
id: Constants.CopyAllCommandId,
title: nls.localize('copyAllLabel', "Copy All")
},
when: Constants.HasSearchResults,
group: 'search_2',
order: 3
});
CommandsRegistry.registerCommand({
id: Constants.CopyAllCommandId,
handler: copyAllCommand
});
CommandsRegistry.registerCommand({
id: Constants.ClearSearchHistoryCommandId,
handler: clearHistoryCommand
});
const clearSearchHistoryLabel = nls.localize('clearSearchHistoryLabel', "Clear Search History");
const ClearSearchHistoryCommand: ICommandAction = {
id: Constants.ClearSearchHistoryCommandId,
title: clearSearchHistoryLabel,
category
};
MenuRegistry.addCommand(ClearSearchHistoryCommand);
CommandsRegistry.registerCommand({
id: Constants.ToggleSearchViewPositionCommandId,
handler: (accessor) => {
const configurationService = accessor.get(IConfigurationService);
const currentValue = configurationService.getValue<ISearchConfigurationProperties>('search').location;
const toggleValue = currentValue === 'sidebar' ? 'panel' : 'sidebar';
configurationService.updateValue('search.location', toggleValue);
}
});
const toggleSearchViewPositionLabel = nls.localize('toggleSearchViewPositionLabel', "Toggle Search View Position");
const ToggleSearchViewPositionCommand: ICommandAction = {
id: Constants.ToggleSearchViewPositionCommandId,
title: toggleSearchViewPositionLabel,
category
};
MenuRegistry.addCommand(ToggleSearchViewPositionCommand);
MenuRegistry.appendMenuItem(MenuId.SearchContext, {
command: ToggleSearchViewPositionCommand,
when: Constants.SearchViewVisibleKey,
group: 'search_9',
order: 1
});
CommandsRegistry.registerCommand({
id: Constants.FocusSearchListCommandID,
handler: focusSearchListCommand
});
const focusSearchListCommandLabel = nls.localize('focusSearchListCommandLabel', "Focus List");
const FocusSearchListCommand: ICommandAction = {
id: Constants.FocusSearchListCommandID,
title: focusSearchListCommandLabel,
category
};
MenuRegistry.addCommand(FocusSearchListCommand);
const searchInFolderCommand: ICommandHandler = (accessor, resource?: URI) => {
const listService = accessor.get(IListService);
const viewletService = accessor.get(IViewletService);
const panelService = accessor.get(IPanelService);
const fileService = accessor.get(IFileService);
const configurationService = accessor.get(IConfigurationService);
const resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService));
return openSearchView(viewletService, panelService, configurationService, true).then(searchView => {
if (resources && resources.length) {
return fileService.resolveFiles(resources.map(resource => ({ resource }))).then(results => {
const folders: URI[] = [];
results.forEach(result => {
if (result.success) {
folders.push(result.stat.isDirectory ? result.stat.resource : dirname(result.stat.resource));
}
});
searchView.searchInFolders(distinct(folders, folder => folder.toString()));
});
}
return undefined;
});
};
const FIND_IN_FOLDER_ID = 'filesExplorer.findInFolder';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: FIND_IN_FOLDER_ID,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerFolderContext, ResourceContextKey.Scheme.isEqualTo(Schemas.file)), // todo@remote
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F,
handler: searchInFolderCommand
});
CommandsRegistry.registerCommand({
id: ClearSearchResultsAction.ID,
handler: (accessor, args: any) => {
accessor.get(IInstantiationService).createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, '').run();
}
});
CommandsRegistry.registerCommand({
id: RefreshAction.ID,
handler: (accessor, args: any) => {
accessor.get(IInstantiationService).createInstance(RefreshAction, RefreshAction.ID, '').run();
}
});
const FIND_IN_WORKSPACE_ID = 'filesExplorer.findInWorkspace';
CommandsRegistry.registerCommand({
id: FIND_IN_WORKSPACE_ID,
handler: (accessor) => {
return openSearchView(accessor.get(IViewletService), accessor.get(IPanelService), accessor.get(IConfigurationService), true).then(searchView => {
searchView.searchInFolders(null);
});
}
});
MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
group: '4_search',
order: 10,
command: {
id: FIND_IN_FOLDER_ID,
title: nls.localize('findInFolder', "Find in Folder...")
},
when: ContextKeyExpr.and(ExplorerFolderContext, ResourceContextKey.Scheme.isEqualTo(Schemas.file)) // todo@remote
});
MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
group: '4_search',
order: 10,
command: {
id: FIND_IN_WORKSPACE_ID,
title: nls.localize('findInWorkspace', "Find in Workspace...")
},
when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext.toNegated())
});
class ShowAllSymbolsAction extends Action {
static readonly ID = 'workbench.action.showAllSymbols';
static readonly LABEL = nls.localize('showTriggerActions', "Go to Symbol in Workspace...");
static readonly ALL_SYMBOLS_PREFIX = '#';
constructor(
actionId: string, actionLabel: string,
@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
@ICodeEditorService private readonly editorService: ICodeEditorService) {
super(actionId, actionLabel);
this.enabled = !!this.quickOpenService;
}
run(context?: any): Promise<void> {
let prefix = ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX;
let inputSelection: { start: number; end: number; } = undefined;
const editor = this.editorService.getFocusedCodeEditor();
const word = editor && getSelectionSearchString(editor);
if (word) {
prefix = prefix + word;
inputSelection = { start: 1, end: word.length + 1 };
}
this.quickOpenService.show(prefix, { inputSelection });
return Promise.resolve(undefined);
}
}
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor(
SearchViewlet,
VIEWLET_ID,
nls.localize('name', "Search"),
'search',
1
));
class RegisterSearchViewContribution implements IWorkbenchContribution {
constructor(
@IViewletService viewletService: IViewletService,
@IPanelService panelService: IPanelService,
@IConfigurationService configurationService: IConfigurationService
) {
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
const updateSearchViewLocation = (open: boolean) => {
const config = configurationService.getValue<ISearchConfiguration>();
if (config.search.location === 'panel') {
viewsRegistry.deregisterViews(viewsRegistry.getViews(VIEW_CONTAINER), VIEW_CONTAINER);
Registry.as<PanelRegistry>(PanelExtensions.Panels).registerPanel(new PanelDescriptor(
SearchPanel,
PANEL_ID,
nls.localize('name', "Search"),
'search',
10
));
if (open) {
panelService.openPanel(PANEL_ID);
}
} else {
Registry.as<PanelRegistry>(PanelExtensions.Panels).deregisterPanel(PANEL_ID);
viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: { ctor: SearchView }, canToggleVisibility: false }], VIEW_CONTAINER);
if (open) {
viewletService.openViewlet(VIEWLET_ID);
}
}
};
configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('search.location')) {
updateSearchViewLocation(true);
}
});
updateSearchViewLocation(false);
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterSearchViewContribution, LifecyclePhase.Starting);
// Actions
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
// Show Search and Find in Files are redundant, but we can't break keybindings by removing one. So it's the same action, same keybinding, registered to different IDs.
// Show Search 'when' is redundant but if the two conflict with exactly the same keybinding and 'when' clause, then they can show up as "unbound" - #51780
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSearchViewletAction, VIEWLET_ID, OpenSearchViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchViewVisibleKey.toNegated()), 'View: Show Search', nls.localize('view', "View"));
registry.registerWorkbenchAction(new SyncActionDescriptor(FindInFilesAction, Constants.FindInFilesActionId, nls.localize('findInFiles', "Find in Files"), { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }), 'Find in Files', category);
MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, {
group: '4_find_global',
command: {
id: Constants.FindInFilesActionId,
title: nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files")
},
order: 1
});
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category);
MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, {
group: '4_find_global',
command: {
id: ReplaceInFilesAction.ID,
title: nls.localize({ key: 'miReplaceInFiles', comment: ['&& denotes a mnemonic'] }, "Replace &&in Files")
},
order: 2
});
KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
id: Constants.ToggleCaseSensitiveCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchViewFocusedKey, Constants.FileMatchOrFolderMatchFocusKey.toNegated()),
handler: toggleCaseSensitiveCommand
}, ToggleCaseSensitiveKeybinding));
KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
id: Constants.ToggleWholeWordCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchViewFocusedKey),
handler: toggleWholeWordCommand
}, ToggleWholeWordKeybinding));
KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
id: Constants.ToggleRegexCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchViewFocusedKey),
handler: toggleRegexCommand
}, ToggleRegexKeybinding));
registry.registerWorkbenchAction(new SyncActionDescriptor(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL), 'Search: Collapse All', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllSymbolsAction, ShowAllSymbolsAction.ID, ShowAllSymbolsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...');
registry.registerWorkbenchAction(new SyncActionDescriptor(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear', category);
// Register Quick Open Handler
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerDefaultQuickOpenHandler(
new QuickOpenHandlerDescriptor(
OpenAnythingHandler,
OpenAnythingHandler.ID,
'',
defaultQuickOpenContextKey,
nls.localize('openAnythingHandlerDescription', "Go to File")
)
);
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
OpenSymbolHandler,
OpenSymbolHandler.ID,
ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX,
'inWorkspaceSymbolsPicker',
[
{
prefix: ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX,
needsEditor: false,
description: nls.localize('openSymbolDescriptionNormal', "Go to Symbol in Workspace")
}
]
)
);
// Configuration
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
id: 'search',
order: 13,
title: nls.localize('searchConfigurationTitle', "Search"),
type: 'object',
properties: {
'search.exclude': {
type: 'object',
markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."),
default: { '**/node_modules': true, '**/bower_components': true },
additionalProperties: {
anyOf: [
{
type: 'boolean',
description: nls.localize('exclude.boolean', "The glob pattern to match file paths against. Set to true or false to enable or disable the pattern."),
},
{
type: 'object',
properties: {
when: {
type: 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } })
pattern: '\\w*\\$\\(basename\\)\\w*',
default: '$(basename).ext',
description: nls.localize('exclude.when', 'Additional check on the siblings of a matching file. Use $(basename) as variable for the matching file name.')
}
}
}
]
},
scope: ConfigurationScope.RESOURCE
},
'search.useRipgrep': {
type: 'boolean',
description: nls.localize('useRipgrep', "This setting is deprecated and now falls back on \"search.usePCRE2\"."),
deprecationMessage: nls.localize('useRipgrepDeprecated', "Deprecated. Consider \"search.usePCRE2\" for advanced regex feature support."),
default: true
},
'search.maintainFileSearchCache': {
type: 'boolean',
description: nls.localize('search.maintainFileSearchCache', "When enabled, the searchService process will be kept alive instead of being shut down after an hour of inactivity. This will keep the file search cache in memory."),
default: false
},
'search.useIgnoreFiles': {
type: 'boolean',
markdownDescription: nls.localize('useIgnoreFiles', "Controls whether to use `.gitignore` and `.ignore` files when searching for files."),
default: true,
scope: ConfigurationScope.RESOURCE
},
'search.useGlobalIgnoreFiles': {
type: 'boolean',
markdownDescription: nls.localize('useGlobalIgnoreFiles', "Controls whether to use global `.gitignore` and `.ignore` files when searching for files."),
default: false,
scope: ConfigurationScope.RESOURCE
},
'search.quickOpen.includeSymbols': {
type: 'boolean',
description: nls.localize('search.quickOpen.includeSymbols', "Whether to include results from a global symbol search in the file results for Quick Open."),
default: false
},
'search.quickOpen.includeHistory': {
type: 'boolean',
description: nls.localize('search.quickOpen.includeHistory', "Whether to include results from recently opened files in the file results for Quick Open."),
default: true
},
'search.followSymlinks': {
type: 'boolean',
description: nls.localize('search.followSymlinks', "Controls whether to follow symlinks while searching."),
default: true
},
'search.smartCase': {
type: 'boolean',
description: nls.localize('search.smartCase', "Search case-insensitively if the pattern is all lowercase, otherwise, search case-sensitively."),
default: false
},
'search.globalFindClipboard': {
type: 'boolean',
default: false,
description: nls.localize('search.globalFindClipboard', "Controls whether the search view should read or modify the shared find clipboard on macOS."),
included: platform.isMacintosh
},
'search.location': {
type: 'string',
enum: ['sidebar', 'panel'],
default: 'sidebar',
description: nls.localize('search.location', "Controls whether the search will be shown as a view in the sidebar or as a panel in the panel area for more horizontal space."),
},
'search.collapseResults': {
type: 'string',
enum: ['auto', 'alwaysCollapse', 'alwaysExpand'],
enumDescriptions: [
'Files with less than 10 results are expanded. Others are collapsed.',
'',
''
],
default: 'auto',
description: nls.localize('search.collapseAllResults', "Controls whether the search results will be collapsed or expanded."),
},
'search.useReplacePreview': {
type: 'boolean',
default: true,
description: nls.localize('search.useReplacePreview', "Controls whether to open Replace Preview when selecting or replacing a match."),
},
'search.showLineNumbers': {
type: 'boolean',
default: false,
description: nls.localize('search.showLineNumbers', "Controls whether to show line numbers for search results."),
},
'searchRipgrep.enable': {
type: 'boolean',
default: false,
deprecationMessage: nls.localize('search.searchRipgrepEnableDeprecated', "Deprecated. Use \"search.runInExtensionHost\" instead"),
description: nls.localize('search.searchRipgrepEnable', "Whether to run search in the extension host")
},
'search.runInExtensionHost': {
type: 'boolean',
default: false,
description: nls.localize('search.runInExtensionHost', "Whether to run search in the extension host. Requires a restart to take effect.")
},
'search.usePCRE2': {
type: 'boolean',
default: false,
description: nls.localize('search.usePCRE2', "Whether to use the PCRE2 regex engine in text search. This enables using some advanced regex features like lookahead and backreferences. However, not all PCRE2 features are supported - only features that are also supported by JavaScript.")
},
'search.actionsPosition': {
type: 'string',
enum: ['auto', 'right'],
enumDescriptions: [
nls.localize('search.actionsPositionAuto', "Position the actionbar to the right when the search view is narrow, and immediately after the content when the search view is wide."),
nls.localize('search.actionsPositionRight', "Always position the actionbar to the right."),
],
default: 'auto',
description: nls.localize('search.actionsPosition', "Controls the positioning of the actionbar on rows in the search view.")
}
}
});
registerLanguageCommand('_executeWorkspaceSymbolProvider', function (accessor, args: { query: string; }) {
const { query } = args;
if (typeof query !== 'string') {
throw illegalArgument();
}
return getWorkspaceSymbols(query);
});
// View menu
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
group: '3_views',
command: {
id: VIEWLET_ID,
title: nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search")
},
order: 2
});
// Go to menu
// {{SQL CARBON EDIT}} - Disable unused menu item
// MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
// group: '3_global_nav',
// command: {
// id: 'workbench.action.showAllSymbols',
// title: nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace...")
// },
// order: 2
// });
// {{SQL CARBON EDIT}} - End

View File

@@ -0,0 +1,776 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { Action } from 'vs/base/common/actions';
import { INavigator } from 'vs/base/common/iterator';
import { createKeybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { Schemas } from 'vs/base/common/network';
import { normalize } from 'vs/base/common/path';
import { isWindows, OS } from 'vs/base/common/platform';
import { repeat } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ICommandHandler } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { SearchView } from 'vs/workbench/contrib/search/browser/searchView';
import * as Constants from 'vs/workbench/contrib/search/common/constants';
import { IReplaceService } from 'vs/workbench/contrib/search/common/replace';
import { BaseFolderMatch, FileMatch, FileMatchOrMatch, FolderMatch, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ISearchConfiguration, VIEWLET_ID, PANEL_ID } from 'vs/workbench/services/search/common/search';
import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet';
import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel';
export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean {
const searchView = getSearchView(viewletService, panelService);
const activeElement = document.activeElement;
return !!(searchView && activeElement && DOM.isAncestor(activeElement, searchView.getContainer()));
}
export function appendKeyBindingLabel(label: string, inputKeyBinding: number | ResolvedKeybinding | undefined, keyBindingService2: IKeybindingService): string {
if (typeof inputKeyBinding === 'number') {
const keybinding = createKeybinding(inputKeyBinding, OS);
if (keybinding) {
const resolvedKeybindings = keyBindingService2.resolveKeybinding(keybinding);
return doAppendKeyBindingLabel(label, resolvedKeybindings.length > 0 ? resolvedKeybindings[0] : undefined);
}
return doAppendKeyBindingLabel(label, undefined);
} else {
return doAppendKeyBindingLabel(label, inputKeyBinding);
}
}
export function openSearchView(viewletService: IViewletService, panelService: IPanelService, configurationService: IConfigurationService, focus?: boolean): Promise<SearchView> {
if (configurationService.getValue<ISearchConfiguration>().search.location === 'panel') {
return Promise.resolve((panelService.openPanel(PANEL_ID, focus) as SearchPanel).getSearchView());
}
return viewletService.openViewlet(VIEWLET_ID, focus).then(viewlet => (viewlet as SearchViewlet).getSearchView());
}
export function getSearchView(viewletService: IViewletService, panelService: IPanelService): SearchView | null {
const activeViewlet = viewletService.getActiveViewlet();
if (activeViewlet && activeViewlet.getId() === VIEWLET_ID) {
return (activeViewlet as SearchViewlet).getSearchView();
}
const activePanel = panelService.getActivePanel();
if (activePanel && activePanel.getId() === PANEL_ID) {
return (activePanel as SearchPanel).getSearchView();
}
return null;
}
function doAppendKeyBindingLabel(label: string, keyBinding: ResolvedKeybinding | undefined): string {
return keyBinding ? label + ' (' + keyBinding.getLabel() + ')' : label;
}
export const toggleCaseSensitiveCommand = (accessor: ServicesAccessor) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
searchView.toggleCaseSensitive();
}
};
export const toggleWholeWordCommand = (accessor: ServicesAccessor) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
searchView.toggleWholeWords();
}
};
export const toggleRegexCommand = (accessor: ServicesAccessor) => {
const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService));
if (searchView) {
searchView.toggleRegex();
}
};
export class FocusNextInputAction extends Action {
static readonly ID = 'search.focus.nextInputBox';
constructor(id: string, label: string,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService
) {
super(id, label);
}
run(): Promise<any> {
const searchView = getSearchView(this.viewletService, this.panelService);
if (searchView) {
searchView.focusNextInputBox();
}
return Promise.resolve(null);
}
}
export class FocusPreviousInputAction extends Action {
static readonly ID = 'search.focus.previousInputBox';
constructor(id: string, label: string,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService
) {
super(id, label);
}
run(): Promise<any> {
const searchView = getSearchView(this.viewletService, this.panelService);
if (searchView) {
searchView.focusPreviousInputBox();
}
return Promise.resolve(null);
}
}
export abstract class FindOrReplaceInFilesAction extends Action {
constructor(id: string, label: string, protected viewletService: IViewletService, protected panelService: IPanelService, protected configurationService: IConfigurationService,
private expandSearchReplaceWidget: boolean
) {
super(id, label);
}
run(): Promise<any> {
return openSearchView(this.viewletService, this.panelService, this.configurationService, false).then(openedView => {
const searchAndReplaceWidget = openedView.searchAndReplaceWidget;
searchAndReplaceWidget.toggleReplace(this.expandSearchReplaceWidget);
const updatedText = openedView.updateTextFromSelection(!this.expandSearchReplaceWidget);
openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText);
});
}
}
export class FindInFilesAction extends FindOrReplaceInFilesAction {
static readonly LABEL = nls.localize('findInFiles', "Find in Files");
constructor(id: string, label: string,
@IViewletService viewletService: IViewletService,
@IPanelService panelService: IPanelService,
@IConfigurationService configurationService: IConfigurationService
) {
super(id, label, viewletService, panelService, configurationService, /*expandSearchReplaceWidget=*/false);
}
}
export class OpenSearchViewletAction extends FindOrReplaceInFilesAction {
static readonly LABEL = nls.localize('showSearch', "Show Search");
constructor(id: string, label: string,
@IViewletService viewletService: IViewletService,
@IPanelService panelService: IPanelService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IConfigurationService configurationService: IConfigurationService
) {
super(id, label, viewletService, panelService, configurationService, /*expandSearchReplaceWidget=*/false);
}
run(): Promise<any> {
// Pass focus to viewlet if not open or focused
if (this.otherViewletShowing() || !isSearchViewFocused(this.viewletService, this.panelService)) {
return super.run();
}
// Otherwise pass focus to editor group
this.editorGroupService.activeGroup.focus();
return Promise.resolve(true);
}
private otherViewletShowing(): boolean {
return !getSearchView(this.viewletService, this.panelService);
}
}
export class ReplaceInFilesAction extends FindOrReplaceInFilesAction {
static readonly ID = 'workbench.action.replaceInFiles';
static readonly LABEL = nls.localize('replaceInFiles', "Replace in Files");
constructor(id: string, label: string,
@IViewletService viewletService: IViewletService,
@IPanelService panelService: IPanelService,
@IConfigurationService configurationService: IConfigurationService
) {
super(id, label, viewletService, panelService, configurationService, /*expandSearchReplaceWidget=*/true);
}
}
export class CloseReplaceAction extends Action {
constructor(id: string, label: string,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService
) {
super(id, label);
}
run(): Promise<any> {
const searchView = getSearchView(this.viewletService, this.panelService);
if (searchView) {
searchView.searchAndReplaceWidget.toggleReplace(false);
searchView.searchAndReplaceWidget.focus();
}
return Promise.resolve(null);
}
}
export class RefreshAction extends Action {
static readonly ID: string = 'search.action.refreshSearchResults';
static LABEL: string = nls.localize('RefreshAction.label', "Refresh");
private searchView: SearchView | null;
constructor(id: string, label: string,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService
) {
super(id, label, 'search-action refresh');
this.searchView = getSearchView(this.viewletService, this.panelService);
}
get enabled(): boolean {
return !!this.searchView && this.searchView.isSearchSubmitted();
}
update(): void {
this._setEnabled(this.enabled);
}
run(): Promise<void> {
const searchView = getSearchView(this.viewletService, this.panelService);
if (searchView) {
searchView.onQueryChanged();
}
return Promise.resolve();
}
}
export class CollapseDeepestExpandedLevelAction extends Action {
static readonly ID: string = 'search.action.collapseSearchResults';
static LABEL: string = nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All");
constructor(id: string, label: string,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService
) {
super(id, label, 'search-action collapse');
this.update();
}
update(): void {
const searchView = getSearchView(this.viewletService, this.panelService);
this.enabled = !!searchView && searchView.hasSearchResults();
}
run(): Promise<void> {
const searchView = getSearchView(this.viewletService, this.panelService);
if (searchView) {
const viewer = searchView.getControl();
/**
* one level to collapse so collapse everything. If FolderMatch, check if there are visible grandchildren,
* i.e. if Matches are returned by the navigator, and if so, collapse to them, otherwise collapse all levels.
*/
const navigator = viewer.navigate();
let node = navigator.first();
let collapseFileMatchLevel = false;
if (node instanceof BaseFolderMatch) {
while (node = navigator.next()) {
if (node instanceof Match) {
collapseFileMatchLevel = true;
break;
}
}
}
if (collapseFileMatchLevel) {
node = navigator.first();
do {
if (node instanceof FileMatch) {
viewer.collapse(node);
}
} while (node = navigator.next());
} else {
viewer.collapseAll();
}
viewer.domFocus();
viewer.focusFirst();
}
return Promise.resolve(undefined);
}
}
export class ClearSearchResultsAction extends Action {
static readonly ID: string = 'search.action.clearSearchResults';
static LABEL: string = nls.localize('ClearSearchResultsAction.label', "Clear Search Results");
constructor(id: string, label: string,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService
) {
super(id, label, 'search-action clear-search-results');
this.update();
}
update(): void {
const searchView = getSearchView(this.viewletService, this.panelService);
this.enabled = !!searchView && (!searchView.allSearchFieldsClear() || searchView.hasSearchResults());
}
run(): Promise<void> {
const searchView = getSearchView(this.viewletService, this.panelService);
if (searchView) {
searchView.clearSearchResults();
}
return Promise.resolve();
}
}
export class CancelSearchAction extends Action {
static readonly ID: string = 'search.action.cancelSearch';
static LABEL: string = nls.localize('CancelSearchAction.label', "Cancel Search");
constructor(id: string, label: string,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService
) {
super(id, label, 'search-action cancel-search');
this.update();
}
update(): void {
const searchView = getSearchView(this.viewletService, this.panelService);
this.enabled = !!searchView && searchView.isSearching();
}
run(): Promise<void> {
const searchView = getSearchView(this.viewletService, this.panelService);
if (searchView) {
searchView.cancelSearch();
}
return Promise.resolve(undefined);
}
}
export class FocusNextSearchResultAction extends Action {
static readonly ID = 'search.action.focusNextSearchResult';
static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result");
constructor(id: string, label: string,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(id, label);
}
run(): Promise<any> {
return openSearchView(this.viewletService, this.panelService, this.configurationService).then(searchView => {
searchView.selectNextMatch();
});
}
}
export class FocusPreviousSearchResultAction extends Action {
static readonly ID = 'search.action.focusPreviousSearchResult';
static readonly LABEL = nls.localize('FocusPreviousSearchResult.label', "Focus Previous Search Result");
constructor(id: string, label: string,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(id, label);
}
run(): Promise<any> {
return openSearchView(this.viewletService, this.panelService, this.configurationService).then(searchView => {
searchView.selectPreviousMatch();
});
}
}
export abstract class AbstractSearchAndReplaceAction extends Action {
/**
* Returns element to focus after removing the given element
*/
getElementToFocusAfterRemoved(viewer: WorkbenchObjectTree<RenderableMatch>, elementToBeRemoved: RenderableMatch): RenderableMatch {
const elementToFocus = this.getNextElementAfterRemoved(viewer, elementToBeRemoved);
return elementToFocus || this.getPreviousElementAfterRemoved(viewer, elementToBeRemoved);
}
getNextElementAfterRemoved(viewer: WorkbenchObjectTree<RenderableMatch>, element: RenderableMatch): RenderableMatch {
const navigator: INavigator<any> = viewer.navigate(element);
if (element instanceof BaseFolderMatch) {
while (!!navigator.next() && !(navigator.current() instanceof BaseFolderMatch)) { }
} else if (element instanceof FileMatch) {
while (!!navigator.next() && !(navigator.current() instanceof FileMatch)) { }
} else {
while (navigator.next() && !(navigator.current() instanceof Match)) {
viewer.expand(navigator.current());
}
}
return navigator.current();
}
getPreviousElementAfterRemoved(viewer: WorkbenchObjectTree<RenderableMatch>, element: RenderableMatch): RenderableMatch {
const navigator: INavigator<any> = viewer.navigate(element);
let previousElement = navigator.previous();
// Hence take the previous element.
const parent = element.parent();
if (parent === previousElement) {
previousElement = navigator.previous();
}
if (parent instanceof FileMatch && parent.parent() === previousElement) {
previousElement = navigator.previous();
}
// If the previous element is a File or Folder, expand it and go to its last child.
// Spell out the two cases, would be too easy to create an infinite loop, like by adding another level...
if (element instanceof Match && previousElement && previousElement instanceof BaseFolderMatch) {
navigator.next();
viewer.expand(previousElement);
previousElement = navigator.previous();
}
if (element instanceof Match && previousElement && previousElement instanceof FileMatch) {
navigator.next();
viewer.expand(previousElement);
previousElement = navigator.previous();
}
return previousElement;
}
}
export class RemoveAction extends AbstractSearchAndReplaceAction {
static LABEL = nls.localize('RemoveAction.label', "Dismiss");
constructor(
private viewer: WorkbenchObjectTree<RenderableMatch>,
private element: RenderableMatch
) {
super('remove', RemoveAction.LABEL, 'action-remove');
}
run(): Promise<any> {
const currentFocusElement = this.viewer.getFocus()[0];
const nextFocusElement = !currentFocusElement || currentFocusElement instanceof SearchResult || elementIsEqualOrParent(currentFocusElement, this.element) ?
this.getElementToFocusAfterRemoved(this.viewer, this.element) :
null;
if (nextFocusElement) {
this.viewer.reveal(nextFocusElement);
this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent());
}
this.element.parent().remove(<any>this.element);
this.viewer.domFocus();
return Promise.resolve();
}
}
function elementIsEqualOrParent(element: RenderableMatch, testParent: RenderableMatch | SearchResult): boolean {
do {
if (element === testParent) {
return true;
}
} while (!(element.parent() instanceof SearchResult) && (element = <RenderableMatch>element.parent()));
return false;
}
export class ReplaceAllAction extends AbstractSearchAndReplaceAction {
static readonly LABEL = nls.localize('file.replaceAll.label', "Replace All");
constructor(
private viewlet: SearchView,
private fileMatch: FileMatch,
@IKeybindingService keyBindingService: IKeybindingService
) {
super(Constants.ReplaceAllInFileActionId, appendKeyBindingLabel(ReplaceAllAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFileActionId), keyBindingService), 'action-replace-all');
}
run(): Promise<any> {
const tree = this.viewlet.getControl();
const nextFocusElement = this.getElementToFocusAfterRemoved(tree, this.fileMatch);
return this.fileMatch.parent().replace(this.fileMatch).then(() => {
if (nextFocusElement) {
tree.setFocus([nextFocusElement], getSelectionKeyboardEvent());
}
tree.domFocus();
this.viewlet.open(this.fileMatch, true);
});
}
}
export class ReplaceAllInFolderAction extends AbstractSearchAndReplaceAction {
static readonly LABEL = nls.localize('file.replaceAll.label', "Replace All");
constructor(private viewer: WorkbenchObjectTree<RenderableMatch>, private folderMatch: FolderMatch,
@IKeybindingService keyBindingService: IKeybindingService
) {
super(Constants.ReplaceAllInFolderActionId, appendKeyBindingLabel(ReplaceAllInFolderAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFolderActionId), keyBindingService), 'action-replace-all');
}
run(): Promise<any> {
const nextFocusElement = this.getElementToFocusAfterRemoved(this.viewer, this.folderMatch);
return this.folderMatch.replaceAll().then(() => {
if (nextFocusElement) {
this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent());
}
this.viewer.domFocus();
});
}
}
export class ReplaceAction extends AbstractSearchAndReplaceAction {
static readonly LABEL = nls.localize('match.replace.label', "Replace");
constructor(private viewer: WorkbenchObjectTree<RenderableMatch>, private element: Match, private viewlet: SearchView,
@IReplaceService private readonly replaceService: IReplaceService,
@IKeybindingService keyBindingService: IKeybindingService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService) {
super(Constants.ReplaceActionId, appendKeyBindingLabel(ReplaceAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceActionId), keyBindingService), 'action-replace');
}
run(): Promise<any> {
this.enabled = false;
return this.element.parent().replace(this.element).then(() => {
const elementToFocus = this.getElementToFocusAfterReplace();
if (elementToFocus) {
this.viewer.setFocus([elementToFocus], getSelectionKeyboardEvent());
}
return this.getElementToShowReplacePreview(elementToFocus);
}).then(elementToShowReplacePreview => {
this.viewer.domFocus();
const useReplacePreview = this.configurationService.getValue<ISearchConfiguration>().search.useReplacePreview;
if (!useReplacePreview || !elementToShowReplacePreview || this.hasToOpenFile()) {
this.viewlet.open(this.element, true);
} else {
this.replaceService.openReplacePreview(elementToShowReplacePreview, true);
}
});
}
private getElementToFocusAfterReplace(): Match {
const navigator: INavigator<any> = this.viewer.navigate();
let fileMatched = false;
let elementToFocus: any = null;
do {
elementToFocus = navigator.current();
if (elementToFocus instanceof Match) {
if (elementToFocus.parent().id() === this.element.parent().id()) {
fileMatched = true;
if (this.element.range().getStartPosition().isBeforeOrEqual((<Match>elementToFocus).range().getStartPosition())) {
// Closest next match in the same file
break;
}
} else if (fileMatched) {
// First match in the next file (if expanded)
break;
}
} else if (fileMatched) {
if (this.viewer.isCollapsed(elementToFocus)) {
// Next file match (if collapsed)
break;
}
}
} while (!!navigator.next());
return elementToFocus;
}
private async getElementToShowReplacePreview(elementToFocus: FileMatchOrMatch): Promise<Match | null> {
if (this.hasSameParent(elementToFocus)) {
return <Match>elementToFocus;
}
const previousElement = await this.getPreviousElementAfterRemoved(this.viewer, this.element);
if (this.hasSameParent(previousElement)) {
return <Match>previousElement;
}
return null;
}
private hasSameParent(element: RenderableMatch): boolean {
return element && element instanceof Match && element.parent().resource() === this.element.parent().resource();
}
private hasToOpenFile(): boolean {
const activeEditor = this.editorService.activeEditor;
const file = activeEditor ? activeEditor.getResource() : undefined;
if (file) {
return file.toString() === this.element.parent().resource().toString();
}
return false;
}
}
function uriToClipboardString(resource: URI): string {
return resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(resource.fsPath)) : resource.toString();
}
export const copyPathCommand: ICommandHandler = (accessor, fileMatch: FileMatch | FolderMatch) => {
const clipboardService = accessor.get(IClipboardService);
const text = uriToClipboardString(fileMatch.resource());
clipboardService.writeText(text);
};
function matchToString(match: Match, indent = 0): string {
const getFirstLinePrefix = () => `${match.range().startLineNumber},${match.range().startColumn}`;
const getOtherLinePrefix = (i: number) => match.range().startLineNumber + i + '';
const fullMatchLines = match.fullPreviewLines();
const largestPrefixSize = fullMatchLines.reduce((largest, _, i) => {
const thisSize = i === 0 ?
getFirstLinePrefix().length :
getOtherLinePrefix(i).length;
return Math.max(thisSize, largest);
}, 0);
const formattedLines = fullMatchLines
.map((line, i) => {
const prefix = i === 0 ?
getFirstLinePrefix() :
getOtherLinePrefix(i);
const paddingStr = repeat(' ', largestPrefixSize - prefix.length);
const indentStr = repeat(' ', indent);
return `${indentStr}${prefix}: ${paddingStr}${line}`;
});
return formattedLines.join('\n');
}
const lineDelimiter = isWindows ? '\r\n' : '\n';
function fileMatchToString(fileMatch: FileMatch, maxMatches: number): { text: string, count: number } {
const matchTextRows = fileMatch.matches()
.sort(searchMatchComparer)
.slice(0, maxMatches)
.map(match => matchToString(match, 2));
return {
text: `${uriToClipboardString(fileMatch.resource())}${lineDelimiter}${matchTextRows.join(lineDelimiter)}`,
count: matchTextRows.length
};
}
function folderMatchToString(folderMatch: FolderMatch | BaseFolderMatch, maxMatches: number): { text: string, count: number } {
const fileResults: string[] = [];
let numMatches = 0;
const matches = folderMatch.matches().sort(searchMatchComparer);
for (let i = 0; i < folderMatch.fileCount() && numMatches < maxMatches; i++) {
const fileResult = fileMatchToString(matches[i], maxMatches - numMatches);
numMatches += fileResult.count;
fileResults.push(fileResult.text);
}
return {
text: fileResults.join(lineDelimiter + lineDelimiter),
count: numMatches
};
}
const maxClipboardMatches = 1e4;
export const copyMatchCommand: ICommandHandler = (accessor, match: RenderableMatch) => {
const clipboardService = accessor.get(IClipboardService);
let text: string | undefined;
if (match instanceof Match) {
text = matchToString(match);
} else if (match instanceof FileMatch) {
text = fileMatchToString(match, maxClipboardMatches).text;
} else if (match instanceof BaseFolderMatch) {
text = folderMatchToString(match, maxClipboardMatches).text;
}
if (text) {
clipboardService.writeText(text);
}
};
function allFolderMatchesToString(folderMatches: Array<FolderMatch | BaseFolderMatch>, maxMatches: number): string {
const folderResults: string[] = [];
let numMatches = 0;
folderMatches = folderMatches.sort(searchMatchComparer);
for (let i = 0; i < folderMatches.length && numMatches < maxMatches; i++) {
const folderResult = folderMatchToString(folderMatches[i], maxMatches - numMatches);
if (folderResult.count) {
numMatches += folderResult.count;
folderResults.push(folderResult.text);
}
}
return folderResults.join(lineDelimiter + lineDelimiter);
}
export const copyAllCommand: ICommandHandler = accessor => {
const viewletService = accessor.get(IViewletService);
const panelService = accessor.get(IPanelService);
const clipboardService = accessor.get(IClipboardService);
const searchView = getSearchView(viewletService, panelService);
if (searchView) {
const root = searchView.searchResult;
const text = allFolderMatchesToString(root.folderMatches(), maxClipboardMatches);
clipboardService.writeText(text);
}
};
export const clearHistoryCommand: ICommandHandler = accessor => {
const searchHistoryService = accessor.get(ISearchHistoryService);
searchHistoryService.clearHistory();
};
export const focusSearchListCommand: ICommandHandler = accessor => {
const viewletService = accessor.get(IViewletService);
const panelService = accessor.get(IPanelService);
const configurationService = accessor.get(IConfigurationService);
openSearchView(viewletService, panelService, configurationService).then(searchView => {
searchView.moveFocusToResults();
});
};

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { PANEL_ID } from 'vs/workbench/services/search/common/search';
import { SearchView } from 'vs/workbench/contrib/search/browser/searchView';
import { Panel } from 'vs/workbench/browser/panel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { localize } from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { IAction } from 'vs/base/common/actions';
export class SearchPanel extends Panel {
private readonly searchView: SearchView;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IInstantiationService instantiationService: IInstantiationService,
) {
super(PANEL_ID, telemetryService, themeService, storageService);
this.searchView = this._register(instantiationService.createInstance(SearchView, { id: PANEL_ID, title: localize('search', "Search") }));
this._register(this.searchView.onDidChangeTitleArea(() => this.updateTitleArea()));
this._register(this.onDidChangeVisibility(visible => this.searchView.setVisible(visible)));
}
create(parent: HTMLElement): void {
dom.addClass(parent, 'monaco-panel-view');
this.searchView.render();
dom.append(parent, this.searchView.element);
this.searchView.setExpanded(true);
this.searchView.headerVisible = false;
}
public getTitle(): string {
return this.searchView.title;
}
public layout(dimension: dom.Dimension): void {
this.searchView.width = dimension.width;
this.searchView.layout(dimension.height);
}
public focus(): void {
this.searchView.focus();
}
getActions(): IAction[] {
return this.searchView.getActions();
}
getSecondaryActions(): IAction[] {
return this.searchView.getSecondaryActions();
}
saveState(): void {
this.searchView.saveState();
super.saveState();
}
getSearchView(): SearchView | null {
return this.searchView;
}
}

View File

@@ -0,0 +1,383 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { ITreeNode, ITreeRenderer, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree';
import { IAction } from 'vs/base/common/actions';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as paths from 'vs/base/common/path';
import * as resources from 'vs/base/common/resources';
import * as nls from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { FileKind } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search';
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction } from 'vs/workbench/contrib/search/browser/searchActions';
import { SearchView } from 'vs/workbench/contrib/search/browser/searchView';
import { FileMatch, FolderMatch, Match, RenderableMatch, SearchModel, BaseFolderMatch } from 'vs/workbench/contrib/search/common/searchModel';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { fillResourceDataTransfers } from 'vs/workbench/browser/dnd';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { URI } from 'vs/base/common/uri';
interface IFolderMatchTemplate {
label: IResourceLabel;
badge: CountBadge;
actions: ActionBar;
disposables: IDisposable[];
}
interface IFileMatchTemplate {
el: HTMLElement;
label: IResourceLabel;
badge: CountBadge;
actions: ActionBar;
disposables: IDisposable[];
}
interface IMatchTemplate {
parent: HTMLElement;
before: HTMLElement;
match: HTMLElement;
replace: HTMLElement;
after: HTMLElement;
lineNumber: HTMLElement;
actions: ActionBar;
}
export class SearchDelegate implements IListVirtualDelegate<RenderableMatch> {
getHeight(element: RenderableMatch): number {
return 22;
}
getTemplateId(element: RenderableMatch): string {
if (element instanceof BaseFolderMatch) {
return FolderMatchRenderer.TEMPLATE_ID;
} else if (element instanceof FileMatch) {
return FileMatchRenderer.TEMPLATE_ID;
} else if (element instanceof Match) {
return MatchRenderer.TEMPLATE_ID;
}
console.error('Invalid search tree element', element);
throw new Error('Invalid search tree element');
}
}
export class FolderMatchRenderer extends Disposable implements ITreeRenderer<FolderMatch, any, IFolderMatchTemplate> {
static readonly TEMPLATE_ID = 'folderMatch';
readonly templateId = FolderMatchRenderer.TEMPLATE_ID;
constructor(
private searchModel: SearchModel,
private searchView: SearchView,
private labels: ResourceLabels,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService
) {
super();
}
renderTemplate(container: HTMLElement): IFolderMatchTemplate {
const disposables: IDisposable[] = [];
const folderMatchElement = DOM.append(container, DOM.$('.foldermatch'));
const label = this.labels.create(folderMatchElement);
disposables.push(label);
const badge = new CountBadge(DOM.append(folderMatchElement, DOM.$('.badge')));
disposables.push(attachBadgeStyler(badge, this.themeService));
const actionBarContainer = DOM.append(folderMatchElement, DOM.$('.actionBarContainer'));
const actions = new ActionBar(actionBarContainer, { animated: false });
disposables.push(actions);
return {
label,
badge,
actions,
disposables
};
}
renderElement(node: ITreeNode<FolderMatch, any>, index: number, templateData: IFolderMatchTemplate): void {
const folderMatch = node.element;
if (folderMatch.hasResource()) {
const workspaceFolder = this.contextService.getWorkspaceFolder(folderMatch.resource());
if (workspaceFolder && resources.isEqual(workspaceFolder.uri, folderMatch.resource())) {
templateData.label.setFile(folderMatch.resource(), { fileKind: FileKind.ROOT_FOLDER, hidePath: true });
} else {
templateData.label.setFile(folderMatch.resource(), { fileKind: FileKind.FOLDER });
}
} else {
templateData.label.setLabel(nls.localize('searchFolderMatch.other.label', "Other files"));
}
const count = folderMatch.fileCount();
templateData.badge.setCount(count);
templateData.badge.setTitleFormat(count > 1 ? nls.localize('searchFileMatches', "{0} files found", count) : nls.localize('searchFileMatch', "{0} file found", count));
templateData.actions.clear();
const actions: IAction[] = [];
if (this.searchModel.isReplaceActive() && count > 0) {
actions.push(this.instantiationService.createInstance(ReplaceAllInFolderAction, this.searchView.getControl(), folderMatch));
}
actions.push(new RemoveAction(this.searchView.getControl(), folderMatch));
templateData.actions.push(actions, { icon: true, label: false });
}
disposeElement(element: ITreeNode<RenderableMatch, any>, index: number, templateData: IFolderMatchTemplate): void {
}
disposeTemplate(templateData: IFolderMatchTemplate): void {
dispose(templateData.disposables);
}
}
export class FileMatchRenderer extends Disposable implements ITreeRenderer<FileMatch, any, IFileMatchTemplate> {
static readonly TEMPLATE_ID = 'fileMatch';
readonly templateId = FileMatchRenderer.TEMPLATE_ID;
constructor(
private searchModel: SearchModel,
private searchView: SearchView,
private labels: ResourceLabels,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService
) {
super();
}
renderTemplate(container: HTMLElement): IFileMatchTemplate {
const disposables: IDisposable[] = [];
const fileMatchElement = DOM.append(container, DOM.$('.filematch'));
const label = this.labels.create(fileMatchElement);
disposables.push(label);
const badge = new CountBadge(DOM.append(fileMatchElement, DOM.$('.badge')));
disposables.push(attachBadgeStyler(badge, this.themeService));
const actionBarContainer = DOM.append(fileMatchElement, DOM.$('.actionBarContainer'));
const actions = new ActionBar(actionBarContainer, { animated: false });
disposables.push(actions);
return {
el: fileMatchElement,
label,
badge,
actions,
disposables
};
}
renderElement(node: ITreeNode<FileMatch, any>, index: number, templateData: IFileMatchTemplate): void {
const fileMatch = node.element;
templateData.el.setAttribute('data-resource', fileMatch.resource().toString());
templateData.label.setFile(fileMatch.resource(), { hideIcon: false });
const count = fileMatch.count();
templateData.badge.setCount(count);
templateData.badge.setTitleFormat(count > 1 ? nls.localize('searchMatches', "{0} matches found", count) : nls.localize('searchMatch', "{0} match found", count));
templateData.actions.clear();
const actions: IAction[] = [];
if (this.searchModel.isReplaceActive() && count > 0) {
actions.push(this.instantiationService.createInstance(ReplaceAllAction, this.searchView, fileMatch));
}
actions.push(new RemoveAction(this.searchView.getControl(), fileMatch));
templateData.actions.push(actions, { icon: true, label: false });
}
disposeElement(element: ITreeNode<RenderableMatch, any>, index: number, templateData: IFileMatchTemplate): void {
}
disposeTemplate(templateData: IFileMatchTemplate): void {
dispose(templateData.disposables);
}
}
export class MatchRenderer extends Disposable implements ITreeRenderer<Match, void, IMatchTemplate> {
static readonly TEMPLATE_ID = 'match';
readonly templateId = MatchRenderer.TEMPLATE_ID;
constructor(
private searchModel: SearchModel,
private searchView: SearchView,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
super();
}
renderTemplate(container: HTMLElement): IMatchTemplate {
DOM.addClass(container, 'linematch');
const parent = DOM.append(container, DOM.$('a.plain.match'));
const before = DOM.append(parent, DOM.$('span'));
const match = DOM.append(parent, DOM.$('span.findInFileMatch'));
const replace = DOM.append(parent, DOM.$('span.replaceMatch'));
const after = DOM.append(parent, DOM.$('span'));
const lineNumber = DOM.append(container, DOM.$('span.matchLineNum'));
const actionBarContainer = DOM.append(container, DOM.$('span.actionBarContainer'));
const actions = new ActionBar(actionBarContainer, { animated: false });
return {
parent,
before,
match,
replace,
after,
lineNumber,
actions
};
}
renderElement(node: ITreeNode<Match, any>, index: number, templateData: IMatchTemplate): void {
const match = node.element;
const preview = match.preview();
const replace = this.searchModel.isReplaceActive() && !!this.searchModel.replaceString;
templateData.before.textContent = preview.before;
templateData.match.textContent = preview.inside;
DOM.toggleClass(templateData.match, 'replace', replace);
templateData.replace.textContent = replace ? match.replaceString : '';
templateData.after.textContent = preview.after;
templateData.parent.title = (preview.before + (replace ? match.replaceString : preview.inside) + preview.after).trim().substr(0, 999);
const numLines = match.range().endLineNumber - match.range().startLineNumber;
const extraLinesStr = numLines > 0 ? `+${numLines}` : '';
const showLineNumbers = this.configurationService.getValue<ISearchConfigurationProperties>('search').showLineNumbers;
const lineNumberStr = showLineNumbers ? `:${match.range().startLineNumber}` : '';
DOM.toggleClass(templateData.lineNumber, 'show', (numLines > 0) || showLineNumbers);
templateData.lineNumber.textContent = lineNumberStr + extraLinesStr;
templateData.lineNumber.setAttribute('title', this.getMatchTitle(match, showLineNumbers));
templateData.actions.clear();
if (this.searchModel.isReplaceActive()) {
templateData.actions.push([this.instantiationService.createInstance(ReplaceAction, this.searchView.getControl(), match, this.searchView), new RemoveAction(this.searchView.getControl(), match)], { icon: true, label: false });
} else {
templateData.actions.push([new RemoveAction(this.searchView.getControl(), match)], { icon: true, label: false });
}
}
disposeElement(element: ITreeNode<Match, any>, index: number, templateData: IMatchTemplate): void {
}
disposeTemplate(templateData: IMatchTemplate): void {
templateData.actions.dispose();
}
private getMatchTitle(match: Match, showLineNumbers: boolean): string {
const startLine = match.range().startLineNumber;
const numLines = match.range().endLineNumber - match.range().startLineNumber;
const lineNumStr = showLineNumbers ?
nls.localize('lineNumStr', "From line {0}", startLine, numLines) + ' ' :
'';
const numLinesStr = numLines > 0 ?
'+ ' + nls.localize('numLinesStr', "{0} more lines", numLines) :
'';
return lineNumStr + numLinesStr;
}
}
export class SearchAccessibilityProvider implements IAccessibilityProvider<RenderableMatch> {
constructor(
private searchModel: SearchModel,
@ILabelService private readonly labelService: ILabelService
) {
}
getAriaLabel(element: RenderableMatch): string | null {
if (element instanceof BaseFolderMatch) {
return element.hasResource() ?
nls.localize('folderMatchAriaLabel', "{0} matches in folder root {1}, Search result", element.count(), element.name()) :
nls.localize('otherFilesAriaLabel', "{0} matches outside of the workspace, Search result", element.count());
}
if (element instanceof FileMatch) {
const path = this.labelService.getUriLabel(element.resource(), { relative: true }) || element.resource().fsPath;
return nls.localize('fileMatchAriaLabel', "{0} matches in file {1} of folder {2}, Search result", element.count(), element.name(), paths.dirname(path));
}
if (element instanceof Match) {
const match = <Match>element;
const searchModel: SearchModel = this.searchModel;
const replace = searchModel.isReplaceActive() && !!searchModel.replaceString;
const matchString = match.getMatchString();
const range = match.range();
const matchText = match.text().substr(0, range.endColumn + 150);
if (replace) {
return nls.localize('replacePreviewResultAria', "Replace term {0} with {1} at column position {2} in line with text {3}", matchString, match.replaceString, range.startColumn + 1, matchText);
}
return nls.localize('searchResultAria', "Found term {0} at column position {1} in line with text {2}", matchString, range.startColumn + 1, matchText);
}
return null;
}
}
export class SearchDND implements ITreeDragAndDrop<RenderableMatch> {
constructor(
@IInstantiationService private instantiationService: IInstantiationService
) { }
onDragOver(data: IDragAndDropData, targetElement: RenderableMatch, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
return false;
}
getDragURI(element: RenderableMatch): string | null {
if (element instanceof FileMatch) {
return element.remove.toString();
}
return null;
}
getDragLabel?(elements: RenderableMatch[]): string | undefined {
if (elements.length > 1) {
return String(elements.length);
}
const element = elements[0];
return element instanceof FileMatch ?
resources.basename(element.resource()) :
undefined;
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
const elements = (data as ElementsDragAndDropData<RenderableMatch>).elements;
const resources: URI[] = elements
.filter(e => e instanceof FileMatch)
.map((fm: FileMatch) => fm.resource());
if (resources.length) {
// Apply some datatransfer types to allow for dragging the element outside of the application
this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent);
}
}
drop(data: IDragAndDropData, targetElement: RenderableMatch, targetIndex: number, originalEvent: DragEvent): void {
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search';
import { SearchView } from 'vs/workbench/contrib/search/browser/searchView';
import { Registry } from 'vs/platform/registry/common/platform';
import { ViewletRegistry, Extensions } from 'vs/workbench/browser/viewlet';
export class SearchViewlet extends ViewContainerViewlet {
constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@ITelemetryService telemetryService: ITelemetryService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IStorageService protected storageService: IStorageService,
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService
) {
super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
}
getTitle(): string {
return Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet(this.getId()).name;
}
getSearchView(): SearchView | null {
const view = super.getView(VIEW_ID);
return view ? view as SearchView : null;
}
}

View File

@@ -0,0 +1,557 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Button, IButtonOptions } from 'vs/base/browser/ui/button/button';
import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput';
import { HistoryInputBox, IMessage } from 'vs/base/browser/ui/inputbox/inputBox';
import { Widget } from 'vs/base/browser/ui/widget';
import { Action } from 'vs/base/common/actions';
import { Delayer } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as strings from 'vs/base/common/strings';
import { CONTEXT_FIND_WIDGET_NOT_VISIBLE } from 'vs/editor/contrib/find/findModel';
import * as nls from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search';
import { attachFindInputBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget';
import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions';
import * as Constants from 'vs/workbench/contrib/search/common/constants';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
export interface ISearchWidgetOptions {
value?: string;
replaceValue?: string;
isRegex?: boolean;
isCaseSensitive?: boolean;
isWholeWords?: boolean;
searchHistory?: string[];
replaceHistory?: string[];
}
class ReplaceAllAction extends Action {
private static fgInstance: ReplaceAllAction | null = null;
static readonly ID: string = 'search.action.replaceAll';
static get INSTANCE(): ReplaceAllAction {
if (ReplaceAllAction.fgInstance === null) {
ReplaceAllAction.fgInstance = new ReplaceAllAction();
}
return ReplaceAllAction.fgInstance;
}
private _searchWidget: SearchWidget | null = null;
constructor() {
super(ReplaceAllAction.ID, '', 'action-replace-all', false);
}
set searchWidget(searchWidget: SearchWidget) {
this._searchWidget = searchWidget;
}
run(): Promise<any> {
if (this._searchWidget) {
return this._searchWidget.triggerReplaceAll();
}
return Promise.resolve(null);
}
}
export class SearchWidget extends Widget {
private static readonly REPLACE_ALL_DISABLED_LABEL = nls.localize('search.action.replaceAll.disabled.label', "Replace All (Submit Search to Enable)");
private static readonly REPLACE_ALL_ENABLED_LABEL = (keyBindingService2: IKeybindingService): string => {
const kb = keyBindingService2.lookupKeybinding(ReplaceAllAction.ID);
return appendKeyBindingLabel(nls.localize('search.action.replaceAll.enabled.label', "Replace All"), kb, keyBindingService2);
}
domNode: HTMLElement;
searchInput: FindInput;
searchInputFocusTracker: dom.IFocusTracker;
private searchInputBoxFocused: IContextKey<boolean>;
private replaceContainer: HTMLElement;
replaceInput: HistoryInputBox;
private toggleReplaceButton: Button;
private replaceAllAction: ReplaceAllAction;
private replaceActive: IContextKey<boolean>;
private replaceActionBar: ActionBar;
replaceInputFocusTracker: dom.IFocusTracker;
private replaceInputBoxFocused: IContextKey<boolean>;
private _replaceHistoryDelayer: Delayer<void>;
private ignoreGlobalFindBufferOnNextFocus = false;
private previousGlobalFindBufferValue: string;
private _onSearchSubmit = this._register(new Emitter<void>());
readonly onSearchSubmit: Event<void> = this._onSearchSubmit.event;
private _onSearchCancel = this._register(new Emitter<void>());
readonly onSearchCancel: Event<void> = this._onSearchCancel.event;
private _onReplaceToggled = this._register(new Emitter<void>());
readonly onReplaceToggled: Event<void> = this._onReplaceToggled.event;
private _onReplaceStateChange = this._register(new Emitter<boolean>());
readonly onReplaceStateChange: Event<boolean> = this._onReplaceStateChange.event;
private _onReplaceValueChanged = this._register(new Emitter<string | undefined>());
readonly onReplaceValueChanged: Event<string> = this._onReplaceValueChanged.event;
private _onReplaceAll = this._register(new Emitter<void>());
readonly onReplaceAll: Event<void> = this._onReplaceAll.event;
private _onBlur = this._register(new Emitter<void>());
readonly onBlur: Event<void> = this._onBlur.event;
private _onDidHeightChange = this._register(new Emitter<void>());
readonly onDidHeightChange: Event<void> = this._onDidHeightChange.event;
constructor(
container: HTMLElement,
options: ISearchWidgetOptions,
@IContextViewService private readonly contextViewService: IContextViewService,
@IThemeService private readonly themeService: IThemeService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IKeybindingService private readonly keyBindingService: IKeybindingService,
@IClipboardService private readonly clipboardServce: IClipboardService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
) {
super();
this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.contextKeyService);
this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService);
this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService);
this._replaceHistoryDelayer = new Delayer<void>(500);
this.render(container, options);
this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('editor.accessibilitySupport')) {
this.updateAccessibilitySupport();
}
});
this.accessibilityService.onDidChangeAccessibilitySupport(() => this.updateAccessibilitySupport());
this.updateAccessibilitySupport();
}
focus(select: boolean = true, focusReplace: boolean = false, suppressGlobalSearchBuffer = false): void {
this.ignoreGlobalFindBufferOnNextFocus = suppressGlobalSearchBuffer;
if (focusReplace && this.isReplaceShown()) {
this.replaceInput.focus();
if (select) {
this.replaceInput.select();
}
} else {
this.searchInput.focus();
if (select) {
this.searchInput.select();
}
}
}
setWidth(width: number) {
this.searchInput.inputBox.layout();
this.replaceInput.width = width - 28;
this.replaceInput.layout();
}
clear() {
this.searchInput.clear();
this.replaceInput.value = '';
this.setReplaceAllActionState(false);
}
isReplaceShown(): boolean {
return !dom.hasClass(this.replaceContainer, 'disabled');
}
isReplaceActive(): boolean {
return !!this.replaceActive.get();
}
getReplaceValue(): string {
return this.replaceInput.value;
}
toggleReplace(show?: boolean): void {
if (show === undefined || show !== this.isReplaceShown()) {
this.onToggleReplaceButton();
}
}
getSearchHistory(): string[] {
return this.searchInput.inputBox.getHistory();
}
getReplaceHistory(): string[] {
return this.replaceInput.getHistory();
}
clearHistory(): void {
this.searchInput.inputBox.clearHistory();
}
showNextSearchTerm() {
this.searchInput.inputBox.showNextValue();
}
showPreviousSearchTerm() {
this.searchInput.inputBox.showPreviousValue();
}
showNextReplaceTerm() {
this.replaceInput.showNextValue();
}
showPreviousReplaceTerm() {
this.replaceInput.showPreviousValue();
}
searchInputHasFocus(): boolean {
return !!this.searchInputBoxFocused.get();
}
replaceInputHasFocus(): boolean {
return this.replaceInput.hasFocus();
}
focusReplaceAllAction(): void {
this.replaceActionBar.focus(true);
}
focusRegexAction(): void {
this.searchInput.focusOnRegex();
}
private render(container: HTMLElement, options: ISearchWidgetOptions): void {
this.domNode = dom.append(container, dom.$('.search-widget'));
this.domNode.style.position = 'relative';
this.renderToggleReplaceButton(this.domNode);
this.renderSearchInput(this.domNode, options);
this.renderReplaceInput(this.domNode, options);
}
private isScreenReaderOptimized() {
const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled;
const config = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
return config === 'on' || (config === 'auto' && detected);
}
private updateAccessibilitySupport(): void {
this.searchInput.setFocusInputOnOptionClick(!this.isScreenReaderOptimized());
}
private renderToggleReplaceButton(parent: HTMLElement): void {
const opts: IButtonOptions = {
buttonBackground: undefined,
buttonBorder: undefined,
buttonForeground: undefined,
buttonHoverBackground: undefined
};
this.toggleReplaceButton = this._register(new Button(parent, opts));
this.toggleReplaceButton.element.setAttribute('aria-expanded', 'false');
this.toggleReplaceButton.element.classList.add('collapse');
this.toggleReplaceButton.icon = 'toggle-replace-button';
// TODO@joh need to dispose this listener eventually
this.toggleReplaceButton.onDidClick(() => this.onToggleReplaceButton());
this.toggleReplaceButton.element.title = nls.localize('search.replace.toggle.button.title', "Toggle Replace");
}
private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
const inputOptions: IFindInputOptions = {
label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search or Escape to cancel'),
validation: (value: string) => this.validateSearchInput(value),
placeholder: nls.localize('search.placeHolder', "Search"),
appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleCaseSensitiveCommandId), this.keyBindingService),
appendWholeWordsLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleWholeWordCommandId), this.keyBindingService),
appendRegexLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleRegexCommandId), this.keyBindingService),
history: options.searchHistory,
flexibleHeight: true
};
const searchInputContainer = dom.append(parent, dom.$('.search-container.input-box'));
this.searchInput = this._register(new ContextScopedFindInput(searchInputContainer, this.contextViewService, inputOptions, this.contextKeyService, true));
this._register(attachFindInputBoxStyler(this.searchInput, this.themeService));
this.searchInput.onKeyDown((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyDown(keyboardEvent));
this.searchInput.setValue(options.value || '');
this.searchInput.setRegex(!!options.isRegex);
this.searchInput.setCaseSensitive(!!options.isCaseSensitive);
this.searchInput.setWholeWords(!!options.isWholeWords);
this._register(this.onSearchSubmit(() => {
this.searchInput.inputBox.addToHistory();
}));
this._register(this.searchInput.onCaseSensitiveKeyDown((keyboardEvent: IKeyboardEvent) => this.onCaseSensitiveKeyDown(keyboardEvent)));
this._register(this.searchInput.onRegexKeyDown((keyboardEvent: IKeyboardEvent) => this.onRegexKeyDown(keyboardEvent)));
this._register(this.searchInput.inputBox.onDidChange(() => this.onSearchInputChanged()));
this._register(this.searchInput.inputBox.onDidHeightChange(() => this._onDidHeightChange.fire()));
this._register(this.onReplaceValueChanged(() => {
this._replaceHistoryDelayer.trigger(() => this.replaceInput.addToHistory());
}));
this.searchInputFocusTracker = this._register(dom.trackFocus(this.searchInput.inputBox.inputElement));
this._register(this.searchInputFocusTracker.onDidFocus(() => {
this.searchInputBoxFocused.set(true);
const useGlobalFindBuffer = this.searchConfiguration.globalFindClipboard;
if (!this.ignoreGlobalFindBufferOnNextFocus && useGlobalFindBuffer) {
const globalBufferText = this.clipboardServce.readFindText();
if (this.previousGlobalFindBufferValue !== globalBufferText) {
this.searchInput.inputBox.addToHistory();
this.searchInput.setValue(globalBufferText);
this.searchInput.select();
}
this.previousGlobalFindBufferValue = globalBufferText;
}
this.ignoreGlobalFindBufferOnNextFocus = false;
}));
this._register(this.searchInputFocusTracker.onDidBlur(() => this.searchInputBoxFocused.set(false)));
}
private renderReplaceInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
this.replaceContainer = dom.append(parent, dom.$('.replace-container.disabled'));
const replaceBox = dom.append(this.replaceContainer, dom.$('.input-box'));
this.replaceInput = this._register(new ContextScopedHistoryInputBox(replaceBox, this.contextViewService, {
ariaLabel: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview or Escape to cancel'),
placeholder: nls.localize('search.replace.placeHolder', "Replace"),
history: options.replaceHistory || [],
flexibleHeight: true
}, this.contextKeyService));
this._register(attachInputBoxStyler(this.replaceInput, this.themeService));
this.onkeydown(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent));
this.replaceInput.value = options.replaceValue || '';
this._register(this.replaceInput.onDidChange(() => this._onReplaceValueChanged.fire(undefined)));
this._register(this.replaceInput.onDidHeightChange(() => this._onDidHeightChange.fire()));
this.replaceAllAction = ReplaceAllAction.INSTANCE;
this.replaceAllAction.searchWidget = this;
this.replaceAllAction.label = SearchWidget.REPLACE_ALL_DISABLED_LABEL;
this.replaceActionBar = this._register(new ActionBar(this.replaceContainer));
this.replaceActionBar.push([this.replaceAllAction], { icon: true, label: false });
this.onkeydown(this.replaceActionBar.domNode, (keyboardEvent) => this.onReplaceActionbarKeyDown(keyboardEvent));
this.replaceInputFocusTracker = this._register(dom.trackFocus(this.replaceInput.inputElement));
this._register(this.replaceInputFocusTracker.onDidFocus(() => this.replaceInputBoxFocused.set(true)));
this._register(this.replaceInputFocusTracker.onDidBlur(() => this.replaceInputBoxFocused.set(false)));
}
triggerReplaceAll(): Promise<any> {
this._onReplaceAll.fire();
return Promise.resolve(null);
}
private onToggleReplaceButton(): void {
dom.toggleClass(this.replaceContainer, 'disabled');
dom.toggleClass(this.toggleReplaceButton.element, 'collapse');
dom.toggleClass(this.toggleReplaceButton.element, 'expand');
this.toggleReplaceButton.element.setAttribute('aria-expanded', this.isReplaceShown() ? 'true' : 'false');
this.updateReplaceActiveState();
this._onReplaceToggled.fire();
}
setReplaceAllActionState(enabled: boolean): void {
if (this.replaceAllAction.enabled !== enabled) {
this.replaceAllAction.enabled = enabled;
this.replaceAllAction.label = enabled ? SearchWidget.REPLACE_ALL_ENABLED_LABEL(this.keyBindingService) : SearchWidget.REPLACE_ALL_DISABLED_LABEL;
this.updateReplaceActiveState();
}
}
private updateReplaceActiveState(): void {
const currentState = this.isReplaceActive();
const newState = this.isReplaceShown() && this.replaceAllAction.enabled;
if (currentState !== newState) {
this.replaceActive.set(newState);
this._onReplaceStateChange.fire(newState);
this.replaceInput.layout();
}
}
private validateSearchInput(value: string): IMessage | null {
if (value.length === 0) {
return null;
}
if (!this.searchInput.getRegex()) {
return null;
}
try {
// tslint:disable-next-line: no-unused-expression
new RegExp(value);
} catch (e) {
return { content: e.message };
}
if (strings.regExpContainsBackreference(value)) {
if (!this.searchConfiguration.usePCRE2) {
return { content: nls.localize('regexp.backreferenceValidationFailure', "Backreferences are not supported") };
}
}
return null;
}
private onSearchInputChanged(): void {
this.searchInput.clearMessage();
this.setReplaceAllActionState(false);
}
private onSearchInputKeyDown(keyboardEvent: IKeyboardEvent) {
if (keyboardEvent.equals(KeyCode.Enter)) {
this.submitSearch();
keyboardEvent.preventDefault();
}
else if (keyboardEvent.equals(KeyCode.Escape)) {
this._onSearchCancel.fire();
keyboardEvent.preventDefault();
}
else if (keyboardEvent.equals(KeyCode.Tab)) {
if (this.isReplaceShown()) {
this.replaceInput.focus();
} else {
this.searchInput.focusOnCaseSensitive();
}
keyboardEvent.preventDefault();
}
else if (keyboardEvent.equals(KeyCode.UpArrow)) {
const ta = this.searchInput.domNode.querySelector('textarea');
const isMultiline = !!this.searchInput.getValue().match(/\n/);
if (ta && isMultiline && ta.selectionStart > 0) {
keyboardEvent.stopPropagation();
}
}
else if (keyboardEvent.equals(KeyCode.DownArrow)) {
const ta = this.searchInput.domNode.querySelector('textarea');
const isMultiline = !!this.searchInput.getValue().match(/\n/);
if (ta && isMultiline && ta.selectionEnd < ta.value.length) {
keyboardEvent.stopPropagation();
}
}
}
private onCaseSensitiveKeyDown(keyboardEvent: IKeyboardEvent) {
if (keyboardEvent.equals(KeyMod.Shift | KeyCode.Tab)) {
if (this.isReplaceShown()) {
this.replaceInput.focus();
keyboardEvent.preventDefault();
}
}
}
private onRegexKeyDown(keyboardEvent: IKeyboardEvent) {
if (keyboardEvent.equals(KeyCode.Tab)) {
if (this.isReplaceActive()) {
this.focusReplaceAllAction();
} else {
this._onBlur.fire();
}
keyboardEvent.preventDefault();
}
}
private onReplaceInputKeyDown(keyboardEvent: IKeyboardEvent) {
if (keyboardEvent.equals(KeyCode.Enter)) {
this.submitSearch();
keyboardEvent.preventDefault();
}
else if (keyboardEvent.equals(KeyCode.Tab)) {
this.searchInput.focusOnCaseSensitive();
keyboardEvent.preventDefault();
}
else if (keyboardEvent.equals(KeyMod.Shift | KeyCode.Tab)) {
this.searchInput.focus();
keyboardEvent.preventDefault();
}
else if (keyboardEvent.equals(KeyCode.UpArrow)) {
const ta = this.searchInput.domNode.querySelector('textarea');
if (ta && ta.selectionStart > 0) {
keyboardEvent.stopPropagation();
}
}
else if (keyboardEvent.equals(KeyCode.DownArrow)) {
const ta = this.searchInput.domNode.querySelector('textarea');
if (ta && ta.selectionEnd < ta.value.length) {
keyboardEvent.stopPropagation();
}
}
}
private onReplaceActionbarKeyDown(keyboardEvent: IKeyboardEvent) {
if (keyboardEvent.equals(KeyMod.Shift | KeyCode.Tab)) {
this.focusRegexAction();
keyboardEvent.preventDefault();
}
}
private submitSearch(): void {
this.searchInput.validate();
if (!this.searchInput.inputBox.isInputValid()) {
return;
}
const value = this.searchInput.getValue();
const useGlobalFindBuffer = this.searchConfiguration.globalFindClipboard;
if (value) {
if (useGlobalFindBuffer) {
this.clipboardServce.writeFindText(value);
}
this._onSearchSubmit.fire();
}
}
dispose(): void {
this.setReplaceAllActionState(false);
this.replaceAllAction.searchWidget = null;
this.replaceActionBar = null;
super.dispose();
}
private get searchConfiguration(): ISearchConfigurationProperties {
return this.configurationService.getValue<ISearchConfigurationProperties>('search');
}
}
export function registerContributions() {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: ReplaceAllAction.ID,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE),
primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter,
handler: accessor => {
if (isSearchViewFocused(accessor.get(IViewletService), accessor.get(IPanelService))) {
ReplaceAllAction.INSTANCE.run();
}
}
});
}