Skip to content

Commit 9f6cbb4

Browse files
committed
feat: filter problems by regex or tag
close: #39
1 parent 296a9df commit 9f6cbb4

File tree

3 files changed

+137
-34
lines changed

3 files changed

+137
-34
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
2019-10-09 Wang Kai <[email protected]>
2+
3+
* leetcode.el (leetcode--fetch-problem): filter problems by regex or tag
4+
15
2019-09-06 Wang Kai <[email protected]>
26

37
* leetcode.el: use emacs-aio and remove request.el and

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,16 @@ All supported languages can be found in variable `leetcode--prefer-language-suff
3434

3535
In leetcode problems list buffer:
3636

37-
| keymap | command |
38-
|--------|----------------------------------|
39-
| n | cursor move down |
40-
| p | cursor move up |
41-
| RET | show current problem description |
37+
| keymap | command |
38+
|--------|----------------------------------------|
39+
| n | cursor move down |
40+
| p | cursor move up |
41+
| s | filter problem by regex |
42+
| t | filter problem by tag |
43+
| / | clear filters |
44+
| g | refresh without fetching from LeetCode |
45+
| G | refresh all data |
46+
| RET | show current problem description |
4247

4348
2. Press `<RET>`, show problem description, move cursor to "solve it", press `<RET>` again, start coding!
4449

leetcode.el

Lines changed: 123 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -61,33 +61,41 @@ The object with following attributes:
6161
:medium Number
6262
:hard Number")
6363

64-
(defvar leetcode--problems nil
64+
(defvar leetcode--all-problems nil
6565
"Problems info with a list of problem object.
6666
The object with following attributes:
6767
:num Number
6868
:tag String
6969
:problems List
70+
7071
The elements of :problems has attributes:
7172
:status String
7273
:id Number
7374
:pos Number
7475
:title String
7576
:acceptance String
7677
:difficulty Number {1,2,3}
77-
:paid-only Boolean {t|nil}")
78+
:paid-only Boolean {t|nil}
79+
:tags List")
80+
81+
(defvar leetcode--all-tags nil
82+
"Problems tags")
7883

7984
(defvar leetcode--problem-titles nil
8085
"Problem titles that have been open in solving layout.")
8186

82-
(defvar leetcode-checkmark "" "Checkmark for accepted problem.")
87+
(defvar leetcode--filter-regex nil "Filter rows by regex.")
88+
(defvar leetcode--filter-tag nil "Filter rows by leetcode tag.")
89+
90+
(defconst leetcode--checkmark "" "Checkmark for accepted problem.")
8391
(defconst leetcode--buffer-name "*leetcode*")
8492
(defconst leetcode--description-buffer-name "*leetcode-description*")
8593
(defconst leetcode--testcase-buffer-name "*leetcode-testcase*")
8694
(defconst leetcode--result-buffer-name "*leetcode-result*")
8795

8896
(defface leetcode-checkmark-face
8997
'((t (:foreground "#5CB85C")))
90-
"Face for `leetcode-checkmark'"
98+
"Face for `leetcode--checkmark'"
9199
:group 'leetcode)
92100

93101
(defface leetcode-easy-face
@@ -121,13 +129,16 @@ The elements of :problems has attributes:
121129
(defconst leetcode--api-root (concat leetcode--base-url "/api"))
122130
(defconst leetcode--api-graphql (concat leetcode--base-url "/graphql"))
123131
(defconst leetcode--api-all-problems (concat leetcode--api-root "/problems/all/"))
132+
(defconst leetcode--api-all-tags (concat leetcode--base-url "/problems/api/tags"))
124133
;; submit
125134
(defconst leetcode--api-submit (concat leetcode--base-url "/problems/%s/submit/"))
126135
(defconst leetcode--api-problems-submission (concat leetcode--base-url "/problems/%s/submissions/"))
127136
(defconst leetcode--api-check-submission (concat leetcode--base-url "/submissions/detail/%s/check/"))
128137
;; try testcase
129138
(defconst leetcode--api-try (concat leetcode--base-url "/problems/%s/interpret_solution/"))
130139

140+
(defun to-list (vec)
141+
(append vec '()))
131142

132143
(defun leetcode--referer (value)
133144
"It will return an alist as the HTTP Referer Header.
@@ -219,20 +230,21 @@ When ACCOUNT or PASSWORD is empty string it will show a prompt."
219230
"LEETCODE_SESSION"))
220231
(url-cookie-retrieve leetcode--domain "/" t)))))
221232

222-
(defun leetcode--set-user-and-problems (res)
223-
"Set `leetcode--user' and `leetcode--problems'.
224-
If user isn't login, only `leetcode--problems' will be set. RES
225-
is an alist comes from `leetcode--api-all-problems'."
233+
(defun leetcode--set-user-and-problems (user-and-problems)
234+
"Set `leetcode--user' and `leetcode--all-problems'.
235+
If user isn't login, only `leetcode--all-problems' will be set.
236+
USER-AND-PROBLEMS is an alist comes from
237+
`leetcode--api-all-problems'."
226238
;; user
227-
(let-alist res
239+
(let-alist user-and-problems
228240
(setq leetcode--user
229241
(list :username .user_name
230242
:solved .num_solved
231243
:easy .ac_easy
232244
:medium .ac_medium
233245
:hard .ac_hard))
234246
;; problem list
235-
(setq leetcode--problems
247+
(setq leetcode--all-problems
236248
(list
237249
:num .num_total
238250
:tag "all"
@@ -257,6 +269,24 @@ is an alist comes from `leetcode--api-all-problems'."
257269
problems)))
258270
problems)))))
259271

272+
(defun leetcode--set-tags (all-tags)
273+
"Set `leetcode--all-tags' and `leetcode--all-problems' with
274+
ALL-TAGS."
275+
(let-alist all-tags
276+
;; set problems tags
277+
(dolist (problem (plist-get leetcode--all-problems :problems))
278+
(dolist (topic (to-list .topics))
279+
(let-alist topic
280+
(when (member (plist-get problem :id) (to-list .questions))
281+
(let ((cur-tags (plist-get problem :tags)))
282+
(push .slug cur-tags)
283+
(plist-put problem :tags cur-tags))))))
284+
;; set leetcode--all-tags
285+
(dolist (topic (to-list .topics))
286+
(let-alist topic
287+
(unless (member topic leetcode--all-tags)
288+
(push .slug leetcode--all-tags))))))
289+
260290
(defun leetcode--slugify-title (title)
261291
"Make TITLE a slug title.
262292
Such as 'Two Sum' will be converted to 'two-sum'."
@@ -339,10 +369,10 @@ row."
339369
header-names widths))))
340370

341371
(defun leetcode--problems-rows ()
342-
"Generate tabulated list rows from `leetcode--problems'.
372+
"Generate tabulated list rows from `leetcode--all-problems'.
343373
Return a list of rows, each row is a vector:
344374
\([<checkmark> <position> <title> <acceptance> <difficulty>] ...)"
345-
(let ((problems (reverse (plist-get leetcode--problems :problems)))
375+
(let ((problems (reverse (plist-get leetcode--all-problems :problems)))
346376
(easy-tag "easy")
347377
(medium-tag "medium")
348378
(hard-tag "hard")
@@ -351,15 +381,20 @@ Return a list of rows, each row is a vector:
351381
(setq rows
352382
(cons
353383
(vector
384+
;; status
354385
(if (equal (plist-get p :status) "ac")
355-
(prog1 leetcode-checkmark
386+
(prog1 leetcode--checkmark
356387
(put-text-property
357-
0 (length leetcode-checkmark)
358-
'font-lock-face 'leetcode-checkmark-face leetcode-checkmark))
388+
0 (length leetcode--checkmark)
389+
'font-lock-face 'leetcode-checkmark-face leetcode--checkmark))
359390
" ")
391+
;; position
360392
(number-to-string (plist-get p :pos))
393+
;; title
361394
(plist-get p :title)
395+
;; acceptance
362396
(plist-get p :acceptance)
397+
;; difficulty
363398
(cond
364399
((eq 1 (plist-get p :difficulty))
365400
(prog1 easy-tag
@@ -375,13 +410,60 @@ Return a list of rows, each row is a vector:
375410
(prog1 hard-tag
376411
(put-text-property
377412
0 (length hard-tag)
378-
'font-lock-face 'leetcode-hard-face hard-tag)))))
413+
'font-lock-face 'leetcode-hard-face hard-tag))))
414+
;; tags
415+
(string-join (plist-get p :tags) ", "))
379416
rows)))
380417
rows))
381418

382-
(aio-defun leetcode--fetch-user-and-problems ()
383-
"Refresh problems and update `tabulated-list-entries'."
419+
(defun leetcode--filter (rows)
420+
"Filter ROWS by `leetcode--filter-regex' and `leetcode--filter-tag'"
421+
(seq-filter
422+
(lambda (row)
423+
(and
424+
(if leetcode--filter-regex
425+
(let ((title (aref row 2)))
426+
(string-match-p leetcode--filter-regex title))
427+
t)
428+
(if leetcode--filter-tag
429+
(let ((tags (split-string (aref row 5) ",")))
430+
(member leetcode--filter-tag tags))
431+
t)))
432+
rows))
433+
434+
(defun leetcode-reset-filter ()
435+
"Reset filter."
436+
(interactive)
437+
(setq leetcode--filter-regex nil)
438+
(setq leetcode--filter-tag nil)
439+
(leetcode-refresh))
440+
441+
(defun leetcode-set-filter-regex (regex)
442+
"Set `leetcode--filter-regex' as REGEX."
443+
(interactive "sSearch: ")
444+
(setq leetcode--filter-regex regex)
445+
(leetcode-refresh))
446+
447+
(defun leetcode-set-filter-tag ()
448+
"Set `leetcode--filter-tag'"
384449
(interactive)
450+
(setq leetcode--filter-tag
451+
(completing-read "Tags: " leetcode--all-tags))
452+
(leetcode-refresh))
453+
454+
(aio-defun leetcode--fetch-all-tags ()
455+
(let* ((url-request-method "GET")
456+
(url-request-extra-headers
457+
`(,leetcode--User-Agent
458+
,leetcode--X-Requested-With
459+
,(leetcode--referer leetcode--url-login)))
460+
(result (aio-await (aio-url-retrieve leetcode--api-all-tags))))
461+
(with-current-buffer (cdr result)
462+
(goto-char url-http-end-of-headers)
463+
(json-read))))
464+
465+
(aio-defun leetcode--fetch-user-and-problems ()
466+
"Fetch user and problems info."
385467
(if leetcode--loading-mode
386468
(message "LeetCode has been refreshing...")
387469
(leetcode--loading-mode t)
@@ -398,15 +480,11 @@ Return a list of rows, each row is a vector:
398480
(goto-char url-http-end-of-headers)
399481
(json-read))))))
400482

401-
(aio-defun leetcode-refresh ()
402-
"Refresh problems and update `tabulated-list-entries'."
483+
(defun leetcode-refresh ()
484+
"Make `tabulated-list-entries'."
403485
(interactive)
404-
(if-let ((users-and-problems
405-
(aio-await (leetcode--fetch-user-and-problems))))
406-
(leetcode--set-user-and-problems users-and-problems)
407-
(message "LeetCode parse user and problems failed"))
408-
(let* ((header-names '(" " "#" "Problem" "Acceptance" "Difficulty"))
409-
(rows (leetcode--problems-rows))
486+
(let* ((header-names '(" " "#" "Problem" "Acceptance" "Difficulty" "Tags"))
487+
(rows (leetcode--filter (leetcode--problems-rows)))
410488
(headers (leetcode--make-tabulated-headers header-names rows)))
411489
(with-current-buffer (get-buffer-create leetcode--buffer-name)
412490
(leetcode--problems-mode)
@@ -420,13 +498,25 @@ Return a list of rows, each row is a vector:
420498
(tabulated-list-print t)
421499
(leetcode--loading-mode -1))))
422500

501+
(aio-defun leetcode-refresh-fetch ()
502+
"Refresh problems and update `tabulated-list-entries'."
503+
(interactive)
504+
(if-let ((users-and-problems (aio-await (leetcode--fetch-user-and-problems)))
505+
(all-tags (aio-await (leetcode--fetch-all-tags))))
506+
(progn
507+
(leetcode--set-user-and-problems users-and-problems)
508+
(leetcode--set-tags all-tags))
509+
(message "LeetCode parse user and problems failed"))
510+
(leetcode-reset-filter)
511+
(leetcode-refresh))
512+
423513
(aio-defun leetcode--async ()
424514
"Show leetcode problems buffer."
425515
(if (get-buffer leetcode--buffer-name)
426516
(switch-to-buffer leetcode--buffer-name)
427517
(unless (leetcode--login-p)
428518
(aio-await (leetcode--login)))
429-
(aio-await (leetcode-refresh))
519+
(aio-await (leetcode-refresh-fetch))
430520
(switch-to-buffer leetcode--buffer-name)))
431521

432522
;;;###autoload
@@ -454,7 +544,7 @@ see: https://github.com/skeeto/emacs-aio/issues/3."
454544
(equal slug-title
455545
(leetcode--slugify-title
456546
(plist-get p :title))))
457-
(plist-get leetcode--problems :problems))
547+
(plist-get leetcode--all-problems :problems))
458548
:id)))
459549
(let* ((url-request-method "POST")
460550
(url-request-extra-headers
@@ -650,7 +740,7 @@ following possible value:
650740
(equal slug-title
651741
(leetcode--slugify-title
652742
(plist-get p :title))))
653-
(plist-get leetcode--problems :problems))
743+
(plist-get leetcode--all-problems :problems))
654744
:id)))
655745
(let* ((url-request-method "POST")
656746
(url-request-extra-headers
@@ -837,7 +927,11 @@ for current problem."
837927
(define-key map (kbd "RET") #'leetcode-show-current-problem)
838928
(define-key map "n" #'next-line)
839929
(define-key map "p" #'previous-line)
930+
(define-key map "s" #'leetcode-set-filter-regex)
931+
(define-key map "t" #'leetcode-set-filter-tag)
840932
(define-key map "g" #'leetcode-refresh)
933+
(define-key map "G" #'leetcode-refresh-fetch)
934+
(define-key map "/" #'leetcode-reset-filter)
841935
(define-key map "q" #'quit-window)))
842936
"Keymap for `leetcode--problems-mode'.")
843937

0 commit comments

Comments
 (0)