-
Notifications
You must be signed in to change notification settings - Fork 40
/
sx-question-list.el
674 lines (587 loc) · 24.6 KB
/
sx-question-list.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
;;; sx-question-list.el --- major-mode for navigating questions list -*- lexical-binding: t; -*-
;; Copyright (C) 2014-2018 Artur Malabarba
;; Author: Artur Malabarba <[email protected]>
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Provides question list logic (as used in e.g. `sx-tab-frontpage').
;;; Code:
(require 'tabulated-list)
(require 'cl-lib)
(require 'sx)
(require 'sx-switchto)
(require 'sx-time)
(require 'sx-tag)
(require 'sx-site)
(require 'sx-question)
(require 'sx-question-mode)
(require 'sx-favorites)
(defgroup sx-question-list nil
"Customization group for sx-question-list."
:prefix "sx-question-list-"
:tag "SX Question List"
:group 'sx)
(defgroup sx-question-list-faces nil
"Customization group for the faces of `sx-question-list'."
:prefix "sx-question-list-"
:tag "SX Question List Faces"
:group 'sx-question-list)
;;; Customization
(defcustom sx-question-list-height 12
"Height, in lines, of SX's *question-list* buffer."
:type 'integer
:group 'sx-question-list)
(defcustom sx-question-list-excluded-tags nil
"List of tags (strings) to be excluded from the question list."
:type '(repeat string)
:group 'sx-question-list)
(defface sx-question-list-parent
'((t :inherit default))
""
:group 'sx-question-list-faces)
(defface sx-question-list-answers
'((((background light)) :foreground "SeaGreen4"
:height 1.0 :inherit sx-question-list-parent)
(((background dark)) :foreground "#D1FA71"
:height 1.0 :inherit sx-question-list-parent)
(t :inherit sx-question-list-parent))
""
:group 'sx-question-list-faces)
(defface sx-question-list-answers-accepted
'((t :box 1 :inherit sx-question-list-answers))
""
:group 'sx-question-list-faces)
(defface sx-question-list-score
'((t :height 1.0 :inherit sx-question-list-parent))
""
:group 'sx-question-list-faces)
(defface sx-question-list-score-upvoted
'((t :weight bold
:inherit sx-question-list-score))
""
:group 'sx-question-list-faces)
(defface sx-question-list-date
'((t :inherit font-lock-comment-face))
""
:group 'sx-question-list-faces)
(defface sx-question-list-read-question
'((t :height 1.0 :inherit sx-question-list-parent))
""
:group 'sx-question-list-faces)
(defface sx-question-list-unread-question
'((t :weight bold :inherit sx-question-list-read-question))
""
:group 'sx-question-list-faces)
(defface sx-question-list-favorite
'((t :inherit sx-question-list-score-upvoted))
""
:group 'sx-question-list-faces)
(defface sx-question-list-bounty
'((t :inherit font-lock-warning-face))
""
:group 'sx-question-list-faces)
;;; Backend variables
(defvar sx-question-list--site nil
"Site being displayed in the *question-list* buffer.")
(make-variable-buffer-local 'sx-question-list--site)
(defvar sx-question-list--order nil
"Order being displayed in the *question-list* buffer.
This is also affected by `sx-question-list--descending'.")
(make-variable-buffer-local 'sx-question-list--order)
(defvar sx-question-list--descending t
"In which direction should `sx-question-list--order' be sorted.
If non-nil (default), descending.
If nil, ascending.")
(make-variable-buffer-local 'sx-question-list--order)
(defvar sx-question-list--print-function #'sx-question-list--print-info
"Function to convert a question alist into a tabulated-list entry.
Used by `sx-question-list-mode', the default value is
`sx-question-list--print-info'.
If this is set to a different value, it may be necessary to
change `tabulated-list-format' accordingly.")
(make-variable-buffer-local 'sx-question-list--print-function)
(defcustom sx-question-list-ago-string " ago"
"String appended to descriptions of the time since something happened.
Used in the questions list to indicate a question was updated
\"4d ago\"."
:type 'string
:group 'sx-question-list)
(defun sx-question-list--print-info (question-data)
"Convert `json-read' QUESTION-DATA into tabulated-list format.
This is the default printer used by `sx-question-list'. It
assumes QUESTION-DATA is an alist containing (at least) the
elements:
`question_id', `site_par', `score', `upvoted', `answer_count',
`title', `bounty_amount', `bounty_amount', `bounty_amount',
`last_activity_date', `tags', `owner'.
Also see `sx-question-list-refresh'."
(sx-assoc-let question-data
(let ((favorite (if (member .question_id
(assoc .site_par
sx-favorites--user-favorite-list))
(if (char-displayable-p ?\x2b26) "\x2b26" "*") " ")))
(list
question-data
(vector
(list (int-to-string .score)
'face (if .upvoted 'sx-question-list-score-upvoted
'sx-question-list-score))
(list (int-to-string .answer_count)
'face (if (sx-question--accepted-answer-id question-data)
'sx-question-list-answers-accepted
'sx-question-list-answers))
(concat
;; First line
(propertize
.title
'face (if (sx-question--read-p question-data)
'sx-question-list-read-question
'sx-question-list-unread-question))
(propertize " " 'display "\n ")
;; Second line
(propertize favorite 'face 'sx-question-list-favorite)
(if (and (numberp .bounty_amount) (> .bounty_amount 0))
(propertize (format "%4d" .bounty_amount)
'face 'sx-question-list-bounty)
" ")
" "
(propertize (format "%3s%s"
(sx-time-since .last_activity_date)
sx-question-list-ago-string)
'face 'sx-question-list-date)
" "
;; @TODO: Make this width customizable. (Or maybe just make
;; the whole thing customizable)
(format "%-40s" (sx-tag--format-tags .tags sx-question-list--site))
" "
(sx-user--format "%15d %4r" .owner)
(propertize " " 'display "\n")))))))
(defvar sx-question-list--pages-so-far 0
"Number of pages currently being displayed.
This variable gets reset to 0 before every refresh.
It should be used by `sx-question-list--next-page-function'.")
(make-variable-buffer-local 'sx-question-list--pages-so-far)
(defvar sx-question-list--refresh-function nil
"Function used to refresh the list of questions to be displayed.
Used by `sx-question-list-mode', this is a function, called with
no arguments, which returns a list questions to be displayed,
like the one returned by `sx-question-get-questions'.
If this is not set, the value of `sx-question-list--dataset' is
used, and the list is simply redisplayed.")
(make-variable-buffer-local 'sx-question-list--refresh-function)
(defvar sx-question-list--next-page-function nil
"Function used to fetch the next page of questions to be displayed.
Used by `sx-question-list-mode'. This is a function, called with
no arguments, which returns a list questions to be displayed,
like the one returned by `sx-question-get-questions'.
This function will be called when the user presses \\<sx-question-list-mode-map>\\[sx-question-list-next] at the end
of the question list. It should either return nil (indicating
\"no more questions\") or return a list of questions which will
appended to the currently displayed list.
If this is not set, it's the same as a function which always
returns nil.")
(make-variable-buffer-local 'sx-question-list--next-page-function)
(defvar sx-question-list--dataset nil
"The logical data behind the displayed list of questions.
This dataset contains even questions that are hidden by the user,
and thus not displayed in the list of questions.
This is ignored if `sx-question-list--refresh-function' is set.")
(make-variable-buffer-local 'sx-question-list--dataset)
(defconst sx-question-list--key-definitions
'(
;; S-down and S-up would collide with `windmove'.
("<down>" sx-question-list-next)
("<up>" sx-question-list-previous)
("RET" sx-display "Display")
("n" sx-question-list-next "Navigate")
("p" sx-question-list-previous "Navigate")
("j" sx-question-list-view-next "Navigate")
("k" sx-question-list-view-previous "Navigate")
("N" sx-question-list-next-far)
("P" sx-question-list-previous-far)
("J" sx-question-list-next-far)
("K" sx-question-list-previous-far)
("g" sx-question-list-refresh)
("t" sx-tab-switch "tab")
("a" sx-ask "ask")
("S" sx-search "Search")
("s" sx-switchto-map "switch-to")
("v" sx-visit-externally "visit")
("u" sx-upvote)
("d" sx-downvote)
("h" sx-question-list-hide "hide")
("m" sx-question-list-mark-read "mark-read")
("*" sx-favorite)
)
"List of key definitions for `sx-question-list-mode'.
This list must follow the form described in
`sx--key-definitions-to-header-line'.")
(defconst sx-question-list--header-line
(sx--key-definitions-to-header-line
sx-question-list--key-definitions)
"Header-line used on the question list.")
(defvar sx-question-list--order-methods
'(("Recent Activity" . activity)
("Creation Date" . creation)
("Most Voted" . votes)
("Score" . votes))
"Alist of possible values to be passed to the `sort' keyword.")
(make-variable-buffer-local 'sx-question-list--order-methods)
(defun sx-question-list--interactive-order-prompt (&optional prompt)
"Interactively prompt for a sorting order.
PROMPT is displayed to the user. If it is omitted, a default one
is used."
(let ((order (sx-completing-read
(or prompt "Order questions by: ")
(mapcar #'car sx-question-list--order-methods))))
(cdr-safe (assoc-string order sx-question-list--order-methods))))
(defun sx-question-list--make-pager (method &optional submethod)
"Return a function suitable for use as a question list pager.
Meant to be used as `sx-question-list--next-page-function'."
(lambda (page)
(sx-method-call method
:keywords `((page . ,page)
,@(when sx-question-list--order
`((order . ,(if sx-question-list--descending 'desc 'asc))
(sort . ,sx-question-list--order))))
:site sx-question-list--site
:auth t
:submethod submethod
:filter sx-browse-filter)))
;;; Mode Definition
(defvar sx-question-list--current-tab "Latest"
;; @TODO Other values (once we implement them) are "Top Voted",
;; "Unanswered", etc.
"Variable describing current tab being viewed.")
(defconst sx-question-list--mode-line-format
'(" "
(:propertize
(:eval (sx--pretty-site-parameter sx-question-list--site))
face mode-line-buffer-id)
" " mode-name ": "
(:propertize sx-question-list--current-tab
face mode-line-buffer-id)
" ["
"Unread: "
(:propertize
(:eval (sx-question-list--unread-count))
face mode-line-buffer-id)
", "
"Total: "
(:propertize
(:eval (int-to-string (length tabulated-list-entries)))
face mode-line-buffer-id)
"] ")
"Mode-line construct to use in question-list buffers.")
(define-derived-mode sx-question-list-mode
tabulated-list-mode "Question List"
"Major mode for browsing a list of questions from StackExchange.
Letters do not insert themselves; instead, they are commands.
The recommended way of using this mode is to activate it and then
set `sx-question-list--next-page-function'. The return value of
this function is mapped with `sx-question-list--print-function',
so you may need to customize the latter if the former does not
return a list of questions.
The full list of variables which can be set is:
1. `sx-question-list--site'
Set this to the name of the site if that makes sense. If it
doesn't leave it as nil.
2. `sx-question-list--print-function'
Change this if the data you're dealing with is not strictly a
list of questions (see the doc for details).
3. `sx-question-list--refresh-function'
This is used to populate the initial list. It is only necessary
if item 4 does not fit your needs.
4. `sx-question-list--next-page-function'
This is used to fetch further questions. If item 3 is nil, it is
also used to populate the initial list.
5. `sx-question-list--dataset'
This is only used if both 3 and 4 are nil. It can be used to
display a static list.
6. `sx-question-list--order'
Set this to the `sort' method that should be used when
requesting the list, if that makes sense. If it doesn't
leave it as nil.
\\<sx-question-list-mode-map>
If none of these is configured, the behaviour is that of a
\"Frontpage\", for the site given by
`sx-question-list--site'.
Item 2 is mandatory, but it also has a sane default which is
usually enough.
As long as one of 3, 4, or 5 is provided, the other two are
entirely optional. Populating or refreshing the list of questions
is done in the following way:
- Set `sx-question-list--pages-so-far' to 1.
- Call function 2.
- If function 2 is not given, call function 3 with argument 1.
- If 3 is not given use the value of 4.
Adding further questions to the bottom of the list is done by:
- Increment `sx-question-list--pages-so-far'.
- Call function 3 with argument `sx-question-list--pages-so-far'.
- If it returns anything, append to the dataset and refresh the
display; otherwise, decrement `sx-question-list--pages-so-far'.
If `sx-question-list--site' is given, items 3 and 4 should take it
into consideration. The same holds for `sx-question-list--order'.
\\{sx-question-list-mode-map}"
(hl-line-mode 1)
(setq mode-line-format
sx-question-list--mode-line-format)
(setq sx-question-list--pages-so-far 0)
(setq tabulated-list-format
[(" V" 3 t :right-align t)
(" A" 3 t :right-align t)
("Title" 0 sx-question-list--date-more-recent-p)])
(setq tabulated-list-padding 1)
;; Sorting by title actually sorts by date. It's what we want, but
;; it's not terribly intuitive.
(setq tabulated-list-sort-key nil)
(add-hook 'tabulated-list-revert-hook
#'sx-question-list-refresh nil t)
;; This is the default value, but we'll error out if the user has
;; set it to nil.
(setq tabulated-list-use-header-line t)
(tabulated-list-init-header)
(setq header-line-format sx-question-list--header-line))
(defcustom sx-question-list-date-sort-method 'last_activity_date
"Parameter which controls date sorting."
;; This should be made into a (choice ...) of constants.
:type 'symbol
;; Add a setter to protect the value.
:group 'sx-question-list)
(sx--create-comparator sx-question-list--date-more-recent-p
"Non-nil if tabulated-entry A is newer than B."
#'> (lambda (x)
(cdr (assq sx-question-list-date-sort-method (car x)))))
;;; Keybinds
;; We need this quote+eval combo because `kbd' was a macro in 24.2.
(mapc (lambda (x) (eval `(define-key sx-question-list-mode-map
(kbd ,(car x)) #',(cadr x))))
sx-question-list--key-definitions)
(sx--define-conditional-key sx-question-list-mode-map "O" #'sx-question-list-order-by
(and (boundp 'sx-question-list--order) sx-question-list--order))
(defun sx-question-list-hide (data)
"Hide question under point.
Non-interactively, DATA is a question alist."
(interactive
(list (if (derived-mode-p 'sx-question-list-mode)
(tabulated-list-get-id)
(sx-user-error "Not in `sx-question-list-mode'"))))
(sx-question--mark-hidden data)
;; The current entry will not be present after the list is
;; redisplayed. To avoid `tabulated-list-mode' getting lost (and
;; sending us to the top) we move to the next entry before
;; redisplaying.
(forward-line 1)
(when (called-interactively-p 'any)
(sx-question-list-refresh 'redisplay 'noupdate)))
(defun sx-question-list-mark-read (data)
"Mark as read question under point.
Non-interactively, DATA is a question alist."
(interactive
(list (if (derived-mode-p 'sx-question-list-mode)
(tabulated-list-get-id)
(sx-user-error "Not in `sx-question-list-mode'"))))
(sx-question--mark-read data)
(sx-question-list-next 1)
(when (called-interactively-p 'any)
(sx-question-list-refresh 'redisplay 'noupdate)))
(defun sx-question-list--unread-count ()
"Number of unread questions in current dataset, as a string."
(int-to-string
(cl-count-if-not
#'sx-question--read-p sx-question-list--dataset)))
(defun sx-question-list--remove-excluded-tags (question-list)
"Return QUESTION-LIST, with some questions removed.
Removes all questions hidden by the user, as well as those
containing a tag in `sx-question-list-excluded-tags'."
(cl-remove-if (lambda (q)
(or (sx-question--hidden-p q)
(cl-intersection (let-alist q .tags)
sx-question-list-excluded-tags
:test #'string=)))
question-list))
(defun sx-question-list-refresh (&optional redisplay no-update)
"Update the list of questions.
If REDISPLAY is non-nil (or if interactive), also call `tabulated-list-print'.
If the prefix argument NO-UPDATE is nil, query StackExchange for
a new list before redisplaying."
(interactive "p\nP")
;; Reset the mode-line unread count (we rebuild it here).
(unless no-update
(setq sx-question-list--pages-so-far 1))
(let* ((question-list
(or (and no-update sx-question-list--dataset)
(and (functionp sx-question-list--refresh-function)
(funcall sx-question-list--refresh-function))
(and (functionp sx-question-list--next-page-function)
(funcall sx-question-list--next-page-function 1))
sx-question-list--dataset))
;; Preserve window positioning.
(window (get-buffer-window (current-buffer)))
(old-start (when window (window-start window))))
;; The dataset contains everything. Hiding and filtering is done
;; on the `tabulated-list-entries' below.
(setq sx-question-list--dataset question-list)
;; Print the result.
(setq tabulated-list-entries
(mapcar sx-question-list--print-function
(sx-question-list--remove-excluded-tags
sx-question-list--dataset)))
(when redisplay
(tabulated-list-print 'remember))
(when window
(set-window-start window old-start)))
(sx-message "Done."))
(defun sx-question-list-view-previous (n)
"Move cursor up N questions up and display this question.
Displayed in `sx-question-mode--window', replacing any question
that may currently be there."
(interactive "p")
(sx-question-list-view-next (- n)))
(defun sx-question-list-view-next (n)
"Move cursor down N questions and display this question.
Displayed in `sx-question-mode--window', replacing any question
that may currently be there."
(interactive "p")
(sx-question-list-next n)
(sx-question-mode--display
(tabulated-list-get-id)
(sx-question-list--create-question-window)))
(defun sx-question-list--create-question-window ()
"Create or find a window where a question can be displayed.
If any current window displays a question, that window is
returned. If none do, a new one is created such that the
question-list window remains `sx-question-list-height' lines
high (if possible)."
(or (sx-question-mode--get-window)
;; Create a proper window.
(let ((window
(condition-case er
(split-window (selected-window) sx-question-list-height 'below)
(error
;; If the window is too small to split, use any one.
(if (string-match
"Window #<window .*> too small for splitting"
(car (cdr-safe er)))
(next-window)
(error (cdr er)))))))
;; Configure the window to be closed on `q'.
(set-window-prev-buffers window nil)
(set-window-parameter
window 'quit-restore
;; See (info "(elisp) Window Parameters")
`(window window ,(selected-window) ,sx-question-mode--buffer))
window)))
(defvar sx-question-list--last-refresh (current-time)
"Time of the latest refresh.")
(defun sx-question-list-next (n)
"Move cursor down N questions.
This does not update `sx-question-mode--window'."
(interactive "p")
(if (and (< n 0) (bobp))
(when (> (time-to-seconds
(time-subtract (current-time) sx-question-list--last-refresh))
1)
(sx-question-list-refresh 'redisplay)
(setq sx-question-list--last-refresh (current-time)))
(forward-line n)
;; If we were trying to move forward, but we hit the end.
(when (eobp)
;; Try to get more questions.
(sx-question-list-next-page))
(sx-question-list--ensure-line-good-line-position)))
(defun sx-question-list--ensure-line-good-line-position ()
"Scroll window such that current line is a good place.
Check if we're at least 6 lines from the bottom. Scroll up if
we're not. Do the same for 3 lines from the top."
;; At least one entry below us.
(let ((lines-to-bottom (count-screen-lines (point) (window-end))))
(unless (>= lines-to-bottom 6)
(recenter (- 6))))
;; At least one entry above us.
(let ((lines-to-top (count-screen-lines (point) (window-start))))
(unless (>= lines-to-top 3)
(recenter 3))))
(defun sx-question-list-next-page ()
"Fetch and display the next page of questions."
(interactive)
;; Stay at the last line.
(goto-char (point-max))
(forward-line -1)
(when (functionp sx-question-list--next-page-function)
;; Try to get more questions
(let ((list
(funcall sx-question-list--next-page-function
(1+ sx-question-list--pages-so-far))))
(if (null list)
(message "No further questions.")
;; If it worked, increment the variable.
(cl-incf sx-question-list--pages-so-far)
;; And update the dataset.
;; @TODO: Check for duplicates.
(setq sx-question-list--dataset
(append sx-question-list--dataset list))
(sx-question-list-refresh 'redisplay 'no-update)
(forward-line 1)))))
(defun sx-question-list-previous (n)
"Move cursor up N questions.
This does not update `sx-question-mode--window'."
(interactive "p")
(sx-question-list-next (- n)))
(defcustom sx-question-list-far-step-size 5
"How many questions `sx-question-list-next-far' skips."
:type 'integer
:group 'sx-question-list
:package-version '(sx-question-list . ""))
(defun sx-question-list-next-far (n)
"Move cursor up N*`sx-question-list-far-step-size' questions.
This does not update `sx-question-mode--window'."
(interactive "p")
(sx-question-list-next (* n sx-question-list-far-step-size)))
(defun sx-question-list-previous-far (n)
"Move cursor up N questions.
This does not update `sx-question-mode--window'."
(interactive "p")
(sx-question-list-next-far (- n)))
(defun sx-question-list-switch-site (site)
"Switch the current site to SITE and display its questions.
Retrieve completions from `sx-site-get-api-tokens'.
Sets `sx-question-list--site' and then call
`sx-question-list-refresh' with `redisplay'."
(interactive
(list (sx-completing-read
"Switch to site: " (sx-site-get-api-tokens)
(lambda (site) (not (equal site sx-question-list--site)))
t)))
(when (and (stringp site) (> (length site) 0))
(setq sx-question-list--site site)
(sx-question-list-refresh 'redisplay)))
(defun sx-question-list-order-by (sort &optional ascend)
"Order questions in the current list by the method SORT.
Sets `sx-question-list--order' and then calls
`sx-question-list-refresh' with `redisplay'.
With a prefix argument or a non-nil ASCEND, invert the sorting
order."
(interactive
(list (when sx-question-list--order
(sx-question-list--interactive-order-prompt))
current-prefix-arg))
(unless sx-question-list--order
(sx-user-error "This list can't be reordered"))
(when (and sort (symbolp sort))
(setq sx-question-list--order sort)
(setq sx-question-list--descending (not ascend))
(sx-question-list-refresh 'redisplay)))
(provide 'sx-question-list)
;;; sx-question-list.el ends here
;; Local Variables:
;; indent-tabs-mode: nil
;; End: