├── .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 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 ... |
Chaparral, Calgary, AB - From your IP address - Learn more
--------------------------------------------------------------------------------
/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 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 ... |
Chaparral, Calgary, AB - From your IP address - Learn more
--------------------------------------------------------------------------------
/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 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 ... |
Chaparral, Calgary, AB - From your IP address - Learn more
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------