55; ; Author: Wang Kai <kaiwkx@gmail.com>
66; ; Keywords: extensions, tools
77; ; URL: https://github.com/kaiwk/leetcode.el
8- ; ; Package-Requires: ((emacs "26 .1") (s "1.13.0") (aio "1.0") (log4e "0.3.3"))
8+ ; ; Package-Requires: ((emacs "28 .1") (s "1.13.0") (aio "1.0") (log4e "0.3.3"))
99; ; Version: 0.1.27
1010
1111; ; This program is free software; you can redistribute it and/or modify
7171
7272(defun leetcode--install-my-cookie ()
7373 " Install leetcode dependencies."
74- (let ((async-shell-command-display-buffer t ))
74+ (let ((async-shell-command-display-buffer t )
75+ (pipx (executable-find " pipx" ))
76+ (python3 (executable-find " python3" ))
77+ (python (executable-find " python" )))
7578 (async-shell-command
76- " pip3 install my_cookies"
79+ (if pipx
80+ (format " %s install my_cookies" pipx)
81+ (format " %s -m venv --clear %s && %s /bin/pip3 install my_cookies"
82+ (or python3 python " python" ) ; require python environment
83+ leetcode-python-environment leetcode-python-environment))
7784 (get-buffer-create " *leetcode-install*" ))))
7885
86+ (defun leetcode--my-cookies-path ()
87+ " Find the path to the my_cookies executable."
88+ (or (executable-find (format " %s /bin/my_cookies" leetcode-python-environment))
89+ (executable-find " my_cookies" )))
90+
7991(defun leetcode--check-deps ()
8092 " Check if all dependencies installed."
81- (if (executable-find " my_cookies " )
93+ (if (leetcode--my-cookies-path )
8294 t
8395 (leetcode--install-my-cookie)
8496 nil ))
@@ -121,6 +133,11 @@ mysql, mssql, oraclesql."
121133 :group 'leetcode
122134 :type 'boolean )
123135
136+ (defcustom leetcode-python-environment (file-name-concat user-emacs-directory " leetcode-env" )
137+ " The path to the isolated python virtual-environment to use."
138+ :group 'leetcode
139+ :type 'directory )
140+
124141(cl-defstruct leetcode-user
125142 " A LeetCode User.
126143The object with following attributes:
@@ -216,6 +233,11 @@ Default is programming language.")
216233c, cpp, csharp, golang, java, javascript, typescript, kotlin, php, python,
217234python3, ruby, rust, scala, swift, mysql, mssql, oraclesql." )
218235
236+ (defconst leetcode--code-start " // code_start"
237+ " Code start mark in LeetCode description." )
238+ (defconst leetcode--code-end " // code_end"
239+ " Code end mark in LeetCode description." )
240+
219241(defvar leetcode--filter-status nil " Filter rows by status." )
220242(defvar leetcode--filter-regex nil " Filter rows by regex." )
221243(defvar leetcode--filter-tag nil " Filter rows by tag." )
@@ -377,15 +399,27 @@ query consolePanelConfig($titleSlug: String!) {
377399
378400; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Utils ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
379401
402+ (defun leetcode--insert-code-start-marker ()
403+ " Insert code start marker."
404+ (when (or (string= leetcode--lang " c" )
405+ (string= leetcode--lang " cpp" ))
406+ (insert (format " \n\n %s \n\n " leetcode--code-start))))
407+
408+ (defun leetcode--insert-code-end-marker ()
409+ " Insert code end marker."
410+ (when (or (string= leetcode--lang " c" )
411+ (string= leetcode--lang " cpp" ))
412+ (insert (format " \n\n %s \n\n " leetcode--code-end))))
413+
380414(defun leetcode--referer (value )
381415 " It will return an alist as the HTTP Referer Header.
382416VALUE should be the referer."
383417 (cons " Referer" value))
384418
385419(defun leetcode--cookie-get-all ()
386420 " Get leetcode session with `my_cookies' . You can install it with pip."
387- (let* ((my-cookies (executable-find " my_cookies " ))
388- (my-cookies-output (shell-command-to-string " my_cookies " ))
421+ (let* ((my-cookies (leetcode--my-cookies-path ))
422+ (my-cookies-output (shell-command-to-string (leetcode--my-cookies-path) ))
389423 (cookies-list (seq-filter (lambda (s ) (not (string-empty-p s)))
390424 (s-split " \n " my-cookies-output 'OMIT-NULLS )))
391425 (cookies-pairs (seq-map (lambda (s ) (s-split-up-to " " s 1 'OMIT-NULLS )) cookies-list)))
@@ -425,7 +459,7 @@ VALUE should be the referer."
425459 " Make TITLE a slug title.
426460Such as 'Two Sum' will be converted to 'two-sum'. 'Pow(x, n)' will be 'powx-n'"
427461 (let* ((str1 (replace-regexp-in-string " [\s -]+" " -" (downcase title)))
428- (res (replace-regexp-in-string " [(),]" " " str1)))
462+ (res (replace-regexp-in-string " [(),' ]" " " str1)))
429463 res))
430464
431465(defun leetcode--replace-in-buffer (regex to )
@@ -492,7 +526,11 @@ Such as 'Two Sum' will be converted to 'two-sum'. 'Pow(x, n)' will be 'powx-n'"
492526
493527(defun leetcode--code-buffer-data ()
494528 " Get code buffer content, that is, the `current-buffer' ."
495- (leetcode--buffer-content (current-buffer )))
529+ (let ((code (leetcode--buffer-content (current-buffer )))
530+ (pattern (concat leetcode--code-start " \\ ([\0 -\3 77]*?\\ )" leetcode--code-end)))
531+ (if (string-match pattern code)
532+ (match-string 1 code)
533+ code)))
496534
497535(defun leetcode--get-slug-title (code-buf )
498536 " Get slug title before try or submit with CODE-BUF.
@@ -560,7 +598,7 @@ of QUERY-NAME."
560598 :acceptance (format " %. 1f%% " .acRate)
561599 :frequency (format " %. 1f%% " .freqBar)
562600 :difficulty .difficulty
563- :paid-only (eq .paidOnly :json-true )
601+ :paid-only (eq .paidOnly t )
564602 :tags (seq-reduce (lambda (tags tag )
565603 (let-alist tag
566604 (push .slug tags)))
@@ -734,7 +772,7 @@ Return a list of rows, each row is a vector:
734772 (p-title (concat
735773 (leetcode-problem-title p)
736774 " "
737- (if (eq ( leetcode-problem-paid-only p) t )
775+ (if (leetcode-problem-paid-only p)
738776 (leetcode--add-font-lock leetcode--paid 'leetcode-paid-face )
739777 " " )))
740778 (p-acceptance (leetcode-problem-acceptance p))
@@ -940,11 +978,12 @@ row."
940978 (goto-char url-http-end-of-headers)
941979 (let-alist (json-read )
942980 (let ((qid .data.activeDailyCodingChallengeQuestion.question.qid))
943- (leetcode-show-problem ( string-to-number qid) ))))))
981+ (leetcode-show-problem qid))))))
944982
945983(aio-defun leetcode-try ()
946984 " Asynchronously test the code using customized testcase."
947985 (interactive )
986+ (leetcode-restore-layout)
948987 (aio-await (leetcode--ensure-login t ))
949988 (let* ((title-slug (leetcode--get-slug-title (current-buffer )))
950989 (problem (leetcode--get-problem title-slug))
@@ -955,9 +994,10 @@ row."
955994(aio-defun leetcode-submit ()
956995 " Asynchronously submit the code and show result."
957996 (interactive )
997+ (leetcode-restore-layout)
958998 (aio-await (leetcode--ensure-login t ))
959999 (let* ((code-buf (current-buffer ))
960- (code (leetcode--buffer-content code-buf ))
1000+ (code (leetcode--code-buffer-data ))
9611001 (slug-title (leetcode--get-slug-title code-buf))
9621002 (problem (leetcode--get-problem slug-title))
9631003 (problem-id (leetcode-problem-id problem))
@@ -1045,6 +1085,10 @@ alist specified in `display-buffer-alist'."
10451085 (goto-char (point-max ))
10461086 (cond
10471087 ((eq .status_code 10 )
1088+ (if (equal .code_answer .expected_code_answer)
1089+ (insert (leetcode--add-font-lock " PASS: " 'leetcode-accepted-face ))
1090+ (insert (leetcode--add-font-lock " FAIL: " 'leetcode-error-face )))
1091+ (insert " \n\n " )
10481092 ; ; Code Answer
10491093 (insert " Code Answer:\n " )
10501094 (dotimes (i (length .code_answer))
@@ -1061,8 +1105,15 @@ alist specified in `display-buffer-alist'."
10611105 (dotimes (i (length .std_output_list))
10621106 (when (aref .std_output_list i)
10631107 (insert (aref .std_output_list i))))))
1064- ((eq .status_code 14 )
1065- (insert .status_msg))
1108+ ((or (eq .status_code 12 ) (eq .status_code 14 ))
1109+ (insert (format " Status: %s \n\n "
1110+ (leetcode--add-font-lock
1111+ (format " %s (%s /%s )" .status_msg .total_correct .total_testcases)
1112+ 'leetcode-error-face )))
1113+ (insert (format " Test Case: \n %s \n\n " .last_testcase))
1114+ (insert (format " Expected Answer: %s \n\n " .expected_output))
1115+ (unless (string-empty-p .std_output)
1116+ (insert (format " Stdout: \n %s \n " .std_output))))
10661117 ((eq .status_code 15 )
10671118 (insert (leetcode--add-font-lock .status_msg 'leetcode-error-face ))
10681119 (insert " \n\n " )
@@ -1114,9 +1165,12 @@ STATUS_CODE has following possible value:
11141165 (insert (format " Last Test Case: %s \n " .last_testcase)))
11151166 ((eq .status_code 14 )
11161167 (insert (format " Status: %s " (leetcode--add-font-lock .status_msg 'leetcode-error-face )))
1117- (insert " \n " ))
1168+ (insert (format " \n\n %s / %s testcases passed\n " .total_correct .total_testcases))
1169+ (insert (format " Last Test Case: %s \n " .last_testcase)))
11181170 ((eq .status_code 15 )
11191171 (insert (format " Status: %s " (leetcode--add-font-lock .status_msg 'leetcode-error-face )))
1172+ (insert (format " \n\n %s / %s testcases passed\n " .total_correct .total_testcases))
1173+ (insert (format " Last Test Case: %s \n " .last_testcase))
11201174 (insert " \n\n " )
11211175 (insert (format .full_runtime_error)))
11221176 ((eq .status_code 20 )
@@ -1184,7 +1238,8 @@ Get problem by id and use `shr-render-buffer' to render problem
11841238detail. This action will show the detail in other window and jump
11851239to it."
11861240 (interactive (list (read-string " Show problem by problem id: "
1187- (leetcode--get-current-problem-id))))
1241+ (when (derived-mode-p 'leetcode--problems-mode )
1242+ (leetcode--get-current-problem-id)))))
11881243 (let* ((problem (leetcode--get-problem-by-id problem-id))
11891244 (title-slug (leetcode-problem-title-slug problem))
11901245 (problem-with-title (aio-await (leetcode--ensure-question-title problem)))
@@ -1267,8 +1322,9 @@ Call `leetcode-solve-problem' on the current problem id."
12671322
12681323(defun leetcode--kill-buff-and-delete-window (buf )
12691324 " Kill BUF and delete its window."
1270- (delete-windows-on buf t )
1271- (kill-buffer buf))
1325+ (when buf
1326+ (delete-windows-on buf t )
1327+ (kill-buffer buf)))
12721328
12731329(defun leetcode-quit ()
12741330 " Close and delete leetcode related buffers and windows."
@@ -1283,7 +1339,8 @@ Call `leetcode-solve-problem' on the current problem id."
12831339 (leetcode--kill-buff-and-delete-window (get-buffer (leetcode--detail-buffer-name problem-id)))
12841340 (leetcode--kill-buff-and-delete-window (get-buffer (leetcode--result-buffer-name problem-id)))
12851341 (leetcode--kill-buff-and-delete-window (get-buffer (leetcode--testcase-buffer-name problem-id)))))
1286- leetcode--problem-titles))
1342+ leetcode--problem-titles)
1343+ (setq leetcode--problem-titles '()))
12871344
12881345(defun leetcode--set-lang (snippets )
12891346 " Set `leetcode--lang' based on langSlug in SNIPPETS."
@@ -1369,7 +1426,9 @@ major mode by `leetcode-prefer-language'and `auto-mode-alist'."
13691426 (equal (leetcode-snippet-lang-slug s) leetcode--lang))
13701427 snippets))
13711428 (template-code (leetcode-snippet-code snippet)))
1429+ (leetcode--insert-code-start-marker)
13721430 (insert template-code)
1431+ (leetcode--insert-code-end-marker)
13731432 (leetcode--replace-in-buffer " " " " )))
13741433 (funcall (assoc-default suffix auto-mode-alist #'string-match-p ))
13751434 (leetcode-solution-mode t ))
@@ -1392,7 +1451,7 @@ major mode by `leetcode-prefer-language'and `auto-mode-alist'."
13921451 " This command should be run in LeetCode code buffer.
13931452It will restore the layout based on current buffer's name."
13941453 (interactive )
1395- (let* ((slug-title (file-name-sans-extension ( buffer-name )))
1454+ (let* ((slug-title (leetcode--get-slug-title ( current-buffer )))
13961455 (problem (leetcode--get-problem slug-title))
13971456 (problem-id (leetcode-problem-id problem))
13981457 (desc-buf (get-buffer (leetcode--detail-buffer-name problem-id)))
@@ -1401,6 +1460,9 @@ It will restore the layout based on current buffer's name."
14011460 (leetcode--solving-window-layout)
14021461 (unless desc-buf
14031462 (aio-await (leetcode-show-problem problem-id)))
1463+ (with-current-buffer result-buf
1464+ (erase-buffer )
1465+ (insert " Waiting for result..." ))
14041466 (display-buffer desc-buf
14051467 '((display-buffer-reuse-window
14061468 leetcode--display-detail)
0 commit comments