Closes #410 - adds ability to search by multiple criteria Adds ability to match case, match all, & use regexmain
@ -0,0 +1,4 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#fff" fill-opacity=".1" stroke="#C5C5C5" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||||
<path fill="#C5C5C5" fill-rule="evenodd" d="M8 10.75c2.59 0 4.1-1.218 5.233-2.75C12.1 6.468 10.59 5.25 8 5.25S3.9 6.468 2.767 8C3.9 9.532 5.41 10.75 8 10.75zM14.75 8c-1.294 2-3.177 4-6.75 4s-5.456-2-6.75-4C2.544 6 4.427 4 8 4s5.456 2 6.75 4zM8 9.75a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#C5C5C5" fill-rule="evenodd" d="M8 10.75c2.59 0 4.1-1.218 5.233-2.75C12.1 6.468 10.59 5.25 8 5.25S3.9 6.468 2.767 8C3.9 9.532 5.41 10.75 8 10.75zM14.75 8c-1.294 2-3.177 4-6.75 4s-5.456-2-6.75-4C2.544 6 4.427 4 8 4s5.456 2 6.75 4zM8 9.75a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,4 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#fff" fill-opacity=".1" stroke="#C5C5C5" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||||
<path fill="#C5C5C5" d="M5.811 4.914a1.748 1.748 0 0 0 .186.789c.065.128.148.26.248.395.1.131.22.27.36.418.227-.14.424-.27.59-.394.165-.124.302-.25.41-.377a1.248 1.248 0 0 0 .324-.855 1.01 1.01 0 0 0-.075-.395.909.909 0 0 0-.532-.52 1.143 1.143 0 0 0-.434-.078c-.332 0-.594.09-.787.27-.193.175-.29.424-.29.747zm.782 6.123c.223 0 .43-.022.619-.066a2.676 2.676 0 0 0 .978-.472c.139-.108.27-.223.393-.347l-2.24-2.434c-.2.132-.377.261-.532.389a2.244 2.244 0 0 0-.393.4 1.635 1.635 0 0 0-.237.473 1.912 1.912 0 0 0-.081.586c0 .215.032.415.098.598.07.18.168.335.295.467.127.127.284.227.469.299.185.071.395.107.63.107zM4 9.602c0-.311.039-.588.116-.831a2.27 2.27 0 0 1 .335-.664c.15-.2.332-.385.544-.556a7.35 7.35 0 0 1 .735-.508 13.39 13.39 0 0 1-.353-.443 3.563 3.563 0 0 1-.306-.49 3.282 3.282 0 0 1-.209-.557 2.438 2.438 0 0 1-.08-.64c0-.298.047-.565.144-.8.096-.24.235-.441.417-.605.18-.167.405-.293.67-.376.267-.088.57-.132.91-.132.32 0 .605.044.856.132a1.653 1.653 0 0 1 1.047.98c.093.236.14.503.14.802 0 .255-.049.492-.145.711a2.456 2.456 0 0 1-.388.604c-.162.184-.35.355-.561.515a9.266 9.266 0 0 1-.66.448l2.037 2.225a3.257 3.257 0 0 0 .503-.736c.066-.135.124-.281.174-.436.054-.156.1-.327.139-.515h1.065a5.77 5.77 0 0 1-.209.73 4.48 4.48 0 0 1-.272.622c-.1.195-.214.378-.341.55-.124.167-.26.333-.411.496L11.5 11.88h-1.302l-.972-1.028a7.43 7.43 0 0 1-.562.484c-.185.14-.382.26-.59.359A3.578 3.578 0 0 1 6.593 12a3.58 3.58 0 0 1-1.094-.155 2.271 2.271 0 0 1-.816-.467 2.11 2.11 0 0 1-.51-.753A2.817 2.817 0 0 1 4 9.602z"/> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#C5C5C5" d="M5.811 4.914a1.748 1.748 0 0 0 .186.789c.065.128.148.26.248.395.1.131.22.27.36.418.227-.14.424-.27.59-.394.165-.124.302-.25.41-.377a1.248 1.248 0 0 0 .324-.855 1.01 1.01 0 0 0-.075-.395.909.909 0 0 0-.532-.52 1.143 1.143 0 0 0-.434-.078c-.332 0-.594.09-.787.27-.193.175-.29.424-.29.747zm.782 6.123c.223 0 .43-.022.619-.066a2.676 2.676 0 0 0 .978-.472c.139-.108.27-.223.393-.347l-2.24-2.434c-.2.132-.377.261-.532.389a2.244 2.244 0 0 0-.393.4 1.635 1.635 0 0 0-.237.473 1.912 1.912 0 0 0-.081.586c0 .215.032.415.098.598.07.18.168.335.295.467.127.127.284.227.469.299.185.071.395.107.63.107zM4 9.602c0-.311.039-.588.116-.831a2.27 2.27 0 0 1 .335-.664c.15-.2.332-.385.544-.556a7.35 7.35 0 0 1 .735-.508 13.39 13.39 0 0 1-.353-.443 3.563 3.563 0 0 1-.306-.49 3.282 3.282 0 0 1-.209-.557 2.438 2.438 0 0 1-.08-.64c0-.298.047-.565.144-.8.096-.24.235-.441.417-.605.18-.167.405-.293.67-.376.267-.088.57-.132.91-.132.32 0 .605.044.856.132a1.653 1.653 0 0 1 1.047.98c.093.236.14.503.14.802 0 .255-.049.492-.145.711a2.456 2.456 0 0 1-.388.604c-.162.184-.35.355-.561.515a9.266 9.266 0 0 1-.66.448l2.037 2.225a3.257 3.257 0 0 0 .503-.736c.066-.135.124-.281.174-.436.054-.156.1-.327.139-.515h1.065a5.77 5.77 0 0 1-.209.73 4.48 4.48 0 0 1-.272.622c-.1.195-.214.378-.341.55-.124.167-.26.333-.411.496L11.5 11.88h-1.302l-.972-1.028a7.43 7.43 0 0 1-.562.484c-.185.14-.382.26-.59.359A3.578 3.578 0 0 1 6.593 12a3.58 3.58 0 0 1-1.094-.155 2.271 2.271 0 0 1-.816-.467 2.11 2.11 0 0 1-.51-.753A2.817 2.817 0 0 1 4 9.602z"/> | |||||
</svg> |
@ -0,0 +1,4 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#fff" fill-opacity=".1" stroke="#C5C5C5" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||||
<path fill="#C5C5C5" fill-rule="evenodd" d="M7.328 9.052l.864 2.35H9.25L6.108 3H5.12L2 11.402h1.062l.812-2.35h3.454zM5.695 4.453l.043.135 1.278 3.574h-2.83l1.268-3.574.043-.135.036-.156.031-.152.02-.126h.023l.023.126.028.152.037.156zm7.335 6.011v.936h.96V7.498c0-.719-.18-1.272-.539-1.661-.359-.389-.889-.583-1.588-.583-.199 0-.401.019-.606.056a4.875 4.875 0 0 0-1.078.326 2.081 2.081 0 0 0-.343.188v.984c.266-.23.566-.411.904-.54a2.927 2.927 0 0 1 1.052-.193c.188 0 .358.028.513.085a.98.98 0 0 1 .396.267c.109.121.193.279.252.472.059.193.088.427.088.7l-1.811.252c-.344.047-.64.126-.888.237a1.947 1.947 0 0 0-.615.419 1.6 1.6 0 0 0-.36.58 2.134 2.134 0 0 0-.117.721c0 .246.042.475.124.688.082.213.203.397.363.551.16.154.36.276.598.366.238.09.513.135.826.135.402 0 .76-.092 1.075-.278.315-.186.572-.454.771-.806h.023zm-2.128-1.743c.176-.064.401-.114.674-.149l1.465-.205v.609c0 .246-.041.475-.123.688a1.727 1.727 0 0 1-.343.557 1.573 1.573 0 0 1-.524.372 1.63 1.63 0 0 1-.668.135c-.187 0-.353-.025-.495-.076a1.03 1.03 0 0 1-.357-.211.896.896 0 0 1-.22-.316 1.005 1.005 0 0 1-.076-.393 1.6 1.6 0 0 1 .055-.44.739.739 0 0 1 .202-.334 1.16 1.16 0 0 1 .41-.237z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#C5C5C5" fill-rule="evenodd" d="M7.328 9.052l.864 2.35H9.25L6.108 3H5.12L2 11.402h1.062l.812-2.35h3.454zM5.695 4.453l.043.135 1.278 3.574h-2.83l1.268-3.574.043-.135.036-.156.031-.152.02-.126h.023l.023.126.028.152.037.156zm7.335 6.011v.936h.96V7.498c0-.719-.18-1.272-.539-1.661-.359-.389-.889-.583-1.588-.583-.199 0-.401.019-.606.056a4.875 4.875 0 0 0-1.078.326 2.081 2.081 0 0 0-.343.188v.984c.266-.23.566-.411.904-.54a2.927 2.927 0 0 1 1.052-.193c.188 0 .358.028.513.085a.98.98 0 0 1 .396.267c.109.121.193.279.252.472.059.193.088.427.088.7l-1.811.252c-.344.047-.64.126-.888.237a1.947 1.947 0 0 0-.615.419 1.6 1.6 0 0 0-.36.58 2.134 2.134 0 0 0-.117.721c0 .246.042.475.124.688.082.213.203.397.363.551.16.154.36.276.598.366.238.09.513.135.826.135.402 0 .76-.092 1.075-.278.315-.186.572-.454.771-.806h.023zm-2.128-1.743c.176-.064.401-.114.674-.149l1.465-.205v.609c0 .246-.041.475-.123.688a1.727 1.727 0 0 1-.343.557 1.573 1.573 0 0 1-.524.372 1.63 1.63 0 0 1-.668.135c-.187 0-.353-.025-.495-.076a1.03 1.03 0 0 1-.357-.211.896.896 0 0 1-.22-.316 1.005 1.005 0 0 1-.076-.393 1.6 1.6 0 0 1 .055-.44.739.739 0 0 1 .202-.334 1.16 1.16 0 0 1 .41-.237z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,4 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#fff" fill-opacity=".1" stroke="#C5C5C5" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||||
<path fill="#C5C5C5" fill-rule="evenodd" d="M10.012 2h.976v3.113l2.56-1.557.486.885L11.47 6l2.564 1.559-.485.885-2.561-1.557V10h-.976V6.887l-2.56 1.557-.486-.885L9.53 6 6.966 4.441l.485-.885 2.561 1.557V2zM2 10h4v4H2v-4z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#C5C5C5" fill-rule="evenodd" d="M10.012 2h.976v3.113l2.56-1.557.486.885L11.47 6l2.564 1.559-.485.885-2.561-1.557V10h-.976V6.887l-2.56 1.557-.486-.885L9.53 6 6.966 4.441l.485-.885 2.561 1.557V2zM2 10h4v4H2v-4z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,4 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#000" fill-opacity=".1" stroke="#424242" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||||
<path fill="#424242" fill-rule="evenodd" d="M8 10.75c2.59 0 4.1-1.218 5.233-2.75C12.1 6.468 10.59 5.25 8 5.25S3.9 6.468 2.767 8C3.9 9.532 5.41 10.75 8 10.75zM14.75 8c-1.294 2-3.177 4-6.75 4s-5.456-2-6.75-4C2.544 6 4.427 4 8 4s5.456 2 6.75 4zM8 9.75a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#424242" fill-rule="evenodd" d="M8 10.75c2.59 0 4.1-1.218 5.233-2.75C12.1 6.468 10.59 5.25 8 5.25S3.9 6.468 2.767 8C3.9 9.532 5.41 10.75 8 10.75zM14.75 8c-1.294 2-3.177 4-6.75 4s-5.456-2-6.75-4C2.544 6 4.427 4 8 4s5.456 2 6.75 4zM8 9.75a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,4 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#000" fill-opacity=".1" stroke="#424242" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||||
<path fill="#424242" d="M5.811 4.914a1.748 1.748 0 0 0 .186.789c.065.128.148.26.248.395.1.131.22.27.36.418.227-.14.424-.27.59-.394.165-.124.302-.25.41-.377a1.248 1.248 0 0 0 .324-.855 1.01 1.01 0 0 0-.075-.395.909.909 0 0 0-.532-.52 1.143 1.143 0 0 0-.434-.078c-.332 0-.594.09-.787.27-.193.175-.29.424-.29.747zm.782 6.123c.223 0 .43-.022.619-.066a2.676 2.676 0 0 0 .978-.472c.139-.108.27-.223.393-.347l-2.24-2.434c-.2.132-.377.261-.532.389a2.244 2.244 0 0 0-.393.4 1.635 1.635 0 0 0-.237.473 1.912 1.912 0 0 0-.081.586c0 .215.032.415.098.598.07.18.168.335.295.467.127.127.284.227.469.299.185.071.395.107.63.107zM4 9.602c0-.311.039-.588.116-.831a2.27 2.27 0 0 1 .335-.664c.15-.2.332-.385.544-.556a7.35 7.35 0 0 1 .735-.508 13.39 13.39 0 0 1-.353-.443 3.563 3.563 0 0 1-.306-.49 3.282 3.282 0 0 1-.209-.557 2.438 2.438 0 0 1-.08-.64c0-.298.047-.565.144-.8.096-.24.235-.441.417-.605.18-.167.405-.293.67-.376.267-.088.57-.132.91-.132.32 0 .605.044.856.132a1.653 1.653 0 0 1 1.047.98c.093.236.14.503.14.802 0 .255-.049.492-.145.711a2.456 2.456 0 0 1-.388.604c-.162.184-.35.355-.561.515a9.266 9.266 0 0 1-.66.448l2.037 2.225a3.257 3.257 0 0 0 .503-.736c.066-.135.124-.281.174-.436.054-.156.1-.327.139-.515h1.065a5.77 5.77 0 0 1-.209.73 4.48 4.48 0 0 1-.272.622c-.1.195-.214.378-.341.55-.124.167-.26.333-.411.496L11.5 11.88h-1.302l-.972-1.028a7.43 7.43 0 0 1-.562.484c-.185.14-.382.26-.59.359A3.578 3.578 0 0 1 6.593 12a3.58 3.58 0 0 1-1.094-.155 2.271 2.271 0 0 1-.816-.467 2.11 2.11 0 0 1-.51-.753A2.817 2.817 0 0 1 4 9.602z"/> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#424242" d="M5.811 4.914a1.748 1.748 0 0 0 .186.789c.065.128.148.26.248.395.1.131.22.27.36.418.227-.14.424-.27.59-.394.165-.124.302-.25.41-.377a1.248 1.248 0 0 0 .324-.855 1.01 1.01 0 0 0-.075-.395.909.909 0 0 0-.532-.52 1.143 1.143 0 0 0-.434-.078c-.332 0-.594.09-.787.27-.193.175-.29.424-.29.747zm.782 6.123c.223 0 .43-.022.619-.066a2.676 2.676 0 0 0 .978-.472c.139-.108.27-.223.393-.347l-2.24-2.434c-.2.132-.377.261-.532.389a2.244 2.244 0 0 0-.393.4 1.635 1.635 0 0 0-.237.473 1.912 1.912 0 0 0-.081.586c0 .215.032.415.098.598.07.18.168.335.295.467.127.127.284.227.469.299.185.071.395.107.63.107zM4 9.602c0-.311.039-.588.116-.831a2.27 2.27 0 0 1 .335-.664c.15-.2.332-.385.544-.556a7.35 7.35 0 0 1 .735-.508 13.39 13.39 0 0 1-.353-.443 3.563 3.563 0 0 1-.306-.49 3.282 3.282 0 0 1-.209-.557 2.438 2.438 0 0 1-.08-.64c0-.298.047-.565.144-.8.096-.24.235-.441.417-.605.18-.167.405-.293.67-.376.267-.088.57-.132.91-.132.32 0 .605.044.856.132a1.653 1.653 0 0 1 1.047.98c.093.236.14.503.14.802 0 .255-.049.492-.145.711a2.456 2.456 0 0 1-.388.604c-.162.184-.35.355-.561.515a9.266 9.266 0 0 1-.66.448l2.037 2.225a3.257 3.257 0 0 0 .503-.736c.066-.135.124-.281.174-.436.054-.156.1-.327.139-.515h1.065a5.77 5.77 0 0 1-.209.73 4.48 4.48 0 0 1-.272.622c-.1.195-.214.378-.341.55-.124.167-.26.333-.411.496L11.5 11.88h-1.302l-.972-1.028a7.43 7.43 0 0 1-.562.484c-.185.14-.382.26-.59.359A3.578 3.578 0 0 1 6.593 12a3.58 3.58 0 0 1-1.094-.155 2.271 2.271 0 0 1-.816-.467 2.11 2.11 0 0 1-.51-.753A2.817 2.817 0 0 1 4 9.602z"/> | |||||
</svg> |
@ -0,0 +1,4 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#000" fill-opacity=".1" stroke="#424242" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||||
<path fill="#424242" fill-rule="evenodd" d="M7.328 9.052l.864 2.35H9.25L6.108 3H5.12L2 11.402h1.062l.812-2.35h3.454zM5.695 4.453l.043.135 1.278 3.574h-2.83l1.268-3.574.043-.135.036-.156.031-.152.02-.126h.023l.023.126.028.152.037.156zm7.335 6.011v.936h.96V7.498c0-.719-.18-1.272-.539-1.661-.359-.389-.889-.583-1.588-.583-.199 0-.401.019-.606.056a4.875 4.875 0 0 0-1.078.326 2.081 2.081 0 0 0-.343.188v.984c.266-.23.566-.411.904-.54a2.927 2.927 0 0 1 1.052-.193c.188 0 .358.028.513.085a.98.98 0 0 1 .396.267c.109.121.193.279.252.472.059.193.088.427.088.7l-1.811.252c-.344.047-.64.126-.888.237a1.947 1.947 0 0 0-.615.419 1.6 1.6 0 0 0-.36.58 2.134 2.134 0 0 0-.117.721c0 .246.042.475.124.688.082.213.203.397.363.551.16.154.36.276.598.366.238.09.513.135.826.135.402 0 .76-.092 1.075-.278.315-.186.572-.454.771-.806h.023zm-2.128-1.743c.176-.064.401-.114.674-.149l1.465-.205v.609c0 .246-.041.475-.123.688a1.727 1.727 0 0 1-.343.557 1.573 1.573 0 0 1-.524.372 1.63 1.63 0 0 1-.668.135c-.187 0-.353-.025-.495-.076a1.03 1.03 0 0 1-.357-.211.896.896 0 0 1-.22-.316 1.005 1.005 0 0 1-.076-.393 1.6 1.6 0 0 1 .055-.44.739.739 0 0 1 .202-.334 1.16 1.16 0 0 1 .41-.237z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#424242" fill-rule="evenodd" d="M7.328 9.052l.864 2.35H9.25L6.108 3H5.12L2 11.402h1.062l.812-2.35h3.454zM5.695 4.453l.043.135 1.278 3.574h-2.83l1.268-3.574.043-.135.036-.156.031-.152.02-.126h.023l.023.126.028.152.037.156zm7.335 6.011v.936h.96V7.498c0-.719-.18-1.272-.539-1.661-.359-.389-.889-.583-1.588-.583-.199 0-.401.019-.606.056a4.875 4.875 0 0 0-1.078.326 2.081 2.081 0 0 0-.343.188v.984c.266-.23.566-.411.904-.54a2.927 2.927 0 0 1 1.052-.193c.188 0 .358.028.513.085a.98.98 0 0 1 .396.267c.109.121.193.279.252.472.059.193.088.427.088.7l-1.811.252c-.344.047-.64.126-.888.237a1.947 1.947 0 0 0-.615.419 1.6 1.6 0 0 0-.36.58 2.134 2.134 0 0 0-.117.721c0 .246.042.475.124.688.082.213.203.397.363.551.16.154.36.276.598.366.238.09.513.135.826.135.402 0 .76-.092 1.075-.278.315-.186.572-.454.771-.806h.023zm-2.128-1.743c.176-.064.401-.114.674-.149l1.465-.205v.609c0 .246-.041.475-.123.688a1.727 1.727 0 0 1-.343.557 1.573 1.573 0 0 1-.524.372 1.63 1.63 0 0 1-.668.135c-.187 0-.353-.025-.495-.076a1.03 1.03 0 0 1-.357-.211.896.896 0 0 1-.22-.316 1.005 1.005 0 0 1-.076-.393 1.6 1.6 0 0 1 .055-.44.739.739 0 0 1 .202-.334 1.16 1.16 0 0 1 .41-.237z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,4 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#000" fill-opacity=".1" stroke="#424242" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||||
<path fill="#424242" fill-rule="evenodd" d="M10.012 2h.976v3.113l2.56-1.557.486.885L11.47 6l2.564 1.559-.485.885-2.561-1.557V10h-.976V6.887l-2.56 1.557-.486-.885L9.53 6 6.966 4.441l.485-.885 2.561 1.557V2zM2 10h4v4H2v-4z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||||
<path fill="#424242" fill-rule="evenodd" d="M10.012 2h.976v3.113l2.56-1.557.486.885L11.47 6l2.564 1.559-.485.885-2.561-1.557V10h-.976V6.887l-2.56 1.557-.486-.885L9.53 6 6.966 4.441l.485-.885 2.561 1.557V2zM2 10h4v4H2v-4z" clip-rule="evenodd"/> | |||||
</svg> |
@ -0,0 +1,497 @@ | |||||
'use strict'; | |||||
/* eslint-disable no-loop-func */ | |||||
import { QuickInputButton } from 'vscode'; | |||||
import { Container } from '../../container'; | |||||
import { GitLogCommit, GitService, Repository } from '../../git/gitService'; | |||||
import { GlyphChars } from '../../constants'; | |||||
import { QuickCommandBase, StepAsyncGenerator, StepSelection, StepState } from '../quickCommand'; | |||||
import { RepositoryQuickPickItem } from '../../quickpicks'; | |||||
import { Iterables, Mutable, Strings } from '../../system'; | |||||
import { Logger } from '../../logger'; | |||||
import { | |||||
CommitQuickPickItem, | |||||
Directive, | |||||
DirectiveQuickPickItem, | |||||
QuickPickItemOfT | |||||
} from '../../quickpicks/gitQuickPicks'; | |||||
interface State { | |||||
repo: Repository; | |||||
search: string; | |||||
matchAll: boolean; | |||||
matchCase: boolean; | |||||
matchRegex: boolean; | |||||
showInView: boolean; | |||||
} | |||||
export interface SearchGitCommandArgs { | |||||
readonly command: 'search'; | |||||
state?: Partial<State>; | |||||
confirm?: boolean; | |||||
prefillOnly?: boolean; | |||||
} | |||||
const searchOperators = new Set<string>(['', 'author:', 'change:', 'commit:', 'file:']); | |||||
const searchOperatorToTitleMap = new Map<string, string>([ | |||||
['', 'Search by Message'], | |||||
['author:', 'Search by Author or Committer'], | |||||
['change:', 'Search by Changes'], | |||||
['commit:', 'Search by Commit ID'], | |||||
['file:', 'Search by File'] | |||||
]); | |||||
export class SearchGitCommand extends QuickCommandBase<State> { | |||||
constructor(args?: SearchGitCommandArgs) { | |||||
super('search', 'search', 'Search', { | |||||
description: 'aka grep, searches for commits' | |||||
}); | |||||
if (args == null || args.state === undefined) return; | |||||
let counter = 0; | |||||
if (args.state.repo !== undefined) { | |||||
counter++; | |||||
} | |||||
if (args.state.search !== undefined && !args.prefillOnly) { | |||||
counter++; | |||||
} | |||||
this._initialState = { | |||||
counter: counter, | |||||
confirm: args.confirm, | |||||
...args.state | |||||
}; | |||||
} | |||||
get canConfirm(): boolean { | |||||
return false; | |||||
} | |||||
isMatch(name: string) { | |||||
return super.isMatch(name) || name === 'grep'; | |||||
} | |||||
protected async *steps(): StepAsyncGenerator { | |||||
const state: StepState<State> = this._initialState === undefined ? { counter: 0 } : this._initialState; | |||||
let oneRepo = false; | |||||
let pickedCommit: GitLogCommit | undefined; | |||||
const cfg = Container.config.gitCommands.search; | |||||
if (state.matchAll === undefined) { | |||||
state.matchAll = cfg.matchAll; | |||||
} | |||||
if (state.matchCase === undefined) { | |||||
state.matchCase = cfg.matchCase; | |||||
} | |||||
if (state.matchRegex === undefined) { | |||||
state.matchRegex = cfg.matchRegex; | |||||
} | |||||
if (state.showInView === undefined) { | |||||
state.showInView = cfg.showInView; | |||||
} | |||||
while (true) { | |||||
try { | |||||
if (state.repo === undefined || state.counter < 1) { | |||||
const repos = [...(await Container.git.getOrderedRepositories())]; | |||||
if (repos.length === 1) { | |||||
oneRepo = true; | |||||
state.counter++; | |||||
state.repo = repos[0]; | |||||
} else { | |||||
const active = state.repo ? state.repo : await Container.git.getActiveRepository(); | |||||
const step = this.createPickStep<RepositoryQuickPickItem>({ | |||||
multiselect: true, | |||||
title: this.title, | |||||
placeholder: 'Choose repositories', | |||||
items: await Promise.all( | |||||
repos.map(r => | |||||
RepositoryQuickPickItem.create(r, r.id === (active && active.id), { | |||||
branch: true, | |||||
fetched: true, | |||||
status: true | |||||
}) | |||||
) | |||||
) | |||||
}); | |||||
const selection: StepSelection<typeof step> = yield step; | |||||
if (!this.canPickStepMoveNext(step, state, selection)) { | |||||
break; | |||||
} | |||||
state.repo = selection[0].item; | |||||
} | |||||
} | |||||
if (state.search === undefined || state.counter < 2) { | |||||
const items: QuickPickItemOfT<string>[] = [ | |||||
{ | |||||
label: `${this.title} by Message`, | |||||
description: `pattern ${GlyphChars.Dash} use quotes to search for phrases`, | |||||
item: '' | |||||
}, | |||||
{ | |||||
label: `${this.title} by Author or Committer`, | |||||
description: 'author: pattern', | |||||
item: 'author:' | |||||
}, | |||||
{ | |||||
label: `${this.title} by Commit ID`, | |||||
description: 'commit: sha', | |||||
item: 'commit:' | |||||
}, | |||||
{ | |||||
label: `${this.title} by Files`, | |||||
description: 'file: glob', | |||||
item: 'file:' | |||||
}, | |||||
{ | |||||
label: `${this.title} by Changes`, | |||||
description: 'change: pattern', | |||||
item: 'change:' | |||||
} | |||||
]; | |||||
const titleSuffix = `${Strings.pad(GlyphChars.Dot, 2, 2)}${state.repo.formattedName}`; | |||||
const matchCaseButton: Mutable<QuickInputButton> = { | |||||
iconPath: state.matchCase | |||||
? { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-match-case-selected.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-match-case-selected.svg' | |||||
) as any | |||||
} | |||||
: { | |||||
dark: Container.context.asAbsolutePath('images/dark/icon-match-case.svg') as any, | |||||
light: Container.context.asAbsolutePath('images/light/icon-match-case.svg') as any | |||||
}, | |||||
tooltip: 'Match Case' | |||||
}; | |||||
const matchAllButton: Mutable<QuickInputButton> = { | |||||
iconPath: state.matchAll | |||||
? { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-match-all-selected.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-match-all-selected.svg' | |||||
) as any | |||||
} | |||||
: { | |||||
dark: Container.context.asAbsolutePath('images/dark/icon-match-all.svg') as any, | |||||
light: Container.context.asAbsolutePath('images/light/icon-match-all.svg') as any | |||||
}, | |||||
tooltip: 'Match All' | |||||
}; | |||||
const matchRegexButton: Mutable<QuickInputButton> = { | |||||
iconPath: state.matchRegex | |||||
? { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-match-regex-selected.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-match-regex-selected.svg' | |||||
) as any | |||||
} | |||||
: { | |||||
dark: Container.context.asAbsolutePath('images/dark/icon-match-regex.svg') as any, | |||||
light: Container.context.asAbsolutePath('images/light/icon-match-regex.svg') as any | |||||
}, | |||||
tooltip: 'Match using Regular Expressions' | |||||
}; | |||||
const showInViewButton: Mutable<QuickInputButton> = { | |||||
iconPath: state.showInView | |||||
? { | |||||
dark: Container.context.asAbsolutePath('images/dark/icon-eye-selected.svg') as any, | |||||
light: Container.context.asAbsolutePath('images/light/icon-eye-selected.svg') as any | |||||
} | |||||
: { | |||||
dark: Container.context.asAbsolutePath('images/dark/icon-eye.svg') as any, | |||||
light: Container.context.asAbsolutePath('images/light/icon-eye.svg') as any | |||||
}, | |||||
tooltip: 'Show Results in the Search Commits View' | |||||
}; | |||||
const step = this.createPickStep<QuickPickItemOfT<string>>({ | |||||
title: `${this.title}${titleSuffix}`, | |||||
placeholder: 'e.g. "Updates dependencies" author:eamodio', | |||||
matchOnDescription: true, | |||||
matchOnDetail: true, | |||||
additionalButtons: [matchCaseButton, matchAllButton, matchRegexButton, showInViewButton], | |||||
items: items, | |||||
value: state.search, | |||||
onDidAccept: (quickpick): boolean => { | |||||
const pick = quickpick.selectedItems[0]; | |||||
if (!searchOperators.has(pick.item)) return true; | |||||
const value = quickpick.value.trim(); | |||||
if (value.length === 0 || searchOperators.has(value)) { | |||||
quickpick.value = pick.item; | |||||
} else { | |||||
quickpick.value = `${value} ${pick.item}`; | |||||
} | |||||
void step.onDidChangeValue!(quickpick); | |||||
return false; | |||||
}, | |||||
onDidClickButton: (quickpick, button) => { | |||||
if (button === matchCaseButton) { | |||||
state.matchCase = !state.matchCase; | |||||
matchCaseButton.iconPath = state.matchCase | |||||
? { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-match-case-selected.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-match-case-selected.svg' | |||||
) as any | |||||
} | |||||
: { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-match-case.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-match-case.svg' | |||||
) as any | |||||
}; | |||||
return; | |||||
} | |||||
if (button === matchAllButton) { | |||||
state.matchAll = !state.matchAll; | |||||
matchAllButton.iconPath = state.matchAll | |||||
? { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-match-all-selected.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-match-all-selected.svg' | |||||
) as any | |||||
} | |||||
: { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-match-all.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-match-all.svg' | |||||
) as any | |||||
}; | |||||
return; | |||||
} | |||||
if (button === matchRegexButton) { | |||||
state.matchRegex = !state.matchRegex; | |||||
matchRegexButton.iconPath = state.matchRegex | |||||
? { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-match-regex-selected.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-match-regex-selected.svg' | |||||
) as any | |||||
} | |||||
: { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-match-regex.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-match-regex.svg' | |||||
) as any | |||||
}; | |||||
return; | |||||
} | |||||
if (button === showInViewButton) { | |||||
state.showInView = !state.showInView; | |||||
showInViewButton.iconPath = state.showInView | |||||
? { | |||||
dark: Container.context.asAbsolutePath( | |||||
'images/dark/icon-eye-selected.svg' | |||||
) as any, | |||||
light: Container.context.asAbsolutePath( | |||||
'images/light/icon-eye-selected.svg' | |||||
) as any | |||||
} | |||||
: { | |||||
dark: Container.context.asAbsolutePath('images/dark/icon-eye.svg') as any, | |||||
light: Container.context.asAbsolutePath('images/light/icon-eye.svg') as any | |||||
}; | |||||
} | |||||
}, | |||||
onDidChangeValue: (quickpick): boolean => { | |||||
const operations = GitService.parseSearchOperations(quickpick.value.trim()); | |||||
quickpick.title = | |||||
operations.size === 0 || operations.size > 1 | |||||
? `${this.title}${titleSuffix}` | |||||
: `${searchOperatorToTitleMap.get(operations.keys().next().value)!}${titleSuffix}`; | |||||
if (quickpick.value.length === 0) { | |||||
quickpick.items = items; | |||||
} else { | |||||
quickpick.items = [ | |||||
{ | |||||
label: 'Search for commits matching', | |||||
description: quickpick.value, | |||||
item: quickpick.value | |||||
} | |||||
]; | |||||
} | |||||
return true; | |||||
} | |||||
}); | |||||
const selection: StepSelection<typeof step> = yield step; | |||||
if (!this.canPickStepMoveNext(step, state, selection)) { | |||||
if (oneRepo) { | |||||
break; | |||||
} | |||||
continue; | |||||
} | |||||
state.search = selection[0].item.trim(); | |||||
} | |||||
const resultsPromise = Container.git.getLogForSearch(state.repo.path, { | |||||
pattern: state.search, | |||||
matchAll: state.matchAll, | |||||
matchCase: state.matchCase, | |||||
matchRegex: state.matchRegex | |||||
}); | |||||
if (state.showInView) { | |||||
void Container.searchView.search( | |||||
state.repo.path, | |||||
{ | |||||
pattern: state.search, | |||||
matchAll: state.matchAll, | |||||
matchCase: state.matchCase, | |||||
matchRegex: state.matchRegex | |||||
}, | |||||
{ | |||||
label: { label: `commits matching: ${state.search}` } | |||||
}, | |||||
resultsPromise | |||||
); | |||||
break; | |||||
} | |||||
const results = await resultsPromise; | |||||
const openInViewButton: QuickInputButton = { | |||||
iconPath: { | |||||
dark: Container.context.asAbsolutePath('images/dark/icon-link.svg') as any, | |||||
light: Container.context.asAbsolutePath('images/light/icon-link.svg') as any | |||||
}, | |||||
tooltip: 'Open Results in the Search Commits View' | |||||
}; | |||||
const step = this.createPickStep<CommitQuickPickItem>({ | |||||
title: `${this.title}${Strings.pad(GlyphChars.Dot, 2, 2)}${state.repo.formattedName}`, | |||||
placeholder: | |||||
results === undefined | |||||
? `No results for commits matching: ${state.search}` | |||||
: `${Strings.pluralize('result', results.count, { | |||||
number: results.truncated ? `${results.count}+` : undefined | |||||
})} for commits matching: ${state.search}`, | |||||
matchOnDescription: true, | |||||
matchOnDetail: true, | |||||
items: | |||||
results === undefined | |||||
? [ | |||||
DirectiveQuickPickItem.create(Directive.Back, true), | |||||
DirectiveQuickPickItem.create(Directive.Cancel) | |||||
] | |||||
: [ | |||||
...Iterables.map(results.commits.values(), commit => | |||||
CommitQuickPickItem.create( | |||||
commit, | |||||
commit.ref === (pickedCommit && pickedCommit.ref), | |||||
{ compact: true, icon: true } | |||||
) | |||||
) | |||||
], | |||||
additionalButtons: [openInViewButton], | |||||
onDidClickButton: (quickpick, button) => { | |||||
if (button !== openInViewButton) return; | |||||
void Container.searchView.search( | |||||
state.repo!.path, | |||||
{ | |||||
pattern: state.search!, | |||||
matchAll: state.matchAll, | |||||
matchCase: state.matchCase, | |||||
matchRegex: state.matchRegex | |||||
}, | |||||
{ | |||||
label: { label: `commits matching: ${state.search}` } | |||||
}, | |||||
results | |||||
); | |||||
} | |||||
}); | |||||
const selection: StepSelection<typeof step> = yield step; | |||||
if (!this.canPickStepMoveNext(step, state, selection)) { | |||||
continue; | |||||
} | |||||
state.counter--; | |||||
pickedCommit = selection[0].item; | |||||
void Container.searchView.search( | |||||
pickedCommit.repoPath, | |||||
{ pattern: `commit:${pickedCommit.sha}` }, | |||||
{ | |||||
label: { label: `commits matching: commit:${pickedCommit.shortSha}` } | |||||
} | |||||
); | |||||
// const gitCommandArgs: GitCommandsCommandArgs = { | |||||
// command: 'search', | |||||
// state: { ...state } | |||||
// }; | |||||
// const commandArgs: ShowQuickCommitDetailsCommandArgs = { | |||||
// sha: commit.sha, | |||||
// commit: commit, | |||||
// goBackCommand: new CommandQuickPickItem( | |||||
// { | |||||
// label: 'Back', | |||||
// description: '' | |||||
// }, | |||||
// Commands.GitCommands, | |||||
// [gitCommandArgs] | |||||
// ) | |||||
// }; | |||||
// void commands.executeCommand(Commands.ShowQuickCommitDetails, commit.toGitUri(), commandArgs); | |||||
// break; | |||||
} catch (ex) { | |||||
Logger.error(ex, this.title); | |||||
throw ex; | |||||
} | |||||
} | |||||
return undefined; | |||||
} | |||||
} |