2026-06-05 22:18:39 +08:00
|
|
|
;;; 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)))
|
|
|
|
|
|
2026-06-08 01:28:25 +08:00
|
|
|
(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)))
|
2026-06-05 22:32:49 +08:00
|
|
|
(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)
|
2026-06-08 01:28:25 +08:00
|
|
|
(define-key lc-org-mode-map (kbd "C-c l") prefix)))
|
2026-06-05 22:32:49 +08:00
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(define-minor-mode lc-org-mode
|
|
|
|
|
"Minor mode for LeetCode CLI integration in org source blocks.
|
2026-06-08 01:28:25 +08:00
|
|
|
Keybindings under localleader l (SPC m l or , l):
|
2026-06-05 22:32:49 +08:00
|
|
|
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)
|
|
|
|
|
|
2026-06-05 22:18:39 +08:00
|
|
|
;;;###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
|