├── .gitattributes ├── screenshots ├── howdoyou.gif ├── howdoyou.png └── howdoyou2.gif ├── .gitignore ├── Cask ├── test ├── test-helper.el ├── curl.sh ├── howdoyou-test.el ├── google.0.html ├── google.1.html └── google.2.html ├── README.org ├── TODOs.org └── howdoyou.el /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.el linguist-vendored=false 3 | -------------------------------------------------------------------------------- /screenshots/howdoyou.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thanhvg/emacs-howdoyou/HEAD/screenshots/howdoyou.gif -------------------------------------------------------------------------------- /screenshots/howdoyou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thanhvg/emacs-howdoyou/HEAD/screenshots/howdoyou.png -------------------------------------------------------------------------------- /screenshots/howdoyou2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thanhvg/emacs-howdoyou/HEAD/screenshots/howdoyou2.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled 2 | *.elc 3 | 4 | # Packaging 5 | .cask 6 | 7 | # Backup files 8 | *~ 9 | 10 | # Undo-tree save-files 11 | *.~undo-tree 12 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | ;;(package-file "howdoyou.el") 5 | 6 | (development 7 | (depends-on "f") 8 | (depends-on "ecukes") 9 | (depends-on "ert-runner") 10 | (depends-on "ert-async") 11 | (depends-on "el-mock") 12 | (depends-on "request") 13 | (depends-on "promise")) 14 | -------------------------------------------------------------------------------- /test/test-helper.el: -------------------------------------------------------------------------------- 1 | ;;; test-helper.el --- Helpers for howdoyou-test.el 2 | (require 'f) 3 | (require 'ert-async) 4 | 5 | (defvar root-test-path 6 | (f-dirname (f-this-file))) 7 | 8 | (defvar root-code-path 9 | (f-parent root-test-path)) 10 | 11 | (add-to-list 'load-path root-code-path) 12 | 13 | (require 'howdoyou) 14 | 15 | (defun make-dom-from-file (file) 16 | (with-temp-buffer 17 | (insert-file-contents (concat root-test-path "/" file)) 18 | (libxml-parse-html-region (point-min) (point-max)))) 19 | ;;; test-helper.el ends here 20 | -------------------------------------------------------------------------------- /test/curl.sh: -------------------------------------------------------------------------------- 1 | my_req="https://www.google.com/search?q=quit%20vim%20site%3Astackoverflow.com%20OR%20site%3Astackexchange.com%20OR%20site%3Asuperuser.com%20OR%20site%3Aserverfault.com%20OR%20site%3Aaskubuntu.com&hl=en" 2 | 3 | 4 | my_agent=("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0" 5 | "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100 101 Firefox/22.0" 6 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0" 7 | "Mozilla/5.0 (Windows NT 6.1; rv:11.0) Gecko/20100101 Firefox/11.0") 8 | 9 | # my_agent=("foo" "bar") 10 | 11 | # echo ${my_agent[4]} 12 | 13 | for i in ${!my_agent[@]}; do 14 | echo $i ${my_agent[$i]} 15 | curl -k -X GET $my_req -A "${my_agent[$i]}" > google.$i.html 16 | done 17 | 18 | # curl -k -X GET $my_req -A "${my_agent[0]}" >> google.0.html 19 | # curl -k -X GET $my_req -A $my_agent[1] >> google.1.html 20 | # curl -k -X GET $my_req -A $my_agent[2] >> google.2.html 21 | # curl -k -X GET $my_req -A $my_agent[3] >> google.3.html 22 | # curl -k -X GET $my_req -A $my_agent[4] >> google.4.html 23 | # curl -k -X GET $my_req -A $my_agent[5] >> google.5.html 24 | 25 | 26 | # bellow are not for test but for investigate 27 | my_single_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0" 28 | 29 | for i in {1..5}; do 30 | curl -k -X GET $my_req -A "${my_single_agent}" > google.single.$i.html 31 | done 32 | 33 | 34 | my_chrome_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46 Safari/536.5" 35 | 36 | for i in {1..5}; do 37 | curl -k -X GET $my_req -A "${my_chrome_agent}" > google.chrome.$i.html 38 | done 39 | 40 | my_latest_chrome_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" 41 | 42 | for i in {1..5}; do 43 | curl -k -X GET $my_req -A "${my_latest_chrome_agent}" > google.latest_chrome.$i.html 44 | done 45 | -------------------------------------------------------------------------------- /test/howdoyou-test.el: -------------------------------------------------------------------------------- 1 | ;;; howdoyou-test.el --- Tests for howdoyou 2 | (ert-deftest test/links-from-google-search () 3 | "Should be able to show links" 4 | (dolist (google-file '("google.0.html" "google.1.html" "google.2.html")) 5 | (let* ((dom (make-dom-from-file google-file)) 6 | (result (howdoyou--extract-links-from-google dom))) 7 | (message "%s" result) 8 | (should (listp result))))) 9 | 10 | ;; (ert-deftest test/links-from-google-bot-search () 11 | ;; "Should be able to show links" 12 | ;; (let* ((dom (make-dom-from-file "google2.html")) 13 | ;; (result (howdoyou--extract-links-from-google dom)) ) 14 | ;; (message "%s" result) 15 | ;; (should (listp result)))) 16 | 17 | (ert-deftest-async test/promise-dom (done) 18 | (promise-done 19 | (promise-chain 20 | (howdoyou--promise-dom "https://www.google.com") 21 | (then (lambda (result) 22 | (should (listp result)) 23 | ;; (message "%s" result) 24 | ;; (error "error test") 25 | (funcall done))) 26 | (promise-catch done)))) 27 | ;; (promise-catch (lambda (e) (funcall done e)))))) 28 | 29 | (ert-deftest-async test/promise-curl-dom (done) 30 | (promise-done 31 | (promise-chain 32 | (howdoyou--curl-promise-dom "https://www.google.com") 33 | (then (lambda (result) 34 | (should (listp result)) 35 | ;; (message "%s" result) 36 | (funcall done))) 37 | (promise-catch done)))) 38 | 39 | (ert-deftest-async test/howdoyou-read-so-link (done) 40 | (promise-done 41 | (promise-chain 42 | (howdoyou-read-so-link "https://stackoverflow.com/questions/8425102/how-do-i-load-my-script-into-the-node-js-repl") 43 | (then (lambda (result) 44 | (should (buffer-live-p (get-buffer "*How Do You*"))) 45 | (funcall done))) 46 | (promise-catch done)))) 47 | 48 | (ert-deftest-async test/promise-parsing (done) 49 | (promise-done 50 | (promise-chain 51 | (howdoyou--promise-dom "https://stackoverflow.com/questions/586735/how-can-i-check-if-a-current-buffer-exists-in-emacs") 52 | (then #'howdoyou--promise-so-answer) 53 | (then (lambda (result) 54 | (should (listp result)) 55 | (funcall done))) 56 | (promise-catch done)))) 57 | 58 | ;;; howdoyou-test.el ends here 59 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+STARTUP: align fold hidestars oddeven indent 2 | #+TITLE: Emacs Howdoyou - A package to search and read stackoverflow and its sisters' sites 3 | 4 | [[http://spacemacs.org][file:https://cdn.rawgit.com/syl20bnr/spacemacs/442d025779da2f62fc86c2082703697714db6514/assets/spacemacs-badge.svg]] 5 | 6 | [[file:screenshots/howdoyou.png]] 7 | 8 | * Intro 9 | This package is inspired by howdoi python and howdoi Emacs packages. it searches 10 | your query all across stackoverflow and it's sisters' sites. They are: 11 | - stackoverflow.com 12 | - stackexchange.com 13 | - superuser.com 14 | - serverfault.com 15 | - askubuntu.com 16 | 17 | The result is then showed in an ~org-mode~ buffer. For each result, question and 18 | three answers were showed, but they are collapsed by default except the first 19 | answer. As this package uses Google to get the links, for each query there will 20 | be a dozen of links, the fist link will be used, then users can go to next 21 | link and previous link. The author believes that when searching for solutions it 22 | is important for users to read both questions and answers, so no "quick look" 23 | features such as code only view or code completion are provided. 24 | * Install 25 | MELPA 26 | 27 | [[https://melpa.org/#/howdoyou][file:https://melpa.org/packages/howdoyou-badge.svg]] 28 | 29 | Spacemacs layer: 30 | 31 | https://github.com/thanhvg/spacemacs-eos 32 | 33 | * Dependencies 34 | ~promise~ and ~request~ are required. 35 | User must have ~org-mode~ 9.2 or later installed also. 36 | 37 | * Commands 38 | - ~howdoyou-query:~ prompt for query and do search 39 | - ~howdoyou-next-link:~ go to next link 40 | - ~howdoyou-previous-link:~ go to previous link 41 | - ~howdoyou-go-back-to-first-link:~ go back to first link 42 | - ~howdoyou-reload-link:~ reload link 43 | * Customization 44 | - ~howdoyou-use-curl:~ default is true if ~curl~ is available 45 | - ~howdoyou-number-of-answers:~ maximal number of answers to show, default is 3 46 | - ~howdoyou-switch-to-answer-buffer~: switch to answer buffer if non nil, default is nil 47 | * Use with helm-google-suggest 48 | add this snippet to you config file 49 | #+begin_src elisp 50 | (with-eval-after-load "helm-net" 51 | (push (cons "How Do You" (lambda (candidate) (howdoyou-query candidate))) 52 | helm-google-suggest-actions)) 53 | #+end_src 54 | Now =helm-google-suggest= will pass suggestion to howdoyou-query as default 55 | action. 56 | 57 | Note that =spacemas-eos= has its own faster google suggetion engine provided by 58 | [[https://github.com/thanhvg/emacs-google-suggest][google-suggest]] package. 59 | 60 | * Use with counsel-web-suggest 61 | Requires installed and configured 3rd party package [[https://github.com/mnewt/counsel-web][counsel-web]]. 62 | 63 | add this function definition to your config file 64 | #+begin_src elisp 65 | (defun my/howdoyou-with-suggestions () 66 | "Call `howdoyou-query' with suggestions from `counsel-web-suggest'." 67 | (interactive) 68 | (counsel-web-suggest nil 69 | "How Do You: " 70 | #'counsel-web-suggest--google 71 | (lambda (x) 72 | (howdoyou-query x)))) 73 | #+end_src 74 | Now when calling =my/howdoyou-with-suggestion= (either from ~M-x~ or by [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Changing-Key-Bindings.html][key binding]] of your choice) 75 | it will pass =counsel-web-suggest= suggestions to howdoyou-query. 76 | 77 | * Demo 78 | 79 | [[file:screenshots/howdoyou.gif]] 80 | 81 | 82 | [[file:screenshots/howdoyou2.gif]] 83 | 84 | * Test 85 | ** generate google html files 86 | #+begin_src sh 87 | cd test 88 | bash curl.sh 89 | #+end_src 90 | new google html files are created to run test aganst them. 91 | ** run test 92 | at project root 93 | #+begin_src sh 94 | cask exec ert-runner 95 | #+end_src 96 | * Contributors (in no particular order): 97 | [[https://github.com/dvzubarev][dvzubarev]], [[https://github.com/leothelocust][leothelocust]], [[https://github.com/dickmao][dickmao]], [[https://github.com/AloisJanicek][Alois Janíček]], [[https://github.com/EvanMeek][Evan]], [[https://github.com/Boruch-Baum][Boruch Baum]] 98 | 99 | * Shoutout 100 | - https://github.com/chuntaro/emacs-promise 101 | - https://github.com/tkf/emacs-request 102 | 103 | * References 104 | - https://github.com/gleitz/howdoi 105 | - https://github.com/lockie/emacs-howdoi 106 | - https://github.com/atykhonov/emacs-howdoi 107 | -------------------------------------------------------------------------------- /TODOs.org: -------------------------------------------------------------------------------- 1 | #+STARTUP: align fold hidestars oddeven indent 2 | #+SEQ_TODO: TODO(t) INPROGRESS(i) | DONE(d) CANCELED(c) 3 | * DONE parse SO link 4 | * DONE use async await lib 5 | https://nullprogram.com/blog/2019/03/10/ 6 | this one is hard 7 | use promise.el instead 8 | * DONE pretty print the dom 9 | travese the dom tree and print the code part in side done 10 | 11 | org template and find the code type to highlight 12 | each will have tag use the tag 13 | 14 | * DONE next and previous link 15 | local buffer var 16 | no, just a global state is ok 17 | because you will have a singleton *How Dou You* buffer 18 | * DONE insert link also 19 | done 20 | #+begin_example 21 | 22 | #+end_example 23 | * DONE parse link and retain img 24 | query ubuntu disk partition 25 | 26 | done but not good 27 | now the lines are long and double white spaces 28 | 29 | how about modify the dom directly 30 | * DONE dom is just a tree 31 | walk this dom and if it's an a tag, replace it* dom is just a tree 32 | 33 | (pre nil (code nil int x; x = 10;)) 34 | * DONE manipulate dom all in one 35 | to drop the insert example org 36 | * DONE print 3 answers and scores 37 | * DONE show only first answer, collapse other 38 | * DONE better error handle 39 | uncompressing publicsuffix.txt.gz...done 40 | 41 | * DONE image inside a tag should be ignore 42 | * DONE include question title 43 | class: question-hyperlink 44 | * DONE packaging 45 | * DONE user agent 46 | test url 47 | https://www.google.com/search?q=curl%20 what ever 48 | 49 | working with user agent: 50 | curl -A "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0" https://www.google.com/search?q=curl 51 | 52 | without, die 53 | curl 'https://www.google.com/search?q=curl' 54 | 55 | when you call withou user agent google will show different pages to you, 56 | the trick (dom-by-class dom "jfp3ef") will only work on this case. 57 | 58 | because of this it can figure out you're not using a browser and ban you later 59 | 60 | * DONE use curl 61 | directly use of curl is hard, use request which is an abtraction over curl 62 | curl -A "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0" https://www.google.com 63 | curl -A "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0" https://www.google.com/search?q=howdoy%20you%20re%20site%3Astackoverflow.com%20OR%20site%3Astackexchange.com%20OR%20site%3Asuperuser.com%20OR%20site%3Aserverfault.com%20OR%20site%3Aaskubunu.com 64 | 65 | * DONE bug parsing 66 | query: elisp check if buffers are the same 67 | links https://stackoverflow.com/questions/586735/how-can-i-check-if-a-current-buffer-exists-in-emacs 68 | 69 | eval triggered: 70 | 71 | catch the error: (wrong-type-argument symbolp 72 | (get-buffer name) 73 | 74 | Return the buffer named name (a string). 75 | If there is no live buffer named name, return nil. 76 | name may also be a buffer; if so, the value is that buffer. 77 | 78 | (get-buffer-create name) 79 | 80 | Return the buffer named name, or create such a buffer and return it. 81 | A new buffer is created if there is no live buffer named name. 82 | If name starts with a space, the new buffer does not keep undo information. 83 | If name is a buffer instead of a string, then it is the value returned. 84 | The value is never nil. 85 | ) 86 | 87 | probably when code sections look like lisp code 88 | 89 | #+begin_src 90 | (pre nil #+begin_example emacs 91 | ((code nil (if (buffer-exists "my-buffer-name") 92 | ; do something 93 | ) 94 | )) 95 | #+end_example) 96 | #+end_src 97 | 98 | 99 | #+begin_src 100 | (pre nil #+begin_example emacs 101 | (code nil (if (buffer-exists "my-buffer-name") 102 | ; do something 103 | ) 104 | ) #+end_example) 105 | #+end_src 106 | 107 | bug was due to returning double pathenthese which shr-insert-document will complain 108 | #+begin_example 109 | `(pre nil "#+begin_example " ,howdoyou--current-lang "\n" ,(nthcdr 2 it) "\n#+end_example")) 110 | => (pre nil #begin... ((code nil ...)) #end..) 111 | 112 | (append `(pre nil "#+begin_example " ,howdoyou--current-lang "\n") (nthcdr 2 it) '("#+end_example"))) 113 | => (pre nil #begin... (code nil ...) #end..) 114 | #+end_example 115 | * DONE parse example 116 | https://stackoverflow.com/questions/208105/how-do-i-remove-a-property-from-a-javascript-object 117 | * CANCELED better guest the lang 118 |

119 | 
120 | 

121 | 
122 | if class is lang use lang
123 | if default use tag 
124 | if none use none
125 | 
126 | imposible because the class attribute is added later by js
127 | 
128 | * DONE on fisrt opening, text line is not wrapped 
129 | maybe pop buffer first so it has a size move pop-to-buffers won't solve this but
130 | make the buffer wrapped not in window but a full with window (or full screen?)
131 | 
132 | shawdow ~shr~ params shr-use-fonts nil so shr-internal-width will be calculated
133 | right on first run.
134 | 
135 | #+begin_src elisp
136 | 	(shr-internal-width (or (and shr-width
137 | 				     (if (not shr-use-fonts)
138 | 					 shr-width
139 | 				       (* shr-width (frame-char-width))))
140 |                                 ;; We need to adjust the available
141 |                                 ;; width for when the user disables
142 |                                 ;; the fringes, which will cause the
143 |                                 ;; display engine usurp one column for
144 |                                 ;; the continuation glyph.
145 | ;;=> WE WANT THIS ROUTE
146 | 				(if (not shr-use-fonts)
147 | 				    (- (window-body-width) 1
148 |                                        (if (and (null shr-width)
149 |                                                 (not (shr--have-one-fringe-p)))
150 |                                            0
151 |                                          1))
152 | 				  (- (window-body-width nil t)
153 |                                      (* 2 (frame-char-width))
154 |                                      (if (and (null shr-width)
155 |                                               (not (shr--have-one-fringe-p)))
156 |                                          (* (frame-char-width) 2)
157 |                                        0)))))
158 | #+end_src
159 | 
160 | * DONE how about pop window showing "getting ..." then fill it later just like what we did 
161 | in javascript
162 | 
163 | * DONE window selection should be predictable
164 | - if *hdy* buffer window is currently select. Then use it 
165 | - it not then create new window other than the current one if there is only one window.
166 | 
167 | get hdy buffer, get window having buffer, if window is seleted use it, otherwise use other window
168 | 
169 |       (display-buffer howdoi-buffer '(display-buffer-use-some-window (inhibit-same-window . t))))
170 | * DONE links conflict with line breaks
171 | links in org mode are longer so shr line breaks will be not correct
172 | 
173 | on shr when iterate, if see a convertable links
174 | give out
175 | #+begin_example
176 | [[ ttt ][index]]
177 | [[ ttt ][0]]
178 | [[ ttt ][1]]
179 | #+end_example
180 | and put the links in a index variable
181 | then another function will run through the buffer and do a search and replace
182 | it's possible but too much of work and make the code fragile
183 | 
184 | how about shr width is infinitive and 
185 | turn on visual line mode and your buffer will be responsive, this is the right way 
186 | * DONE bug: catch the error: (error Memory exhausted--use C-x s then exit and restart Emacs)
187 | query: posgresql upsert
188 | https://stackoverflow.com/questions/17267417/how-to-upsert-merge-insert-on-duplicate-update-in-postgresql
189 | 
190 | something with shr-insert-document
191 | when no bindings it works
192 | 
193 | (defun howdoyou--print-node (dom)
194 |   (shr-insert-document dom))
195 | 
196 | with shadow bindings it crashes
197 | 
198 | (defun howdoyou--print-node (dom)
199 |   "Print the DOM."
200 |   ;; shawdow some `shr' parameters
201 |   (let ((shr-bullet "- ") ;; insead of *
202 |         (shr-width most-positive-fixnum) ;; no more line breaks
203 |         (shr-use-fonts nil)) ;; so shr-internal-width is correct on first run
204 |     (shr-insert-document dom)))
205 | 
206 | that would be the parsing with code     
207 | 
208 | no shr-insert-document crashes
209 | 
210 | (shr-width most-positive-fixnum)
211 | 
212 | no it's shr-use-fonts nil
213 | (let ((shr-bullet "- ") (shr-width most-positive-fixnum) (shr-use-fonts nil)) (shr-insert-document thanh))
214 | Eval error ***  Memory exhausted--use C-x s then exit and restart Emacs
215 | when both of them combined
216 | 
217 | set shr-width to 0 or negative will disable line breaks in a sure way
218 | 
219 | (defun shr-fill-lines (start end)
220 |   (if (<= shr-internal-width 0)
221 | * reponse to review
222 | 
223 | #+begin_quote
224 | These look like false positives. You can (and should) silence the compiler so
225 | that the warnings don't confuse users installing the package -- one way would be
226 | to rewrite the function body as:
227 | 
228 |   (let* ((answer-nodes (dom-by-class (cdr result) "answercell"))
229 |          (question-dom (car (dom-by-id (cdr result) "^question$")))
230 |          (title (car (dom-by-class (cdr result) "question-hyperlink")))
231 |          (number-of-answers (min (length answer-nodes) howdoyou-number-of-answers))
232 |          (tags (howdoyou--get-so-tags (cdr result)))
233 |          (score-nodes (dom-by-class (cdr result) "js-vote-count"))
234 |          acc
235 |          scores)
236 |     (dotimes (i number-of-answers)
237 |       (setq acc (append acc (dom-by-class (nth i answer-nodes) "post-text"))))
238 |     (dotimes (i (1+ number-of-answers))
239 |       (setq scores (append scores `(,(dom-text (nth i score-nodes))))))
240 |     (list (car result)
241 |           (dom-text title)
242 |           (dom-by-class question-dom "post-text")
243 |           acc
244 |           scores
245 |           tags)))
246 | 
247 | but the function feels more complicated than it needs to be.
248 | #+end_quote
249 | This won't work
250 | 
251 | #+begin_quote
252 | You should (require 'subr-x) for if-let (rather than rely on transitive imports). Similarly you should (require 'url-http),
253 | #+end_quote
254 | This i don't understand
255 | 
256 | #+begin_quote
257 | And some minor stuff:
258 | 
259 |     howdoyou.el#L198: Consider unless ... instead of when (not ...)
260 |     howdoyou.el#L21: Prefer https over http (if possible)
261 |     
262 |     accross -> across
263 |     Roate -> ?
264 |     shawdow -> shadow
265 |     insead -> instead
266 |     Othewise -> Otherwise
267 | #+end_quote
268 | thanks, corrected
269 | 
270 |     #+begin_quote
271 |     Try to be precise with your indentation, e.g. here, since it can obfuscate
272 |     the scope of your expressions. (Emacs should be able to indent elisp code
273 |     consistently.)
274 |     #+end_quote
275 | This is wrong.
276 | 
277 | * DONE render strikethrough
278 | * DONE hide org makers
279 | * DONE option to select answer buffer
280 | ~howdoyou-switch-to-answer-buffer~
281 | * TODO local buffer functions
282 | minor-mode to manage state?
283 | layers/+spacemacs/spacemacs-org/local/space-doc/space-doc.el
284 | how spacemacs deals with readme, when the file path match the patterns
285 | it runs spacemacs/prettify-org-buffer at core/core-funcs.el
286 | 
287 | current dead simple: use major-mode == 'org-mode as flag
288 | https://stackoverflow.com/questions/8008211/buffer-local-function-in-elisp
289 | * TODO show username
290 | * DONE show time
291 | CLOSED: [2020-08-03 Mon 12:03]
292 | * DONE history
293 | CLOSED: [2019-11-14 Thu 00:10]
294 | * TODO buttons to delete history item and delete all
295 | * TODO use ddg
296 | google changes too much
297 | 
298 | * extract
299 | (dom-elements thanh 'href "^/url?esrc=s&q=&rct=j&sa=U&url=https.*")
300 | 


--------------------------------------------------------------------------------
/test/google.0.html:
--------------------------------------------------------------------------------
1 | quit vim site:stackoverflow.com OR site:stackexchange.com OR site:superuser.com OR site:serverfault.com OR site:askubuntu.com - Google Search
Google
ALLIMAGESVIDEOSNEWS
Aug 6, 2012 · 13 Answers 13 · :q[uit] Quit the current window. Quit Vim if this is the last window. This fails when changes have been made in current buffer.
Nov 15, 2017 · :q should work, unless the file hasn't been saved. :q! will work. :wq will attempt to save and quit, but won't quit if it can't save.
Oct 1, 2013 · To quit the vi editor without saving any changes you've made: If you are currently in insert or append mode, press Esc. Press : (colon). The ...
Sep 18, 2014 · To escape from Vim. ctrl+c to interrupt current task and return to the command mode; :q![ENTER] to quit, bypassing save prompt.
Jun 30, 2015 · To force quit the command is :q! but if :wq doesn't work maybe that means that you don't have the permissions to edit your file.
Jan 30, 2013 · To write and quit in Vim: Press escape. Type ":x". Press enter. Follow this with git push .
Dec 10, 2009 · You can use :sh to exit to your default shell then typing $ exit at the shell prompt will return you to Vim.
Feb 13, 2018 · The : key starts the vim ex command line . You can press escape to exit it.
Dec 12, 2014 · -y option makes vim start in easy mode, you can type CTRL-L to return to normal mode and then type :q! to exit.
Dec 16, 2021 · In short, hit the Esc key to make sure you're in "Normal" mode, then type :q (which will appear on the last line of the screen) and press ...
Next >

Chaparral, Calgary, AB - From your IP address - Learn more
Sign in
SettingsPrivacyTerms
-------------------------------------------------------------------------------- /test/google.1.html: -------------------------------------------------------------------------------- 1 | quit vim site:stackoverflow.com OR site:stackexchange.com OR site:superuser.com OR site:serverfault.com OR site:askubuntu.com - Google Search
Google
ALLIMAGESVIDEOSNEWS
Aug 6, 2012 · 13 Answers 13 · :q[uit] Quit the current window. Quit Vim if this is the last window. This fails when changes have been made in current buffer.
Nov 15, 2017 · :q should work, unless the file hasn't been saved. :q! will work. :wq will attempt to save and quit, but won't quit if it can't save.
Oct 1, 2013 · To quit the vi editor without saving any changes you've made: If you are currently in insert or append mode, press Esc. Press : (colon). The ...
Sep 18, 2014 · To escape from Vim. ctrl+c to interrupt current task and return to the command mode; :q![ENTER] to quit, bypassing save prompt.
Jun 30, 2015 · To force quit the command is :q! but if :wq doesn't work maybe that means that you don't have the permissions to edit your file.
Jan 30, 2013 · To write and quit in Vim: Press escape. Type ":x". Press enter. Follow this with git push .
Dec 10, 2009 · You can use :sh to exit to your default shell then typing $ exit at the shell prompt will return you to Vim.
Feb 13, 2018 · The : key starts the vim ex command line . You can press escape to exit it.
Dec 12, 2014 · -y option makes vim start in easy mode, you can type CTRL-L to return to normal mode and then type :q! to exit.
Dec 16, 2021 · In short, hit the Esc key to make sure you're in "Normal" mode, then type :q (which will appear on the last line of the screen) and press ...
Next >

Chaparral, Calgary, AB - From your IP address - Learn more
Sign in
SettingsPrivacyTerms
-------------------------------------------------------------------------------- /test/google.2.html: -------------------------------------------------------------------------------- 1 | quit vim site:stackoverflow.com OR site:stackexchange.com OR site:superuser.com OR site:serverfault.com OR site:askubuntu.com - Google Search
Google
ALLIMAGESVIDEOSNEWS
Aug 6, 2012 · 13 Answers 13 · :q[uit] Quit the current window. Quit Vim if this is the last window. This fails when changes have been made in current buffer.
Nov 15, 2017 · :q should work, unless the file hasn't been saved. :q! will work. :wq will attempt to save and quit, but won't quit if it can't save.
Oct 1, 2013 · To quit the vi editor without saving any changes you've made: If you are currently in insert or append mode, press Esc. Press : (colon). The ...
Sep 18, 2014 · To escape from Vim. ctrl+c to interrupt current task and return to the command mode; :q![ENTER] to quit, bypassing save prompt.
Jun 30, 2015 · To force quit the command is :q! but if :wq doesn't work maybe that means that you don't have the permissions to edit your file.
Jan 30, 2013 · To write and quit in Vim: Press escape. Type ":x". Press enter. Follow this with git push .
Dec 10, 2009 · You can use :sh to exit to your default shell then typing $ exit at the shell prompt will return you to Vim.
Feb 13, 2018 · The : key starts the vim ex command line . You can press escape to exit it.
Dec 12, 2014 · -y option makes vim start in easy mode, you can type CTRL-L to return to normal mode and then type :q! to exit.
Dec 16, 2021 · In short, hit the Esc key to make sure you're in "Normal" mode, then type :q (which will appear on the last line of the screen) and press ...
Next >

Chaparral, Calgary, AB - From your IP address - Learn more
Sign in
SettingsPrivacyTerms
-------------------------------------------------------------------------------- /howdoyou.el: -------------------------------------------------------------------------------- 1 | ;;; howdoyou.el --- A stackoverflow and its sisters' sites reader -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2019 Thanh Vuong 4 | 5 | ;; Author: Thanh Vuong 6 | ;; URL: https://github.com/thanhvg/howdoyou/ 7 | ;; Package-Requires: ((emacs "25.1") (promise "1.1") (request "0.3.3") (org "9.2")) 8 | ;; Version: 0.5.0 9 | 10 | ;; This program is free software; you can redistribute it and/or modify 11 | ;; it under the terms of the GNU General Public License as published by 12 | ;; the Free Software Foundation, either version 3 of the License, or 13 | ;; (at your option) any later version. 14 | 15 | ;; This program is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ;; GNU General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with this program. If not, see . 22 | 23 | ;;; Commentary: 24 | ;; This package is inspired by python howdoi (https://github.com/gleitz/howdoi) 25 | ;; and howdoi Emacs package (https://github.com/lockie/emacs-howdoi and 26 | ;; https://github.com/atykhonov/emacs-howdoi). it searches your query all across 27 | ;; stackoverflow and its sisters' sites. They are: stackoverflow.com, 28 | ;; stackexchange.com, superuser.com, serverfault.com and askubuntu.com. The 29 | ;; result is then showed in an `org-mode' buffer. For each result, the question 30 | ;; and three answers were showed, but they are collapsed by default except the 31 | ;; first answer. As this package uses Google to get the links, for each query 32 | ;; there will be a dozen of links, the fist link will be used, users can go 33 | ;; through these links. The author believes that when searching for solutions it 34 | ;; is important for users to read both questions and answers, so no "quick look" 35 | ;; features such as code only view or code completion are provided. 36 | 37 | ;;; Dependencies 38 | ;; `promise' and `request' are required. 39 | ;; user must have `org-mode' 9.2 or later installed also. 40 | 41 | ;;; Commands 42 | ;; howdoyou-query: prompt for query and do search 43 | ;; howdoyou-next-link: go to next link 44 | ;; howdoyou-previous-link: go to previous link 45 | ;; howdoyou-go-back-to-first-link: go back to first link 46 | ;; howdoyou-reload-link: reload link 47 | 48 | ;;; Customization 49 | ;; howdoyou-use-curl: default is true if curl is available 50 | ;; howdoyou-number-of-answers: maximal number of answers to show, default is 3 51 | ;; howdoyou-switch-to-answer-buffer: switch to answer buffer if non nil, default is nil 52 | 53 | ;;; Changelog 54 | ;; 2025-03-20: 55 | ;; - update google search extraction 56 | ;; 2021-09-09: 57 | ;; - back to use curl if possible 58 | ;; 2021-09-02: 59 | ;; - use url-retrieve as default instead of request 60 | ;; 2021-07-06: 61 | ;; - adapt to new SO change: trim score text 62 | ;; 2020-10-02: 63 | ;; - update with change from google 64 | ;; - impove test 65 | ;; - bump version 66 | ;; 2020-08-27: 67 | ;; - bump version 68 | ;; - adapt to recent change by SO: css class from "post-text" to "s-prose" 69 | ;; 2020-08-28: 70 | ;; - adapt to recent change by SO: css class from "post-text" to "s-prose" 71 | 72 | ;;; Code: 73 | (require 'promise) 74 | (require 'dom) 75 | (require 'cl-lib) 76 | (require 'request) 77 | (require 'shr) 78 | (require 'org) 79 | (require 'subr-x) 80 | (require 'url) 81 | (require 'font-lock) 82 | 83 | ;; public variables 84 | (defgroup howdoyou nil 85 | "Search and read stackoverflow and sisters's sites." 86 | :group 'extensions 87 | :group 'convenience 88 | :version "25.1" 89 | :link '(emacs-commentary-link "howdoyou.el")) 90 | 91 | (defcustom howdoyou-use-curl (if (executable-find request-curl) 92 | t 93 | nil) 94 | "Use curl instead of buggy `url-retrieve'." 95 | :type 'boolean 96 | :group 'howdoyou) 97 | 98 | (defcustom howdoyou-number-of-answers 3 99 | "Number of maximal answers to show." 100 | :type 'number 101 | :group 'howdoyou) 102 | 103 | 104 | (defcustom howdoyou-max-history 20 105 | "Number of maximal query history." 106 | :type 'number 107 | :group 'howdoyou) 108 | 109 | (defcustom howdoyou-switch-to-answer-buffer nil 110 | "If non-nil answer-buffer will be selected." 111 | :type 'boolean 112 | :group 'howdoyou) 113 | 114 | ;; private variables 115 | (defvar howdoyou--current-link-index 0 116 | "Current index of link.") 117 | 118 | (defvar howdoyou--links nil 119 | "List of so links from google search.") 120 | 121 | (defvar howdoyou--query-history '() 122 | "List of query history") 123 | 124 | (defvar howdoyou--current-lang nil 125 | "Guested language.") 126 | 127 | (defvar howdoyou--current-user-agent 0 128 | "Index to be rotated.") 129 | 130 | ;; (defvar howdoyou--google-link-class "^yuRUbf$" 131 | ;; "css class name of dom node that has node as a child.") 132 | 133 | ;; (setq howdoyou--google-link-class "^yuRUbf$") 134 | 135 | (define-minor-mode howdoyou-mode 136 | "Minor mode for howdoyou. 137 | 138 | \\{howdoyou-mode-map} 139 | " 140 | :lighter " HDY" 141 | :keymap (let ((map (make-sparse-keymap))) 142 | (define-key map (kbd "C-M-") #'howdoyou-previous-link) 143 | (define-key map (kbd "C-M-") #'howdoyou-next-link) 144 | map)) 145 | 146 | ;; copy from https://github.com/gleitz/howdoi 147 | (defvar howdoyou--user-agents 148 | '("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0" 149 | "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100 101 Firefox/22.0" 150 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0" 151 | "Mozilla/5.0 (Windows NT 6.1; rv:11.0) Gecko/20100101 Firefox/11.0") 152 | "List of user agent to make Google happy.") 153 | 154 | ;; functions 155 | ;; (defun howdoyou--extract-links-from-class (dom class) 156 | ;; "Extract links inside r class from DOM." 157 | ;; (let ((my-nodes (dom-by-class dom class))) 158 | ;; (mapcar (lambda (a-node) 159 | ;; ;; (setq thanh a-node) 160 | ;; (dom-attr (dom-child-by-tag (nth 2 (nth 2 a-node)) 'a) 'href)) 161 | ;; my-nodes))) 162 | 163 | (defun howdoyou--extract-links-from-google (dom) 164 | "Produce links from google search dom. 165 | DOM is a dom object of the google search, returns a list of links" 166 | ;; (setq thanh dom) 167 | ;; (howdoyou--extract-links-from-class dom howdoyou--google-link-class) 168 | (mapcar (lambda (it) 169 | (let* (( url (dom-attr it 'href)) 170 | (start (string-match "url=\\(https?://[^&]+\\)" url)) ) 171 | (when start (match-string 1 url)))) 172 | (dom-elements dom 'href "^\\/url\\?esrc=s&q=&rct=j&sa=U&url=https.*"))) 173 | 174 | (defun howdoyou--curl-promise-dom (url) 175 | "Promise (url . dom) from URL with curl." 176 | (promise-new 177 | (lambda (resolve reject) 178 | ;; shadow reject-curl-options to have user agent 179 | (let 180 | ((request-curl-options 181 | `(,(format "-A \"%s\"" (howdoyou--get-user-agent))))) 182 | (request url 183 | :parser 184 | (lambda () (progn 185 | (decode-coding-region (point-min) (point-max) 'utf-8) 186 | (libxml-parse-html-region (point-min) 187 | (point-max)))) 188 | :error (cl-function (lambda 189 | (&rest args &key error-thrown &allow-other-keys) 190 | (funcall reject error-thrown))) 191 | :success (cl-function (lambda (&key data &allow-other-keys) 192 | (funcall resolve (cons url data))))))))) 193 | 194 | (defun howdoyou--url-promise-dom (url) 195 | "Promise a cons (URL . dom). 196 | URL is a link string. Download the url and parse it to a DOM object" 197 | ;; (message "%s" url) 198 | (promise-new 199 | (lambda (resolve reject) 200 | (let ((url-user-agent (howdoyou--get-user-agent))) 201 | (url-retrieve url 202 | (lambda (status) 203 | (if (plist-get status :error) 204 | (funcall reject (plist-get status :error)) 205 | (condition-case ex 206 | (with-current-buffer (current-buffer) 207 | (if (not (url-http-parse-headers)) 208 | (funcall reject (buffer-string)) 209 | (funcall resolve 210 | (cons url 211 | (libxml-parse-html-region 212 | (point-min) (point-max)))))) 213 | (error (funcall reject ex)))))))))) 214 | 215 | (defun howdoyou--promise-dom (url) 216 | "Promise a cons (URL . dom). 217 | URL is a link string. Download the url and parse it to a DOM object" 218 | (if howdoyou-use-curl (howdoyou--curl-promise-dom url) 219 | (howdoyou--url-promise-dom url))) 220 | 221 | (defun howdoyou--get-user-agent () 222 | "Rotate user agent from `howdoyou--user-agents'." 223 | (let ((user-agent (nth howdoyou--current-user-agent howdoyou--user-agents))) 224 | (setq howdoyou--current-user-agent (if (>= howdoyou--current-user-agent 225 | (1- 226 | (length howdoyou--user-agents))) 227 | 0 228 | (1+ howdoyou--current-user-agent))) 229 | user-agent)) 230 | 231 | (defun howdoyou--get-buffer () 232 | "Get *How Do You* buffer." 233 | (get-buffer-create "*How Do You*")) 234 | 235 | (defun howdoyou--print-message (msg &optional &rest args) 236 | "Print MSG message and prepare window for howdoyou buffer." 237 | (let ((my-buffer (howdoyou--get-buffer))) 238 | (unless (equal (window-buffer) my-buffer) 239 | ;; (switch-to-buffer-other-window my-buffer)) 240 | ;; from magit: '(nil (inhibit-same-window . t)) no idea why it works 241 | ;; should show a new window in current frame 242 | (if howdoyou-switch-to-answer-buffer 243 | (select-window 244 | (display-buffer my-buffer '(nil (inhibit-same-window . t)))) 245 | (display-buffer my-buffer '(nil (inhibit-same-window . t))))) 246 | (with-current-buffer my-buffer 247 | (let ((inhibit-read-only t)) 248 | (erase-buffer) 249 | (insert (apply #'format msg args))) 250 | (read-only-mode 1) 251 | (unless howdoyou-mode 252 | (howdoyou-mode 1))))) 253 | 254 | (defun howdoyou-promise-answer (query) 255 | "Process QUERY and print answers to *How Do You* buffer." 256 | (howdoyou--print-message "Searching...") 257 | (let ((url "https://www.google.com/search") 258 | (args (concat "?q=" 259 | (url-hexify-string query) 260 | (url-hexify-string " ") 261 | (url-hexify-string "site:stackoverflow.com OR ") 262 | (url-hexify-string "site:stackexchange.com OR ") 263 | (url-hexify-string "site:superuser.com OR ") 264 | (url-hexify-string "site:serverfault.com OR ") 265 | (url-hexify-string "site:askubuntu.com") 266 | "&hl=en"))) 267 | (promise-chain (howdoyou--promise-dom (concat url args)) 268 | (then (lambda (result) 269 | (howdoyou--extract-links-from-google (cdr result)))) 270 | (then (lambda (links) 271 | (setq howdoyou--links links) 272 | (setq howdoyou--current-link-index 0) 273 | (if howdoyou--links 274 | (howdoyou-n-link 0) 275 | (howdoyou--print-message "No results: \"%s\"" query)))) 276 | (catch (lambda (reason) 277 | (howdoyou--print-message "Error: %s" reason)))))) 278 | 279 | (defun howdoyou--get-so-tags (dom) 280 | "Extract list of tags from stackoverflow DOM." 281 | (let ((tag-doms (dom-by-class (dom-by-class dom "^post-taglist") 282 | "^post-tag$"))) 283 | (mapcar #'dom-text tag-doms))) 284 | 285 | 286 | (defun howdoyou--get-answer-and-time-from-nodes (nodes) 287 | "From answer NODES produce list of (answer. time)." 288 | (cons (dom-by-class nodes "s-prose") 289 | (mapconcat (lambda (it) (substring (dom-attr it 'title) 0 10)) 290 | (dom-by-class nodes "relativetime") 291 | " / "))) 292 | 293 | (defun howdoyou--promise-so-answer (result) 294 | "Produce answer-list from stackoverflow response. 295 | RESULT is a (url . dom). 296 | Return (url title question answers scores tags)" 297 | (let* ((answer-nodes (dom-by-class (cdr result) "answercell")) 298 | (question-dom (car (dom-by-id (cdr result) "^question$"))) 299 | (title (car (dom-by-class (cdr result) "question-hyperlink"))) 300 | (number-of-answers (if 301 | (> (length answer-nodes) 302 | howdoyou-number-of-answers) 303 | howdoyou-number-of-answers 304 | (length answer-nodes))) 305 | (tags (howdoyou--get-so-tags (cdr result))) 306 | (score-nodes (dom-by-class (cdr result) "js-vote-count"))) 307 | (list (car result) 308 | (dom-text title) 309 | (dom-by-class question-dom "s-prose") 310 | (mapcar #'howdoyou--get-answer-and-time-from-nodes 311 | (seq-take answer-nodes number-of-answers)) 312 | (mapcar (lambda (it) (string-trim (dom-text it))) 313 | (seq-take score-nodes (1+ number-of-answers))) 314 | tags))) 315 | 316 | (defun howdoyou--print-answer (answer-list) 317 | "Print ANSWER-LIST to *How Do You* buffer." 318 | (let* ((my-buffer (howdoyou--get-buffer)) 319 | (url (car answer-list)) 320 | (title (nth 1 answer-list)) 321 | (question (nth 2 answer-list)) 322 | (answers (nth 3 answer-list)) ;; list of (answer . time) 323 | (scores (nth 4 answer-list)) 324 | (question-score (car scores)) 325 | (answer-scores (cdr scores)) 326 | (tags (nth 5 answer-list)) 327 | (first-run t) ;; flag for special treatment of first answer 328 | (lang (car tags))) 329 | ;; first tag is usually the language 330 | (setq howdoyou--current-lang lang) 331 | (with-current-buffer my-buffer 332 | (read-only-mode -1) 333 | (erase-buffer) 334 | (insert "#+STARTUP: overview\n#+TITLE: " title "\n") 335 | (insert url) ;; url 336 | (insert (format "\n* Question (%s)" question-score)) 337 | (howdoyou--print-dom question) 338 | (insert "\nTags: ") 339 | (dolist (tag tags) 340 | (insert tag) 341 | (insert " ")) 342 | (cl-mapcar (lambda (a s) 343 | (insert (format "\n* Answer (%s) (%s)" s (cdr a))) 344 | (when first-run 345 | (insert "\n:PROPERTIES:\n:VISIBILITY: all\n:END:\n") 346 | (setq first-run nil)) 347 | (howdoyou--print-dom (car a))) 348 | answers 349 | answer-scores) 350 | (delete-trailing-whitespace) 351 | (howdoyou--print-history) 352 | (if (equal major-mode 'org-mode) 353 | (org-set-startup-visibility) 354 | (org-mode) 355 | (setq-local org-hide-emphasis-markers t) 356 | ;; need this on spacemacs if org-mode never loaded anywhere 357 | (font-lock-flush)) 358 | (visual-line-mode) 359 | (unless howdoyou-mode 360 | (howdoyou-mode 1)) 361 | (goto-char (point-min))))) 362 | 363 | (defun howdoyou--print-node (dom) 364 | "Print the DOM." 365 | ;; shadow some `shr' parameters 366 | (let ((shr-bullet "- ") ;; instead of * 367 | ;; no more line breaks 368 | (shr-width 0) 369 | ;; because we use fixed width anyway, save some computations 370 | (shr-use-fonts nil)) 371 | (shr-insert-document dom))) 372 | 373 | (defun howdoyou--pre-class-name-to-lang (class-name) 374 | "Return language name from CLASS-NAME. 375 | CLASS-NAME has lang-name => name. 376 | CLASS-NAME has default => `howdoyou--current-lang'. 377 | CLASS-NAME has nothing => empty string" 378 | (cond 379 | ((not (stringp class-name)) "") 380 | ((string-match "lang-\\b\\(.+?\\)\\b" class-name) 381 | (match-string 1 class-name)) 382 | (t howdoyou--current-lang))) 383 | 384 | (defun howdoyou--it-to-it (it) 385 | "Map node to node. 386 | IT is an element in the DOM tree. Map to different IT when it is 387 | a, img or pre. Otherwise just copy" 388 | (cond 389 | ((and (listp it) 390 | (listp (cdr it))) 391 | ;; check for list but not cons 392 | (cond 393 | ((equal (car it) 'h2) 394 | (concat "** " (dom-texts it))) 395 | ((equal (car it) 'blockquote) 396 | `(blockquote nil "#+begin_quote" ,(mapcar #'howdoyou--it-to-it it) 397 | "#+end_quote")) 398 | ((equal (car it) 'code) 399 | (concat "~" (dom-texts it) "~")) 400 | ((equal (car it) 'strong) 401 | (concat "*" (dom-texts it) "*")) 402 | ((memq (car it) '(em i)) 403 | (concat "/" (dom-texts it) "/")) 404 | ((memq (car it) '(s del)) 405 | (concat "+" (dom-texts it) "+")) 406 | ((and (equal (car it) 'a) 407 | (not (dom-by-tag it 'img))) 408 | ;; bail out if img 409 | (org-link-make-string (dom-attr it 'href) (dom-texts it))) 410 | ;; ((and (equal (dom-tag it) 'div) 411 | ;; (equal (dom-attr it 'class) "snippet")) 412 | ;; (mapcar #'howdoyou--it-to-it (dom-by-tag it 'pre))) 413 | ((equal (car it) 'pre) 414 | `(pre nil "#+begin_example " 415 | ,howdoyou--current-lang "\n" ,@(nthcdr 2 it) 416 | ,(if (dom-attr it 'class) 417 | "\n#+end_example" 418 | "#+end_example"))) 419 | ;; (append `(pre nil "#+begin_example " ,howdoyou--current-lang "\n") (nthcdr 2 it) '("#+end_example"))) 420 | (t (mapcar #'howdoyou--it-to-it it)))) 421 | (t it))) 422 | 423 | (defun howdoyou--print-dom (dom) 424 | "Map new dom from DOM and print it." 425 | (howdoyou--print-node (mapcar #'howdoyou--it-to-it dom))) 426 | 427 | (defun howdoyou--update-history (query) 428 | "Add QUERY to `howdoyou--query-history'." 429 | (setq howdoyou--query-history 430 | (seq-take (add-to-list 'howdoyou--query-history query) 431 | howdoyou-max-history))) 432 | 433 | (defun howdoyou--print-history () 434 | "Print `howdoyou--query-history'." 435 | (insert "\n* History\n") 436 | (dolist (query howdoyou--query-history) 437 | (insert (format "[[elisp:(howdoyou-promise-answer \"%s\")][%s]]\n" 438 | query 439 | query)))) 440 | 441 | ;;;###autoload 442 | (defun howdoyou-query (query) 443 | "Prompt for QUERY and search for answer. 444 | Pop up *How Do You* buffer to show the answer." 445 | (interactive "sQuery: ") 446 | (message "_") ;; prevent suggest-key-bindings from usurping minibuffer 447 | (howdoyou--update-history query) 448 | (howdoyou-promise-answer query)) 449 | 450 | (defun howdoyou-n-link (n) 451 | "Jump N steps in `howdoyou--links' and request and print the answer." 452 | (let ((cand (+ n howdoyou--current-link-index)) 453 | (total (length howdoyou--links))) 454 | (when (zerop total) 455 | (error "howdoyou-n-link: No current links")) 456 | (cond ((< cand 0) 457 | (setq cand 0) 458 | (message "howdoyou-n-link: at first link %s of %s" (1+ cand) total)) 459 | ((>= cand total) 460 | (setq cand (1- total)) 461 | (message "howdoyou-n-link: at final link %s of %s" (1+ cand) total))) 462 | (when (or (zerop n) (/= cand howdoyou--current-link-index)) 463 | (let ((link (nth cand howdoyou--links))) 464 | (howdoyou--print-message "Loading %s of %s..." (1+ cand) total) 465 | (promise-chain (howdoyou--promise-dom link) 466 | (then #'howdoyou--promise-so-answer) 467 | (then #'howdoyou--print-answer) 468 | (then (lambda (_result) 469 | (setq howdoyou--current-link-index cand))) 470 | (catch (lambda (reason) 471 | (message "catch error in n-link: %s %s" reason link) 472 | (unless (zerop n) 473 | (let ((one-past 474 | (min (1- total) 475 | (max 0 (funcall (if (< n 0) #'1- #'1+) n))))) 476 | (when (/= one-past cand) 477 | (howdoyou-n-link one-past))))))))))) 478 | 479 | (defun howdoyou-read-so-link (link) 480 | "Read stackoverflow LINK in buffer." 481 | (promise-chain (howdoyou--promise-dom link) 482 | (then #'howdoyou--promise-so-answer) 483 | (then #'howdoyou--print-answer) 484 | (catch (lambda (reason) 485 | (message "catch error in so-link: %s" reason))))) 486 | 487 | (defun howdoyou-clear-history () 488 | "Clear `howdoyou--query-history'." 489 | (setq howdoyou--query-history '())) 490 | 491 | ;;;###autoload 492 | (defun howdoyou-next-link () 493 | "Go to next link stored in google search." 494 | (interactive) 495 | (howdoyou-n-link 1)) 496 | 497 | ;;;###autoload 498 | (defun howdoyou-previous-link () 499 | "Go to previous link stored in google search." 500 | (interactive) 501 | (howdoyou-n-link -1)) 502 | 503 | ;;;###autoload 504 | (defun howdoyou-reload-link () 505 | "Reload current link in google search." 506 | (interactive) 507 | (howdoyou-n-link 0)) 508 | 509 | ;;;###autoload 510 | (defun howdoyou-go-back-to-first-link () 511 | "Reload current link in google search." 512 | (interactive) 513 | (howdoyou-n-link (- howdoyou--current-link-index))) 514 | 515 | (provide 'howdoyou) 516 | ;;; howdoyou.el ends here 517 | --------------------------------------------------------------------------------