Files
tomatocream e10cc4257d add flashcard generation tooling and binary search cards
- gen-flashcards.py: auto-generate recognition cards from all problem files
- toolkit/gen-problem-cards.org: 199 auto-generated problem cards
- 5 binary search tool cards (std::binary_search, std::lower_bound, comparison, two-sum pattern, sorting gotcha)
- two-sum.org: add binary search C++ attempt
- lc-org.el: add doom emacs localleader keybinding support
2026-06-08 11:38:09 +08:00

188 lines
7.7 KiB
EmacsLisp

;;; lc-org.el --- LeetCode CLI integration for org source blocks -*- lexical-binding: t; -*-
;; Usage:
;; In an org source block:
;; #+BEGIN_SRC python :lc-problem two-sum
;; class Solution:
;; def twoSum(self, nums, target):
;; ...
;; #+END_SRC
;;
;; M-x lc-org-submit — submit the block's code
;; M-x lc-org-run — run with test input
;; M-x lc-org-status — check last submission
;;
;; Header args:
;; :lc-problem SLUG — problem number or slug (required)
;; :lc-lang LANG — language (default: python3)
;; :lc-input INPUT — test input for `lc run`
(defvar lc-org-bin "lc"
"Path to the `lc` CLI binary.
Set this if `lc` is not on PATH, e.g. \"uv run lc\" or \"/home/you/.local/bin/lc\".")
(defvar lc-org-default-lang "python3"
"Default language when :lc-lang is not specified in the source block.")
(defun lc-org--block-params ()
"Extract lc parameters and code from the current org source block.
Returns a plist (:problem :lang :input :code) or signals an error."
(let* ((info (org-babel-get-src-block-info t))
(lang (nth 0 info))
(body (nth 1 info))
(params (nth 2 info))
(problem (cdr (assq :lc-problem params)))
(lc-lang (or (cdr (assq :lc-lang params))
(cond
((string= lang "python") "python3")
((string= lang "python3") "python3")
((string= lang "cpp") "cpp")
((string= lang "c++") "cpp")
((string= lang "c") "c")
((string= lang "java") "java")
((string= lang "javascript") "javascript")
((string= lang "typescript") "typescript")
((string= lang "rust") "rust")
((string= lang "go") "golang")
((string= lang "golang") "golang")
(t lc-org-default-lang))))
(input (cdr (assq :lc-input params))))
(unless problem
(user-error "No :lc-problem header arg on this source block"))
(list :problem (format "%s" problem) :lang lc-lang :input input :code body)))
(defun lc-org--run-command (cmd)
"Run CMD in a compilation buffer named *lc*."
(let ((buf (get-buffer-create "*lc*")))
(with-current-buffer buf
(read-only-mode -1)
(erase-buffer)
(insert "$ " cmd "\n\n"))
(make-process
:name "lc"
:buffer buf
:command (list shell-file-name shell-command-switch cmd)
:sentinel (lambda (proc _event)
(when (memq (process-status proc) '(exit signal))
(with-current-buffer (process-buffer proc)
(goto-char (point-max))
(insert "\n--- finished (exit " (number-to-string (process-exit-code proc)) ") ---\n")
(read-only-mode 1))
(display-buffer (process-buffer proc)))))
(display-buffer buf)))
(defvar lc-org-mode-map (make-sparse-keymap)
"Keymap for `lc-org-mode'.")
(when (and (boundp 'doom-version) (fboundp 'map!))
(map! :map lc-org-mode-map
:localleader
:prefix "l"
"s" #'lc-org-submit
"r" #'lc-org-run
"t" #'lc-org-status
"p" #'lc-org-show-problem
"d" #'lc-org-daily))
(unless (and (boundp 'doom-version) (fboundp 'map!))
(let ((prefix (make-sparse-keymap)))
(define-key prefix (kbd "s") #'lc-org-submit)
(define-key prefix (kbd "r") #'lc-org-run)
(define-key prefix (kbd "t") #'lc-org-status)
(define-key prefix (kbd "p") #'lc-org-show-problem)
(define-key prefix (kbd "d") #'lc-org-daily)
(define-key lc-org-mode-map (kbd "C-c l") prefix)))
;;;###autoload
(define-minor-mode lc-org-mode
"Minor mode for LeetCode CLI integration in org source blocks.
Keybindings under localleader l (SPC m l or , l):
s submit — submit current block
r run — run with test input
t status — check submission status
p problem — show problem description
d daily — today's daily challenge"
:lighter " LC"
:keymap lc-org-mode-map)
;;;###autoload
(defun lc-org-submit ()
"Submit the current org source block to LeetCode."
(interactive)
(let* ((params (lc-org--block-params))
(problem (plist-get params :problem))
(lang (plist-get params :lang))
(code (plist-get params :code))
(tmpfile (make-temp-file "lc-org-" nil
(concat "." (cond ((string= lang "python3") "py")
((string= lang "cpp") "cpp")
((string= lang "c") "c")
((string= lang "java") "java")
((string= lang "javascript") "js")
((string= lang "typescript") "ts")
((string= lang "rust") "rs")
((string= lang "golang") "go")
(t "txt"))))))
(with-temp-file tmpfile (insert code))
(lc-org--run-command (format "%s submit %s --lang %s --file %s"
lc-org-bin
(shell-quote-argument problem)
(shell-quote-argument lang)
(shell-quote-argument tmpfile)))))
;;;###autoload
(defun lc-org-run ()
"Run the current org source block against test input.
Input is taken from :lc-input header arg, or prompted interactively."
(interactive)
(let* ((params (lc-org--block-params))
(problem (plist-get params :problem))
(lang (plist-get params :lang))
(code (plist-get params :code))
(input (or (plist-get params :input)
(read-string "Test input: ")))
(tmpfile (make-temp-file "lc-org-" nil
(concat "." (cond ((string= lang "python3") "py")
((string= lang "cpp") "cpp")
((string= lang "c") "c")
((string= lang "java") "java")
((string= lang "javascript") "js")
((string= lang "typescript") "ts")
((string= lang "rust") "rs")
((string= lang "golang") "go")
(t "txt"))))))
(with-temp-file tmpfile (insert code))
(lc-org--run-command (format "%s run %s --lang %s --file %s --input %s"
lc-org-bin
(shell-quote-argument problem)
(shell-quote-argument lang)
(shell-quote-argument tmpfile)
(shell-quote-argument input)))))
;;;###autoload
(defun lc-org-status (submission-id)
"Check the status of a LeetCode submission by SUBMISSION-ID."
(interactive "sSubmission ID: ")
(lc-org--run-command (format "%s status %s"
lc-org-bin
(shell-quote-argument submission-id))))
;;;###autoload
(defun lc-org-show-problem ()
"Show the problem description for the :lc-problem on the current block."
(interactive)
(let* ((params (lc-org--block-params))
(problem (plist-get params :problem)))
(lc-org--run-command (format "%s problems show %s"
lc-org-bin
(shell-quote-argument problem)))))
;;;###autoload
(defun lc-org-daily ()
"Show today's daily challenge."
(interactive)
(lc-org--run-command (format "%s problems daily" lc-org-bin)))
(provide 'lc-org)
;;; lc-org.el ends here