├── adoquery.exe ├── README.org ├── helm-locate-windows-search.el └── helm-windows-search.el /adoquery.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/helm-windows-search/master/adoquery.exe -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * Helm Windows Search 2 | ** Introduction 3 | 4 | Helm Windows Search is an interface for accessing [[https://docs.microsoft.com/en-us/windows/desktop/search/windows-search][Windows Desktop Search]] from [[https://github.com/emacs-helm/helm][Emacs-Helm]]. 5 | 6 | ** Install 7 | 8 | 1. Put helm-windows-search.el and adoquery.exe into your load path. 9 | 10 | (adoquery.exe is a simple ADO access wrapper. see:[[https://github.com/misohena/adoquery]]) 11 | 12 | 2. Put the following code in your init.el file. 13 | 14 | #+BEGIN_SRC emacs-lisp 15 | (autoload 'helm-windows-search "helm-windows-search") 16 | (global-set-key (kbd "C-c h w") 'helm-windows-search) 17 | #+END_SRC 18 | 19 | If you want to use Windows Search instead of es.exe in *helm-locate*, follow these steps: 20 | 21 | 1. Put helm-locate-windows-search.el and adoquery.exe into your load path. 22 | 23 | 2. Put the following code in your init.el file. 24 | 25 | #+BEGIN_SRC emacs-lisp 26 | (autoload 'helm-locate-windows-search-setup "helm-locate-windows-search") 27 | (with-eval-after-load "helm-locate" (helm-locate-windows-search-setup)) 28 | #+END_SRC 29 | 30 | Then helm-locate-init function is overwritten and locate command is emulated by Windows Search. 31 | In helm-locate, you can only use locate command format. (ex: ~-b \NAME~) 32 | 33 | If you want to completely replace helm-locate-1 with helm-windows-search-1, you can use the following code instead. 34 | 35 | #+BEGIN_SRC emacs-lisp 36 | (with-eval-after-load "helm-locate" 37 | (fset 'helm-locate-1 38 | (lambda (&optional localdb init from-ff default) 39 | (require 'helm-windows-search) 40 | (helm-windows-search-1 init default))) 41 | #+END_SRC 42 | 43 | You can use helm-windows-search query. (ex: ~file:main date:today author:misohena~ ) 44 | 45 | ** Usage 46 | 47 | M-x helm-windows-search or C-c h w 48 | 49 | ** Example Queries 50 | 51 | filepath or filename or contents 52 | 53 | - hello 54 | - apple orange banana 55 | - "hello world" 56 | - CreateWindow 57 | 58 | author 59 | 60 | - author:misohena 61 | - author:Jhon\ Smith 62 | 63 | title 64 | 65 | - title:hello 66 | 67 | filename or file 68 | 69 | - filename:main.cpp 70 | - ~file:hello.cpp~ 71 | 72 | file extension 73 | 74 | - ext:mp3 75 | - ext:.mp3 76 | - fileext:mp3 77 | 78 | kind 79 | 80 | - kind:folder 81 | - kind:music 82 | (see: [[https://docs.microsoft.com/ja-jp/windows/desktop/properties/props-system-kind][System.Kind | Microsoft Docs]] ) 83 | 84 | size 85 | 86 | - size:>=1000000000 87 | - size:<100000 88 | 89 | date 90 | 91 | - date:today 92 | - date:2018-12-20 93 | - date:2018-12 94 | - date:2018 95 | - date:=2018-12-20 96 | - date:2018-12-1..2019-1-3 97 | - date:2018-12..today 98 | - date:>=2010 date:<2011 99 | 100 | contents 101 | 102 | - contents:hello\ world 103 | -------------------------------------------------------------------------------- /helm-locate-windows-search.el: -------------------------------------------------------------------------------- 1 | ;;; helm-locate-windows-search.el --- helm-locate for Windows Search -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2018 AKIYAMA Kouhei 4 | 5 | ;; Author: AKIYAMA Kouhei 6 | 7 | ;; This program is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;; Override helm-locate-init function for Windows Search 21 | ;; 22 | ;; Usage: 23 | ;; 24 | ;; (autoload 'helm-locate-windows-search-setup "helm-locate-windows-search") 25 | ;; (with-eval-after-load "helm-locate" (helm-locate-windows-search-setup)) 26 | ;; 27 | 28 | ;;; Code: 29 | 30 | 31 | (defcustom helm-locate-adoquery-command nil 32 | "Path to adoquery.exe (See https://github.com/misohena/adoquery )" 33 | :type 'string :group 'helm-locate) 34 | 35 | (defun helm-locate-set-adoquery-command () 36 | "Setup `helm-locate-adoquery-command' if not already defined." 37 | (unless helm-locate-adoquery-command 38 | (let* ((el-path (or load-file-name 39 | (locate-library "helm-locate-windows-search") 40 | (if (and (boundp 'helm-windows-search-adoquery-command) helm-windows-search-adoquery-command) helm-windows-search-adoquery-command))) 41 | (filename "adoquery.exe") 42 | (exe-path (expand-file-name filename (file-name-directory el-path)))) 43 | (setq helm-locate-adoquery-command 44 | (if (file-exists-p exe-path) exe-path filename))))) 45 | 46 | (helm-locate-set-adoquery-command) 47 | 48 | 49 | ;; test: (helm-locate-parse-locate-command-line "-b -c -d AA -dBB --database CC --database=DD -n8 -l 8 --limit=8 word1 -n 8 word2") 50 | (defun helm-locate-parse-locate-command-line (locate-cmdline) 51 | (let* ((args (helm-mm-split-pattern locate-cmdline)) 52 | (options-with-argument '("d" "database" "n" "l" "limit")) ;;regexp? 53 | options words unresolved-opt) 54 | (loop for arg in args 55 | if unresolved-opt do (progn (push (cons unresolved-opt arg) options) (setq unresolved-opt nil)) 56 | else if (string-match 57 | "^-\\([a-zA-Z0-9]\\)\\(.+\\)?\\|^--\\([a-zA-Z0-9-]+\\)\\(=\\(.*\\)\\)?" arg) 58 | do (let ((opt-name (or (match-string 1 arg) (match-string 3 arg))) 59 | (opt-value (or (match-string 2 arg) (match-string 5 arg)))) 60 | (if (and (member opt-name options-with-argument) (null opt-value)) 61 | (setq unresolved-opt opt-name) 62 | (push (cons opt-name opt-value) options))) 63 | else do (push arg words)) 64 | 65 | ;; Replace \s- to space in words 66 | (setq words 67 | (mapcar 68 | (lambda (word) 69 | (if (string-match-p "\\\\s-" word) 70 | (format "\"%s\"" (replace-regexp-in-string "\\\\s-" " " word)) 71 | word)) 72 | words)) 73 | ;; Return cons word and options 74 | (cons (nreverse words) (nreverse options)))) 75 | 76 | 77 | (defun helm-locate-make-windows-search-sql (locate-cmdline &optional scope) 78 | (let* ((words-options (helm-locate-parse-locate-command-line locate-cmdline)) 79 | (words (car words-options)) 80 | (options (cdr words-options)) 81 | (basename-only (or (assoc "b" options) (assoc "basename" options))) 82 | (limit (string-to-number 83 | (or (cdr (find-if (lambda (opt) (member (car opt) '("l" "limit" "n"))) options)) 84 | "100")))) 85 | ;; not supported: 86 | ;; - -i : Windows Search always ignores case (https://docs.microsoft.com/en-us/windows/desktop/search/-search-sql-casesensitivityinsearches) 87 | (concat 88 | "SELECT" 89 | (format " TOP %s" limit) 90 | " System.ItemFolderPathDisplay, System.FileName" 91 | " FROM SystemIndex" 92 | " WHERE " 93 | (if scope (format "SCOPE='file:%s' AND " scope)) ;;https://docs.microsoft.com/en-us/windows/desktop/search/-search-sql-folderdepth 94 | (mapconcat (lambda (word) 95 | ;; Escape single quote 96 | (setq word (replace-regexp-in-string "'" "''" word)) 97 | ;; Exact match ? 98 | (let ((pred (if (string-match "^\\\\\\(.+\\)" word) 99 | (format "= '%s'" (match-string 1 word)) 100 | (format "LIKE '%%%s%%'" word)))) 101 | 102 | (concat 103 | (if (not basename-only) 104 | (format "(System.ItemFolderPathDisplay %s) AND " pred)) 105 | (format "(System.FileName %s)" pred)))) 106 | words " AND ")))) 107 | 108 | (defun helm-locate-make-windows-search-args (locate-cmdline &optional scope) 109 | (list 110 | "/conn" "Provider=Search.CollatorDSO;Extended Properties='Application=Windows';" ;;Connection String 111 | "/format" "%1%\\\\%2%" ;; directory/filename 112 | "/header" "" ;; no header 113 | "/query" (helm-locate-make-windows-search-sql locate-cmdline scope))) 114 | 115 | 116 | (defun helm-locate-init-for-windows-search () 117 | "Replacement of helm-locate-init function for Windows Search." 118 | 119 | (let* ((args (helm-locate-make-windows-search-args (replace-regexp-in-string "\\`[^ ]+ " "" (format helm-locate-command "" helm-pattern)))) 120 | ;; cmd is used for debug only 121 | (cmd (concat helm-locate-adoquery-command " " 122 | (mapconcat 'identity args " ")))) 123 | 124 | (helm-log "Starting helm-locate process") 125 | (helm-log "Command line used was:\n\n%s" 126 | (concat ">>> " (propertize cmd 'face 'font-lock-comment-face) "\n\n")) 127 | (prog1 128 | (apply #'start-process 129 | (append (list "windows-search-process" helm-buffer helm-locate-adoquery-command) 130 | args)) 131 | 132 | ;; Same as helm-locate-init(helm-locate.el) function below 133 | (set-process-sentinel 134 | (get-buffer-process helm-buffer) 135 | (lambda (process event) 136 | (let* ((err (process-exit-status process)) 137 | (noresult (= err 1))) 138 | (cond (noresult 139 | (with-helm-buffer 140 | (unless (cdr helm-sources) 141 | (insert (concat "* Exit with code 1, no result found," 142 | " command line was:\n\n " 143 | cmd))))) 144 | ((string= event "finished\n") 145 | (when (and helm-locate-fuzzy-match 146 | (not (string-match-p "\\s-" helm-pattern))) 147 | (helm-redisplay-buffer)) 148 | (with-helm-window 149 | (setq mode-line-format 150 | '(" " mode-line-buffer-identification " " 151 | (:eval (format "L%s" (helm-candidate-number-at-point))) " " 152 | (:eval (propertize 153 | (format "[Locate process finished - (%s results)]" 154 | (max (1- (count-lines 155 | (point-min) (point-max))) 156 | 0)) 157 | 'face 'helm-locate-finish)))) 158 | (force-mode-line-update))) 159 | (t 160 | (helm-log "Error: Locate %s" 161 | (replace-regexp-in-string "\n" "" event)))))))))) 162 | 163 | 164 | ;;;###autoload 165 | (defun helm-locate-windows-search-setup () 166 | (setq helm-locate-command "locate %s %s") 167 | (fset 'helm-locate-init #'helm-locate-init-for-windows-search)) 168 | ;; ;;(advice-add #'helm-locate-init :override #'helm-locate-init-for-windows-search)) 169 | 170 | 171 | (provide 'helm-locate-windows-search) 172 | ;;; helm-locate-windows-search.el ends here 173 | -------------------------------------------------------------------------------- /helm-windows-search.el: -------------------------------------------------------------------------------- 1 | ;;; helm-windows-search.el --- -*- lexical-binding: t; -*- 2 | ;;; helm interface for windows search. 3 | 4 | ;; Copyright (C) 2018 AKIYAMA Kouhei 5 | 6 | ;; This program is free software; you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or 9 | ;; (at your option) any later version. 10 | 11 | ;; This program is distributed in the hope that it will be useful, 12 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | ;; GNU General Public License for more details. 15 | 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with this program. If not, see . 18 | 19 | ;;; Code: 20 | 21 | (require 'cl-lib) 22 | (require 'helm) 23 | (require 'helm-types) 24 | (require 'helm-help) 25 | 26 | 27 | (defgroup helm-windows-search nil 28 | "Windows Search for Helm." 29 | :group 'helm) 30 | 31 | (defcustom helm-windows-search-adoquery-command nil 32 | "Path to adoquery.exe (See https://github.com/misohena/adoquery )" 33 | :type 'string :group 'helm-windows-search) 34 | 35 | (defun helm-windows-search-set-command () 36 | "Setup `helm-windows-search-adoquery-command' if not already defined." 37 | (unless helm-windows-search-adoquery-command 38 | (let* ((el-path (or load-file-name (locate-library "helm-windows-search"))) 39 | (filename "adoquery.exe") 40 | (exe-path (expand-file-name filename (file-name-directory el-path)))) 41 | (setq helm-windows-search-adoquery-command 42 | (if (file-exists-p exe-path) exe-path filename))))) 43 | (helm-windows-search-set-command) 44 | 45 | 46 | (defvar helm-windows-search-map 47 | (let ((map (make-sparse-keymap))) 48 | (set-keymap-parent map helm-generic-files-map) 49 | (define-key map (kbd "DEL") 'helm-delete-backward-no-update) 50 | map)) 51 | 52 | 53 | (defface helm-windows-search-finish 54 | '((t (:foreground "Green"))) 55 | "Face used in mode line when windows-search process is finish." 56 | :group 'helm-windows-search) 57 | 58 | 59 | 60 | ;;; 61 | ;;; Query 62 | ;;; 63 | 64 | ;; tokenize pattern string 65 | ;; 66 | ;; " aaa bbb ccc " => ("aaa" "bbb" "ccc") 67 | ;; " \"aaa bbb\" " => ("\"aaa bbb\"") 68 | ;; " \"aaa bbb" => ("\"aaa bbb\"") ;;EndOfString => " 69 | ;; 70 | ;; Escape white space: 71 | ;; "aaa\\ bbb ccc" => ("aaa bbb" "ccc") ;;escape space in word 72 | ;; "\"aaa\\ bbb\" ccc" => ("\"aaa bbb\"" "ccc") ;;escape space in phrase 73 | ;; "\"aaa bbb\" ccc" => ("\"aaa bbb\"" "ccc") 74 | ;; 75 | ;; Escape double quotation mark: 76 | ;; " aaa \\\"bbb ccc" => ("aaa" "\"bbb" "ccc") 77 | ;; " \"aaa \\\"bbb\" ccc" => ("\"aaa \"bbb\"" "ccc") ;;@todo 78 | ;; 79 | ;; WHITESPACE ::= | \f | \t | \n | \r | \v 80 | ;; ESCAPESEQ ::= \ ( " | WHITESPACE ) 81 | ;; quoted ::= " ( ESCAPESEQ | \ | [^\"] )* ( " | EOS ) 82 | ;; word ::= ( ESCAPESEQ | \ | [^\ WHITESPACE] )* 83 | ;; spaces ::= WHITESPACE* 84 | ;; pattern ::= spaces ((quoted | word) spaces)* 85 | ;; 86 | (defun helm-windows-search-split-pattern (pattern) 87 | (let* ((pos 0) (len (length pattern)) 88 | (state 'spaces) 89 | char-list 90 | args 91 | (start (lambda (next-state) (setq state next-state))) 92 | (add (lambda (char) (push char char-list))) 93 | (end (lambda () (push (apply #'string (nreverse char-list)) args) (setq char-list nil) (setq state 'spaces))) 94 | curr-char next-char) 95 | (while (< pos len) 96 | (setq curr-char (elt pattern pos)) 97 | (setq next-char (if (< (1+ pos) len ) (elt pattern (1+ pos)))) 98 | 99 | (case state 100 | ;; Skip white spaces 101 | (spaces 102 | (case curr-char 103 | ((? ?\f ?\t ?\n ?\r ?\v) (incf pos 1)) 104 | (?\" (funcall start 'quoted) (funcall add curr-char) (incf pos 1)) 105 | (otherwise (funcall start 'word)))) 106 | 107 | ;; Quoted phrase 108 | (quoted 109 | (case curr-char 110 | (?\" (funcall add curr-char) (incf pos 1) (funcall end)) 111 | (?\\ 112 | (case next-char 113 | ((?\" ? ?\f ?\t ?\n ?\r ?\v) (funcall add next-char) (incf pos 2)) 114 | (otherwise (funcall add curr-char) (incf pos 1)))) 115 | (otherwise (funcall add curr-char) (incf pos 1)))) 116 | 117 | ;; Word 118 | (word 119 | (case curr-char 120 | ((? ?\f ?\t ?\n ?\r ?\v) (funcall end)) 121 | (?\\ 122 | (case next-char 123 | ((?\" ? ?\f ?\t ?\n ?\r ?\v) (funcall add next-char) (incf pos 2)) 124 | (otherwise (funcall add curr-char) (incf pos 1)))) 125 | (otherwise (funcall add curr-char) (incf pos 1)))))) 126 | 127 | ;; End Of String 128 | (case state 129 | (quoted (funcall add ?\") (funcall end)) 130 | (word (funcall end))) 131 | 132 | (nreverse args))) 133 | 134 | ;; Query Date 135 | 136 | (defun helm-windows-search-make-date-range (dayname year month day) 137 | "Calculate lower & upper bound of date. 138 | 139 | ex: 140 | (nil 2018 10 19) => ((enc 2018 10 19) . (enc 2018 10 20)) 141 | (nil 2018 10 nil) => ((enc 2018 10 1) . (enc 2018 11 1)) 142 | (nil 2018 nil nil) => ((enc 2018 1 1) . (enc 2019 1 1)) 143 | 144 | *(enc y m d) = (encode-time 0 0 0 d m y)" 145 | (if (stringp year) (setq year (string-to-number year))) 146 | (if (stringp month) (setq month (string-to-number month))) 147 | (if (stringp day) (setq day (string-to-number day))) 148 | 149 | (if (and (stringp dayname) (string= dayname "today")) 150 | (let ((now (decode-time (current-time)))) 151 | (setq year (nth 5 now)) 152 | (setq month (nth 4 now)) 153 | (setq day (nth 3 now)))) 154 | 155 | (let ((range 156 | (cond 157 | (day (list year month day year month (1+ day))) 158 | (month (list year month 1 year (1+ month) 1)) 159 | (year (list year 1 1 (1+ year) 1 1))))) 160 | (if range 161 | ;; convert to time & normalize date (2018/13/32 => (23635 3440)) 162 | (cons 163 | (encode-time 0 0 0 (nth 2 range) (nth 1 range) (nth 0 range)) 164 | (encode-time 0 0 0 (nth 5 range) (nth 4 range) (nth 3 range)))))) 165 | 166 | (defun helm-windows-search-match-string (n str matched-data) 167 | (let ((from (nth (* n 2) matched-data)) 168 | (to (nth (1+ (* n 2)) matched-data))) 169 | (if (and from to) (substring str from to)))) 170 | 171 | (defvar helm-windows-search-query-date-regexp 172 | (let* ((date-regexp "\\(\\(today\\)\\|\\(\\([0-9][0-9][0-9][0-9]\\)\\([/-]\\([0-9][0-9]?\\)\\([/-]\\([0-9][0-9]?\\)\\)?\\)?\\)\\)") 173 | (expr-regexp 174 | (format "\\(\\(\\(<\\|<=\\|=\\|>\\|>=\\)?%s\\)\\|\\(%s\\.\\.%s\\)\\) *$" 175 | date-regexp date-regexp date-regexp))) 176 | (concat "^date:" expr-regexp))) 177 | 178 | (defun helm-windows-search-parse-date (word &optional matched-data) 179 | "Parse date query string. 180 | 181 | ex: 182 | date:today 183 | date:2018-12-19 => ((\">=\" (enc 2018 12 19)) (\"<\" (enc 2018 12 20))) 184 | date:2018-12 => ((\">=\" (enc 2018 12 1)) (\"<\" (enc 2019 1 1))) 185 | date:2018 => ((\">=\" (enc 2018 1 1)) (\"<\" (enc 2019 1 1))) 186 | date:>=2018-12-19 => ((\">=\" (enc 2018 12 19))) 187 | date:>2018-12-19 => ((\">=\" (enc 2018 12 20))) 188 | date: ((\">=\" (enc 2018 11 3)) (\"<\" (enc 2019 12 24))) 190 | date:2018-11..2019 => ((\">=\" (enc 2018 11 1)) (\"<\" (enc 2020 1 1))) 191 | date:2018-11..today 192 | 193 | * (enc y m d) = (encode-time 0 0 0 d m y)" 194 | (if (null matched-data) 195 | (setq matched-data (if (string-match helm-windows-search-query-date-regexp word) (match-data)))) 196 | 197 | (if matched-data 198 | (let ((unary-op (helm-windows-search-match-string 2 word matched-data)) 199 | (op (helm-windows-search-match-string 3 word matched-data)) ;; < <= = > >= or nil 200 | (op-dayname (helm-windows-search-match-string 5 word matched-data)) 201 | (op-year (helm-windows-search-match-string 7 word matched-data)) 202 | (op-month (helm-windows-search-match-string 9 word matched-data)) 203 | (op-day (helm-windows-search-match-string 11 word matched-data)) 204 | (binary-op (helm-windows-search-match-string 12 word matched-data)) 205 | (start-dayname (helm-windows-search-match-string 14 word matched-data)) 206 | (start-year (helm-windows-search-match-string 16 word matched-data)) 207 | (start-month (helm-windows-search-match-string 18 word matched-data)) 208 | (start-day (helm-windows-search-match-string 20 word matched-data)) 209 | (end-dayname (helm-windows-search-match-string 22 word matched-data)) 210 | (end-year (helm-windows-search-match-string 24 word matched-data)) 211 | (end-month (helm-windows-search-match-string 26 word matched-data)) 212 | (end-day (helm-windows-search-match-string 28 word matched-data))) 213 | 214 | (cond 215 | (unary-op 216 | (let ((date-range (helm-windows-search-make-date-range op-dayname op-year op-month op-day))) 217 | (cond 218 | ((or (null op) (string= op "=")) 219 | (list (cons ">=" (car date-range)) (cons "<" (cdr date-range)))) 220 | ((member op '("<" ">=")) 221 | (list (cons op (car date-range)))) 222 | ((member op '(">" "<=")) 223 | (list (cons op (cdr date-range))))))) 224 | (binary-op 225 | (let ((start-date-range (helm-windows-search-make-date-range start-dayname start-year start-month start-day)) 226 | (end-date-range (helm-windows-search-make-date-range end-dayname end-year end-month end-day))) 227 | (list (cons ">=" (car start-date-range)) (cons "<" (cdr end-date-range))))))))) 228 | 229 | ;; 230 | 231 | (defun helm-windows-search-escape-single-quote (value) 232 | ;; https://docs.microsoft.com/en-us/windows/desktop/search/-search-sql-literals 233 | (replace-regexp-in-string "'" "''" value nil t)) 234 | 235 | (defun helm-windows-search-escape-like (value) 236 | ;; https://docs.microsoft.com/en-us/windows/desktop/search/-search-sql-like 237 | (replace-regexp-in-string "[%_[]" "[\\&]" value)) 238 | 239 | (defvar helm-windows-search-query-sql-map 240 | `( 241 | ;; https://docs.microsoft.com/ja-jp/windows/desktop/lwef/-search-2x-wds-aqsreference#properties-by-file-kind 242 | ;; ( regexp sql-generator ) 243 | ("^\\(filename\\|file\\):\\(.+\\)" (lambda (word) (format "(System.FileName Like '%%%s%%')" (helm-windows-search-escape-single-quote (match-string 2 word))))) 244 | ("^\\(fileext\\|ext\\):\\.?\\(.+\\)" (lambda (word) (format "(System.FileExtension = '.%s')" (helm-windows-search-escape-single-quote (match-string 2 word))))) 245 | ("^kind:\\(.+\\)" (lambda (word) (format "(System.Kind = '%s')" (helm-windows-search-escape-single-quote (match-string 1 word))))) ;;https://docs.microsoft.com/ja-jp/windows/desktop/properties/props-system-kind 246 | ("^author:\\(.+\\)" (lambda (word) (format "(System.Author = '%s')" (helm-windows-search-escape-single-quote (match-string 1 word))))) 247 | ("^title:\\(.+\\)" (lambda (word) (format "(System.Title Like '%%%s%%')" (helm-windows-search-escape-single-quote (match-string 1 word))))) 248 | ("^contents:\\(.+\\)" (lambda (word) (format "FREETEXT('%s')" (helm-windows-search-escape-single-quote (match-string 1 word))))) 249 | ("^size:\\(<\\|<=\\|=\\|>\\|>=\\|!=\\)?\\([0-9]+\\)" (lambda (word) (format "(System.Size %s %s)" (or (match-string 1 word) "=") (match-string 2 word)))) 250 | (,helm-windows-search-query-date-regexp 251 | (lambda (word) 252 | (let ((date-conds (helm-windows-search-parse-date word (match-data)))) 253 | (if date-conds 254 | (mapconcat (lambda (op-date) (format "(System.DateModified %s '%s')" 255 | (car op-date) 256 | (format-time-string "%Y-%m-%d %T" (cdr op-date) "UTC0"))) 257 | date-conds 258 | " AND "))))) 259 | (t (lambda (word) 260 | (concat 261 | "(" 262 | "(System.FileName Like '%" (helm-windows-search-escape-single-quote (helm-windows-search-escape-like word)) "%')" 263 | " OR " 264 | "(System.ItemFolderPathDisplay Like '%" (helm-windows-search-escape-single-quote (helm-windows-search-escape-like word)) "%')" 265 | " OR " 266 | "FREETEXT('" (helm-windows-search-escape-single-quote word) "')" 267 | ")"))))) 268 | 269 | 270 | 271 | (defun helm-windows-search-make-sql (pattern &optional scope) 272 | ;; https://docs.microsoft.com/en-us/windows/desktop/search/-search-sql-windowssearch-entry 273 | (let* ((words (helm-windows-search-split-pattern pattern)) 274 | (where 275 | (concat 276 | (if scope (format "SCOPE='file:%s' AND " scope)) ;;https://docs.microsoft.com/en-us/windows/desktop/search/-search-sql-folderdepth 277 | (mapconcat 278 | (lambda (word) 279 | (loop for query-type in helm-windows-search-query-sql-map 280 | do (let ((regexp (nth 0 query-type)) 281 | (generator (nth 1 query-type))) 282 | (if (or (eq regexp t) 283 | (string-match regexp word)) 284 | (return (funcall generator word)))))) 285 | words " AND ") 286 | ))) 287 | 288 | (concat 289 | "SELECT" 290 | " TOP 100" 291 | " System.ItemFolderPathDisplay, System.FileName" ;;%1% %2% 292 | " FROM SystemIndex" 293 | " WHERE " where))) 294 | 295 | 296 | 297 | ;;; 298 | ;;; Execute 299 | ;;; 300 | 301 | (defun helm-windows-search-make-command-args (pattern &optional scope) 302 | (list 303 | "/conn" "Provider=Search.CollatorDSO;Extended Properties='Application=Windows';" ;;Connection String 304 | "/format" "%1%\\\\%2%" ;; directory/filename 305 | "/header" "" ;; no header 306 | "/query" (helm-windows-search-make-sql pattern scope))) 307 | 308 | (defun helm-windows-search-source-init () 309 | (require 'helm-for-files)) 310 | 311 | (defun helm-windows-search-candidates-process () 312 | "Initialize async Windows Search process for `helm-source-windows-search'." 313 | (let* ((args (helm-windows-search-make-command-args helm-pattern)) 314 | (cmd (concat helm-windows-search-adoquery-command " " 315 | (mapconcat 'identity args " ")))) 316 | (helm-log "Starting helm-windows-search process") 317 | (helm-log "Command line used was:\n\n%s" 318 | (concat ">>> " (propertize cmd 'face 'font-lock-comment-face) "\n\n")) 319 | (prog1 320 | (apply #'start-process 321 | (append (list "windows-search-process" helm-buffer helm-windows-search-adoquery-command) 322 | args)) 323 | (set-process-sentinel 324 | (get-buffer-process helm-buffer) 325 | (lambda (process event) 326 | (let* ((err (process-exit-status process)) 327 | (noresult (= err 1))) 328 | (cond (noresult 329 | (with-helm-buffer 330 | (unless (cdr helm-sources) 331 | (insert (concat "* Exit with code 1, no result found," 332 | " command line was:\n\n " 333 | cmd))))) 334 | ((string= event "finished\n") 335 | (with-helm-window 336 | (setq mode-line-format 337 | '(" " mode-line-buffer-identification " " 338 | (:eval (format "L%s" (helm-candidate-number-at-point))) " " 339 | (:eval (propertize 340 | (format "[Windows Search process finished - (%s results)]" 341 | (max (1- (count-lines 342 | (point-min) (point-max))) 343 | 0)) 344 | 'face 'helm-windows-search-finish)))) 345 | (force-mode-line-update))) 346 | (t 347 | (helm-log "Error: windows-search %s" 348 | (replace-regexp-in-string "\n" "" event)))))))))) 349 | 350 | (defvar helm-windows-search-file-name-history nil) 351 | 352 | (defclass helm-windows-search-override-inheritor (helm-type-file) ()) 353 | 354 | (defclass helm-windows-search-source (helm-source-async helm-windows-search-override-inheritor) 355 | ((init :initform 'helm-windows-search-source-init) 356 | (candidates-process :initform 'helm-windows-search-candidates-process) 357 | (requires-pattern :initform 3) 358 | (history :initform 'helm-windows-search-file-name-history) 359 | (persistent-action :initform 'helm-ff-kill-or-find-buffer-fname) 360 | (candidate-number-limit :initform 9999) 361 | (group :initform 'helm-windows-search))) 362 | 363 | ;; Override helm-type-file class keymap. 364 | (defmethod helm--setup-source :after ((source helm-windows-search-override-inheritor)) 365 | (setf (slot-value source 'keymap) helm-windows-search-map)) 366 | 367 | (defvar helm-source-windows-search 368 | (helm-make-source "Windows Search" 'helm-windows-search-source)) 369 | 370 | 371 | (defun helm-windows-search-1 (&optional initial-input default) 372 | "Run Windows Search." 373 | (require 'helm-mode) 374 | ;;(setq helm-windows-search-file-name-history (mapcar 'helm-basename file-name-history)) 375 | (helm :sources 'helm-source-windows-search 376 | :buffer "*helm windows-search*" 377 | :ff-transformer-show-only-basename nil 378 | :input initial-input 379 | :default default 380 | :history 'helm-windows-search-file-name-history)) 381 | 382 | ;;;###autoload 383 | (defun helm-windows-search () 384 | "Preconfigured `helm' for Windows Search." 385 | (interactive) 386 | (setq helm-ff-default-directory default-directory) 387 | (helm-windows-search-1 nil (thing-at-point 'filename))) 388 | 389 | (provide 'helm-windows-search) 390 | ;;; helm-windows-search.el ends here 391 | --------------------------------------------------------------------------------