From 2fb72671c096779830912dbe84ba704ce695d879 Mon Sep 17 00:00:00 2001 From: verdant Date: Sat, 2 May 2026 00:17:22 +0800 Subject: refactor: migrate configuration to a single org-mode file --- .lsp-session-v1 | 2 +- bookmarks.eld | 24 +- config.el | 461 ++++++++++++++ config.org | 548 ++++++++++++++++ core/core-basic.el | 54 -- core/core-dired.el | 45 -- core/core-editing.el | 25 - core/core-ui.el | 21 - core/core.el | 5 - init.el | 21 +- packages/.hugo_build.lock | 0 packages/packages-editing.el | 95 --- packages/packages-email.el | 153 ----- packages/packages-leetcode.el | 1386 ----------------------------------------- packages/packages-misc.el | 8 - packages/packages-ui.el | 41 -- packages/packages.el | 14 - tramp | 2 +- 18 files changed, 1042 insertions(+), 1863 deletions(-) create mode 100644 config.el create mode 100644 config.org delete mode 100644 core/core-basic.el delete mode 100644 core/core-dired.el delete mode 100644 core/core-editing.el delete mode 100644 core/core-ui.el delete mode 100644 core/core.el delete mode 100644 packages/.hugo_build.lock delete mode 100644 packages/packages-editing.el delete mode 100644 packages/packages-email.el delete mode 100644 packages/packages-leetcode.el delete mode 100644 packages/packages-misc.el delete mode 100644 packages/packages-ui.el delete mode 100644 packages/packages.el diff --git a/.lsp-session-v1 b/.lsp-session-v1 index f6abe7e..f00efc3 100644 --- a/.lsp-session-v1 +++ b/.lsp-session-v1 @@ -1 +1 @@ -#s(lsp-session ("/tmp") nil #s(hash-table test equal) #s(hash-table test equal) #s(hash-table test equal)) \ No newline at end of file +#s(lsp-session ("/home/verdant/blog" "/home/verdant/dev/Leetcode" "/tmp") nil #s(hash-table test equal) #s(hash-table test equal) #s(hash-table test equal)) \ No newline at end of file diff --git a/bookmarks.eld b/bookmarks.eld index 64a3eb1..a0f1d6e 100644 --- a/bookmarks.eld +++ b/bookmarks.eld @@ -2,12 +2,24 @@ ;;; This format is meant to be slightly human-readable; ;;; nevertheless, you probably don't want to edit it. ;;; -*- End Of Bookmark File Format Version Stamp -*- -((".emacs.d" - (filename . "~/.emacs.d/") - (front-context-string . "core\n -rw-r--r-") - (rear-context-string . "96 Apr 24 23:58 ") - (position . 274) - (last-modified 27116 5874 383590 841000)) +(("sicp" + (filename . "~/dev/SICP/") + (front-context-string . " -rw-r--r-- 1 ") + (rear-context-string . "Apr 26 09:44 ..\n") + (position . 138) + (last-modified 27117 36230 984757 78000)) +("leetcode" + (filename . "~/dev/Leetcode/") + (front-context-string . "3783\n drwxr-xr-") + (rear-context-string . "96 Apr 22 22:46 ") + (position . 965) + (last-modified 27116 14552 772552 90000)) +(".emacs.d" + (filename . "~/.emacs.d/") + (front-context-string . "core\n -rw-r--r-") + (rear-context-string . "96 Apr 24 23:58 ") + (position . 274) + (last-modified 27116 5874 383590 841000)) ("blog" (filename . "~/blog/") (front-context-string . "archetypes\n drw") diff --git a/config.el b/config.el new file mode 100644 index 0000000..ed48203 --- /dev/null +++ b/config.el @@ -0,0 +1,461 @@ +;; -*- lexical-binding: t; -*- +(use-package evil + :init + (setq evil-want-keybinding nil + evil-want-C-u-scroll t) + :config + (evil-mode) + :ensure t) + +(use-package evil-collection + :after evil + :ensure t + :config + (evil-collection-init)) + +(use-package olivetti) + +(use-package yasnippet + :ensure t + :config + (yas-global-mode 1)) + +(use-package ivy + :ensure t + :init + (ivy-mode 1) + (counsel-mode 1) + :config + (setq ivy-use-selectable-prompt t) + (setq ivy-use-preview t) + (setq ivy-fixed-height-minibuffer t) + (setq ivy-use-preview t) + (setq ivy-use-virtual-buffers t) + (setq search-default-mode #'char-fold-to-regexp) + (setq ivy-count-format "(%d/%d) ") + (setq ivy-initial-inputs-alist nil) ;; 不预设初始输入 + (setq ivy-use-selectable-prompt t) ;; 允许选择提示 + :bind + (("C-s" . 'swiper) + ("C-x b" . 'ivy-switch-buffer) + ("C-c v" . 'ivy-push-view) + ("C-c s" . 'ivy-switch-view) + ("C-c V" . 'ivy-pop-view) + ("C-x C-@" . 'counsel-mark-ring); 在某些终端上 C-x C-SPC 会被映射为 C-x C-@,比如在 macOS 上,所以要手动设置 + ("C-x C-SPC" . 'counsel-mark-ring) + :map minibuffer-local-map + ("C-r" . counsel-minibuffer-history))) + +;; (use-package ivy-posframe +;; :ensure t +;; :config +;; (ivy-posframe-mode t)) + +(use-package ivy-rich + :ensure t + :config + (ivy-rich-mode t)) + +;; 启用 company-mode 全局补全 +(use-package company + :ensure t + :config + (setq company-idle-delay 0.0 + company-minimum-prefix-length 1) + (global-company-mode) + + (with-eval-after-load 'company + ;; 补全列表背景 + (set-face-attribute 'company-tooltip nil + :foreground "white" :background "gray20") + ;; 选中项背景 + (set-face-attribute 'company-tooltip-selection nil + :foreground "blue" :background "gray20") + ;; 输入前缀高亮 + (set-face-attribute 'company-tooltip-common nil + :foreground "orange" :background "gray20") + ;; 右侧注释/类型 + (set-face-attribute 'company-tooltip-annotation nil + :foreground "cyan" :background "gray20"))) + +(use-package lsp-mode + :ensure t + :init + (setq read-process-output-max (* 1024 1024)) + :hook ( + (go-mode .lsp) + (css-mode . lsp) + (html-mode . lsp)) + :config + (setq lsp-enable-on-type-formatting nil)) + +(use-package lsp-ui + :ensure t + :commands lsp-ui-mode) + +(use-package clang-format + :ensure t + :bind + (:map c-mode-base-map + ("C-c C-f" . clang-format-buffer)) + :config + (setq clang-format-executable (executable-find "clang-format"))) + +(use-package sis + :config + (sis-ism-lazyman-config "1" "2" 'fcitx5) + (sis-global-cursor-color-mode t) + ;; 启用 /respect/ 模式 + (sis-global-respect-mode t) + ;; 为所有缓冲区启用 /context/ 模式 + (sis-global-context-mode t) + ;; 为所有缓冲区启用 /inline english/ 模式 + (sis-global-inline-mode t) + (with-eval-after-load 'evil + ;; 强制在进入插入模式时触发 sis 的恢复逻辑 + ;;(add-hook 'evil-insert-state-entry-hook #'sis-context-restore) + ; (add-hook 'evil-insert-state-entry-hook #'sis-set-chinese)) + )) + +(require 'smtpmail) + +(add-to-list 'load-path "/usr/share/emacs/site-lisp/elpa-src/mu4e-1.8.14") + +(setq gnutls-algorithm-priority "NORMAL:%COMPAT") + +(defun mu4e-goodies~break-cjk-word (word) + "Break CJK word into list of bi-grams like: 我爱你 -> 我爱 爱你" + (if (or (<= (length word) 2) + (equal (length word) (string-bytes word))) + word + (let ((pos nil) + (char-list nil) + (br-word nil)) + (if (setq pos (string-match ":" word)) + (concat (substring word 0 (+ 1 pos)) + (mu4e-goodies~break-cjk-word (substring word (+ 1 pos)))) + (if (memq 'ascii (find-charset-string word)) + word + (progn + (setq char-list (split-string word "" t)) + (while (cdr char-list) + (setq br-word (concat br-word (concat (car char-list) (cadr char-list)) " ")) + (setq char-list (cdr char-list))) + br-word)))))) + +(defun mu4e-goodies~break-cjk-query (expr) + "Break CJK strings into bi-grams in query." + (let ((word-list (split-string expr " " t)) + (new "")) + (dolist (word word-list new) + (setq new (concat new (mu4e-goodies~break-cjk-word word) " "))))) + +(setq mu4e-query-rewrite-function 'mu4e-goodies~break-cjk-query) + +(use-package mu4e + :ensure nil + :if (executable-find "mu") + :commands (mu4e) + :bind (:map mu4e-view-mode-map + ("9" . scroll-down-command) + ("0" . scroll-up-command) + :map mu4e-search-minor-mode-map + ("/" . mu4e-search-maildir) + :map mu4e-main-mode-map + ("g" . mu4e-update-mail-and-index) + :map mu4e-headers-mode-map + ("" . scroll-down-command) + ("j" . mu4e-headers-next) + ("k" . mu4e-headers-prev) + ("r" . mu4e-headers-mark-for-read) + ("!" . mu4e-headers-flag-all-read) + ("f" . mu4e-headers-mark-for-flag)) + + :custom + (mu4e-headers-fields '((:human-date . 10) + (:flags . 6) + (:from-or-to . 22) + (:thread-subject . nil))) + (mu4e-view-fields '(:from :to :cc :bcc :subject :flags + :date :maildir :mailing-list :tags)) + (mu4e-modeline-show-global nil) + (mu4e-hide-index-messages t) + + :init + (setq user-mail-address "im@verdant.ee" + user-full-name "Verdant" + mu4e-debug t) + + (setq message-send-mail-function 'sendmail-send-it + sendmail-program "/usr/bin/msmtp" + mail-specify-envelope-from t + mail-envelope-from 'header) + + (setq message-citation-line-format "\nOn %a, %b %d, %Y at %r %z, %N wrote:\n" + message-citation-line-function 'message-insert-formatted-citation-line + mm-discouraged-alternatives '("text/html" "text/richtext") + gnus-article-time-format "%a, %Y-%m-%d %T %z" + gnus-article-date-headers '(user-defined original)) + + :config + (require 'mu4e-contrib) + + (setq mail-user-agent 'mu4e-user-agent) + + (setq mu4e-contexts + (list + (make-mu4e-context + :name "Verdant" + :match-func (lambda (msg) + (when msg + (string-prefix-p "/ljc" (mu4e-message-field msg :maildir)))) + :vars '((mu4e-sent-folder . "/Verdant/Sent") + (mu4e-trash-folder . "/Verdant/Trash") + (mu4e-refile-folder . "/Verdant/Archive") + (mu4e-drafts-folder . "/Verdant/Drafts") + (user-mail-address . "im@verdant.ee"))))) + + (setq mu4e-compose-complete-only-personal t + mu4e-view-show-addresses t + mu4e-view-show-images nil + mu4e-attachment-dir "~/Downloads" + mu4e-sent-messages-behavior 'sent + mu4e-context-policy 'pick-first + mu4e-compose-context-policy 'ask-if-none + mu4e-compose-dont-reply-to-self t + mu4e-confirm-quit nil + mu4e-headers-date-format "%+4Y-%m-%d" + mu4e-view-html-plaintext-ratio-heuristic most-positive-fixnum + mu4e-update-interval (* 30 60) + mu4e-get-mail-command "true" + mu4e-compose-format-flowed t + mu4e-completing-read-function 'ido-completing-read) + + (setq mu4e-bookmarks '((:name "All Inbox" + :query "maildir:/Verdant/INBOX" + :key ?i) + (:name "Unread messages" + :query "flag:unread AND NOT flag:trashed" + :key ?u) + (:name "Today's messages" + :query "date:today..now AND NOT flag:trashed" + :key ?t) + (:name "Last 7 days" + :query "date:7d..now AND NOT flag:trashed" + :hide-unread t + :key ?w) + (:name "Flagged" + :query "flag:flagged" + :key ?f) + (:name "Sent" + :query "maildir:/Verdant/Sent" + :key ?s))) + + (add-to-list 'mu4e-view-actions '("browser" . mu4e-action-view-in-browser) t) + + (defun my/mu4e-pre-update-hook () + (let ((inhibit-message t)) + (message "Update and index mu4e at %s" (format-time-string "%D %-I:%M %p")))) + + (defun my/mu4e-stop-update-task () + (interactive) + (when mu4e--update-timer + (cancel-timer mu4e--update-timer) + (setq mu4e--update-timer nil))) + + (setq mu4e-update-pre-hook 'my/mu4e-pre-update-hook) + + (add-to-list 'mu4e-view-fields :bcc)) + +(use-package ace-window + :ensure t + :bind + (("C-x o" . ace-window))) + +(use-package dashboard + :ensure t + :config + (setq dashboard-startup-banner 'logo + dashboard-banner-logo-title "Welcome to Verdant's Emacverse!!!" + dashboard-center-content t + dashboard-set-heading-icons t + dashboard-items '((recents . 10) + (bookmarks . 5)) + dashboard-footer-messages '("verdant.el")) + + ;; 核心三件套 + (setq initial-buffer-choice (lambda () (get-buffer-create "*dashboard*"))) + (dashboard-setup-startup-hook) + (add-hook 'after-init-hook #'dashboard-open t)) + +(setq custom-safe-themes t) +(load-theme 'doom-Iosvkem) + +(set-face-attribute + 'default nil + :height 120) + +(setq window-combination-resize t) + +(setq x-stretch-cursor t) + +(setq scheme-program-name "mit-scheme") + +(require 'cmuscheme) + +(global-set-key (kbd "C-c C-e") 'scheme-send-last-sexp) ; 求值前一个表达式 +(global-set-key (kbd "C-c C-r") 'scheme-send-region) ; 求值选中区域 +(global-set-key (kbd "C-c C-l") 'scheme-load-file) ; 加载整个文件 +(global-set-key (kbd "C-c C-z") 'run-scheme) ; 启动/切换 REPL + +(add-to-list 'auto-mode-alist '("\\.scm\\'" . scheme-mode)) + +(defun setup-markdown-writing-environment () + "为 Markdown 写作优化的环境:开启 Olivetti,关闭行号。" + (interactive) + (variable-pitch-mode 1) + (display-line-numbers-mode -1) + (pixel-scroll-precision-mode 1) + (olivetti-mode)) + +(add-hook 'markdown-mode-hook + (lambda () + ;; 取消 Evil 的 TAB 绑定,使用 markdown-cycle + (define-key evil-normal-state-local-map (kbd "TAB") 'markdown-cycle) + (define-key evil-insert-state-local-map (kbd "TAB") 'indent-for-tab-command))) + +(setq dired-recursive-copies 'always) +(setq dired-recursive-deletes 'always) +(setq dired-dwim-target t) + +(put 'dired-find-alternate-file 'disabled nil) +(with-eval-after-load 'dired + (define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file) + (define-key dired-mode-map (kbd "^") (lambda () (interactive) (find-alternate-file "..")))) ; was dired-up-directory) + +(setq dired-dwim-target t) + +;; dired-sort +(defun dired-sort-size () + "Dired sort by size." + (interactive) + (dired-sort-other (concat dired-listing-switches "S"))) + +(defun dired-sort-extension () + "Dired sort by extension." + (interactive) + (dired-sort-other (concat dired-listing-switches "X"))) + +(defun dired-sort-ctime () + "Dired sort by create time." + (interactive) + (dired-sort-other (concat dired-listing-switches "ct"))) + +(defun dired-sort-utime () + "Dired sort by access time." + (interactive) + (dired-sort-other (concat dired-listing-switches "ut"))) + +(defun dired-sort-time () + "Dired sort by time." + (interactive) + (dired-sort-other (concat dired-listing-switches "t"))) + +(defun dired-sort-name () + "Dired sort by name." + (interactive) + (dired-sort-other (concat dired-listing-switches ""))) +;; 在 Dired 中,按`l`进入文件,按`h`回到上一级目录 +(add-hook 'evil-mode-hook (lambda () + (with-eval-after-load 'dired + (add-hook 'dired-mode-hook + (lambda () + (define-key evil-normal-state-local-map (kbd "h") nil) + (define-key evil-normal-state-local-map (kbd "l") nil) + (define-key evil-normal-state-local-map (kbd "h") #'dired-up-directory) + (define-key evil-normal-state-local-map (kbd "l") #'dired-find-file)))))) + +(add-hook 'dired-mode-hook + (lambda () + (define-key dired-mode-map (kbd "C-b") nil) + (define-key dired-mode-map (kbd "C-f") nil) + (define-key dired-mode-map (kbd "C-b") #'dired-up-directory) + (define-key dired-mode-map (kbd "C-f") #'dired-find-file))) + +(global-set-key (kbd "") #'my/keyboard-escape-quit) + +(global-auto-revert-mode t) ; 另一程序修改文件让 Emacs 及时刷新 Buffer + +(use-package org-bullets + :ensure t + :config + (setq org-bullets-bullet-list '("☰" "☷" "☯" "☭"))) +(org-bullets-mode) + +(lambda () (progn + (setq left-margin-width 2) + (setq right-margin-width 2) + (set-window-buffer nil (current-buffer)))) + +(setq org-startup-indented t + org-ellipsis "  " ;; folding symbol + org-pretty-entities t + org-hide-emphasis-markers t + ;; show actually italicized text instead of /italicized text/ + org-agenda-block-separator "" + org-fontify-whole-heading-line t + org-fontify-done-headline t + org-fontify-quote-and-verse-blocks t) + +(auto-save-mode 1) + +(setq ring-bell-function 'ignore + custom-safe-themes t ; 禁用非法操作提示 + cursor-type 'box + fringes-outside-margins t + display-time-24hr-format t ; 时间使用 24 小时制 + display-time-day-and-date t ; 时间显示包括日期和时间 + display-time-interval 60 ; 刷新频率 + display-time-format "%a %b %-e %H:%M" ; 时间格式 + scroll-step 1 + scroll-conservatively 10000) +(when (display-graphic-p) + (set-frame-size (selected-frame) 143 40)) +(global-display-line-numbers-mode t) +(display-time-mode 1) + +(setq confirm-kill-emacs #'yes-or-no-p ; 关闭 Emacs 时询问 y or n + auto-save-visited-interval 5 + native-comp-async-report-warinings-errors nil + backup-directory-alist `((".*" . ,temporary-file-directory)) + auto-save-file-name-transforms `((".*" ,temporary-file-directory t)) + select-enable-clipboard t + select-enable-primary t + interprogram-cut-function + (lambda (text &optional push) + (let ((process-connection-type nil)) + (let ((proc (start-process "xclip" nil "xclip" "-selection" "clipboard"))) + (process-send-string proc text) + (process-send-eof proc)))) + interprogram-paste-function + (lambda () + (shell-command-to-string "xclip -o -selection clipboard"))) + +(setq-default delete-by-moving-to-transh t) + +(defun my/keyboard-escape-quit() + "快速的 Esc 退出 minibuffer" + (interactive) + (keyboard-escape-quit)) + +(global-set-key (kbd "") #'my/keyboard-escape-quit) + +(show-paren-mode 1) ; 减轻数括号的痛苦 + +(provide 'config) + +(setq c-basic-offset 4 + tab-width 4) + +(use-package counsel + :ensure t) diff --git a/config.org b/config.org new file mode 100644 index 0000000..9a80b28 --- /dev/null +++ b/config.org @@ -0,0 +1,548 @@ +#+title: config +#+property: header-args :tangle yes + +* 编辑相关 +** Evil +#+begin_src emacs-lisp + ;; -*- lexical-binding: t; -*- + (use-package evil + :init + (setq evil-want-keybinding nil + evil-want-C-u-scroll t) + :config + (evil-mode) + :ensure t) + + (use-package evil-collection + :after evil + :ensure t + :config + (evil-collection-init)) + +#+end_src +** olivetti +#+begin_src emacs-lisp + (use-package olivetti) +#+end_src +** yasnippet +#+begin_src emacs-lisp + (use-package yasnippet + :ensure t + :config + (yas-global-mode 1)) +#+end_src +** ivy +#+begin_src emacs-lisp + (use-package ivy + :ensure t + :init + (ivy-mode 1) + (counsel-mode 1) + :config + (setq ivy-use-selectable-prompt t) + (setq ivy-use-preview t) + (setq ivy-fixed-height-minibuffer t) + (setq ivy-use-preview t) + (setq ivy-use-virtual-buffers t) + (setq search-default-mode #'char-fold-to-regexp) + (setq ivy-count-format "(%d/%d) ") + (setq ivy-initial-inputs-alist nil) ;; 不预设初始输入 + (setq ivy-use-selectable-prompt t) ;; 允许选择提示 + :bind + (("C-s" . 'swiper) + ("C-x b" . 'ivy-switch-buffer) + ("C-c v" . 'ivy-push-view) + ("C-c s" . 'ivy-switch-view) + ("C-c V" . 'ivy-pop-view) + ("C-x C-@" . 'counsel-mark-ring); 在某些终端上 C-x C-SPC 会被映射为 C-x C-@,比如在 macOS 上,所以要手动设置 + ("C-x C-SPC" . 'counsel-mark-ring) + :map minibuffer-local-map + ("C-r" . counsel-minibuffer-history))) + + ;; (use-package ivy-posframe + ;; :ensure t + ;; :config + ;; (ivy-posframe-mode t)) + + (use-package ivy-rich + :ensure t + :config + (ivy-rich-mode t)) +#+end_src +** company-mode +#+begin_src emacs-lisp + ;; 启用 company-mode 全局补全 + (use-package company + :ensure t + :config + (setq company-idle-delay 0.0 + company-minimum-prefix-length 1) + (global-company-mode) + + (with-eval-after-load 'company + ;; 补全列表背景 + (set-face-attribute 'company-tooltip nil + :foreground "white" :background "gray20") + ;; 选中项背景 + (set-face-attribute 'company-tooltip-selection nil + :foreground "blue" :background "gray20") + ;; 输入前缀高亮 + (set-face-attribute 'company-tooltip-common nil + :foreground "orange" :background "gray20") + ;; 右侧注释/类型 + (set-face-attribute 'company-tooltip-annotation nil + :foreground "cyan" :background "gray20"))) +#+end_src +** LSP + +#+begin_src emacs-lisp + (use-package lsp-mode + :ensure t + :init + (setq read-process-output-max (* 1024 1024)) + :hook ( + (go-mode .lsp) + (css-mode . lsp) + (html-mode . lsp)) + :config + (setq lsp-enable-on-type-formatting nil)) + + (use-package lsp-ui + :ensure t + :commands lsp-ui-mode) +#+end_src +** clang-format +#+begin_src emacs-lisp + (use-package clang-format + :ensure t + :bind + (:map c-mode-base-map + ("C-c C-f" . clang-format-buffer)) + :config + (setq clang-format-executable (executable-find "clang-format"))) +#+end_src +** sis + +#+begin_src emacs-lisp + (use-package sis + :config + (sis-ism-lazyman-config "1" "2" 'fcitx5) + (sis-global-cursor-color-mode t) + ;; 启用 /respect/ 模式 + (sis-global-respect-mode t) + ;; 为所有缓冲区启用 /context/ 模式 + (sis-global-context-mode t) + ;; 为所有缓冲区启用 /inline english/ 模式 + (sis-global-inline-mode t) + (with-eval-after-load 'evil + ;; 强制在进入插入模式时触发 sis 的恢复逻辑 + ;;(add-hook 'evil-insert-state-entry-hook #'sis-context-restore) + ; (add-hook 'evil-insert-state-entry-hook #'sis-set-chinese)) + )) +#+end_src + +* 邮件 +** mu4e +这个东西的配置实在是太庞大了,懒得细拆了。 +#+begin_src emacs-lisp + (require 'smtpmail) + + (add-to-list 'load-path "/usr/share/emacs/site-lisp/elpa-src/mu4e-1.8.14") + + (setq gnutls-algorithm-priority "NORMAL:%COMPAT") + + (defun mu4e-goodies~break-cjk-word (word) + "Break CJK word into list of bi-grams like: 我爱你 -> 我爱 爱你" + (if (or (<= (length word) 2) + (equal (length word) (string-bytes word))) + word + (let ((pos nil) + (char-list nil) + (br-word nil)) + (if (setq pos (string-match ":" word)) + (concat (substring word 0 (+ 1 pos)) + (mu4e-goodies~break-cjk-word (substring word (+ 1 pos)))) + (if (memq 'ascii (find-charset-string word)) + word + (progn + (setq char-list (split-string word "" t)) + (while (cdr char-list) + (setq br-word (concat br-word (concat (car char-list) (cadr char-list)) " ")) + (setq char-list (cdr char-list))) + br-word)))))) + + (defun mu4e-goodies~break-cjk-query (expr) + "Break CJK strings into bi-grams in query." + (let ((word-list (split-string expr " " t)) + (new "")) + (dolist (word word-list new) + (setq new (concat new (mu4e-goodies~break-cjk-word word) " "))))) + + (setq mu4e-query-rewrite-function 'mu4e-goodies~break-cjk-query) + + (use-package mu4e + :ensure nil + :if (executable-find "mu") + :commands (mu4e) + :bind (:map mu4e-view-mode-map + ("9" . scroll-down-command) + ("0" . scroll-up-command) + :map mu4e-search-minor-mode-map + ("/" . mu4e-search-maildir) + :map mu4e-main-mode-map + ("g" . mu4e-update-mail-and-index) + :map mu4e-headers-mode-map + ("" . scroll-down-command) + ("j" . mu4e-headers-next) + ("k" . mu4e-headers-prev) + ("r" . mu4e-headers-mark-for-read) + ("!" . mu4e-headers-flag-all-read) + ("f" . mu4e-headers-mark-for-flag)) + + :custom + (mu4e-headers-fields '((:human-date . 10) + (:flags . 6) + (:from-or-to . 22) + (:thread-subject . nil))) + (mu4e-view-fields '(:from :to :cc :bcc :subject :flags + :date :maildir :mailing-list :tags)) + (mu4e-modeline-show-global nil) + (mu4e-hide-index-messages t) + + :init + (setq user-mail-address "im@verdant.ee" + user-full-name "Verdant" + mu4e-debug t) + + (setq message-send-mail-function 'sendmail-send-it + sendmail-program "/usr/bin/msmtp" + mail-specify-envelope-from t + mail-envelope-from 'header) + + (setq message-citation-line-format "\nOn %a, %b %d, %Y at %r %z, %N wrote:\n" + message-citation-line-function 'message-insert-formatted-citation-line + mm-discouraged-alternatives '("text/html" "text/richtext") + gnus-article-time-format "%a, %Y-%m-%d %T %z" + gnus-article-date-headers '(user-defined original)) + + :config + (require 'mu4e-contrib) + + (setq mail-user-agent 'mu4e-user-agent) + + (setq mu4e-contexts + (list + (make-mu4e-context + :name "Verdant" + :match-func (lambda (msg) + (when msg + (string-prefix-p "/ljc" (mu4e-message-field msg :maildir)))) + :vars '((mu4e-sent-folder . "/Verdant/Sent") + (mu4e-trash-folder . "/Verdant/Trash") + (mu4e-refile-folder . "/Verdant/Archive") + (mu4e-drafts-folder . "/Verdant/Drafts") + (user-mail-address . "im@verdant.ee"))))) + + (setq mu4e-compose-complete-only-personal t + mu4e-view-show-addresses t + mu4e-view-show-images nil + mu4e-attachment-dir "~/Downloads" + mu4e-sent-messages-behavior 'sent + mu4e-context-policy 'pick-first + mu4e-compose-context-policy 'ask-if-none + mu4e-compose-dont-reply-to-self t + mu4e-confirm-quit nil + mu4e-headers-date-format "%+4Y-%m-%d" + mu4e-view-html-plaintext-ratio-heuristic most-positive-fixnum + mu4e-update-interval (* 30 60) + mu4e-get-mail-command "true" + mu4e-compose-format-flowed t + mu4e-completing-read-function 'ido-completing-read) + + (setq mu4e-bookmarks '((:name "All Inbox" + :query "maildir:/Verdant/INBOX" + :key ?i) + (:name "Unread messages" + :query "flag:unread AND NOT flag:trashed" + :key ?u) + (:name "Today's messages" + :query "date:today..now AND NOT flag:trashed" + :key ?t) + (:name "Last 7 days" + :query "date:7d..now AND NOT flag:trashed" + :hide-unread t + :key ?w) + (:name "Flagged" + :query "flag:flagged" + :key ?f) + (:name "Sent" + :query "maildir:/Verdant/Sent" + :key ?s))) + + (add-to-list 'mu4e-view-actions '("browser" . mu4e-action-view-in-browser) t) + + (defun my/mu4e-pre-update-hook () + (let ((inhibit-message t)) + (message "Update and index mu4e at %s" (format-time-string "%D %-I:%M %p")))) + + (defun my/mu4e-stop-update-task () + (interactive) + (when mu4e--update-timer + (cancel-timer mu4e--update-timer) + (setq mu4e--update-timer nil))) + + (setq mu4e-update-pre-hook 'my/mu4e-pre-update-hook) + + (add-to-list 'mu4e-view-fields :bcc)) +#+end_src +* 外观和 UI +** ace-window +#+begin_src emacs-lisp + (use-package ace-window + :ensure t + :bind + (("C-x o" . ace-window))) +#+end_src +** dashboard +#+begin_src emacs-lisp + (use-package dashboard + :ensure t + :config + (setq dashboard-startup-banner 'logo + dashboard-banner-logo-title "Welcome to Verdant's Emacverse!!!" + dashboard-center-content t + dashboard-set-heading-icons t + dashboard-items '((recents . 10) + (bookmarks . 5)) + dashboard-footer-messages '("verdant.el")) + + ;; 核心三件套 + (setq initial-buffer-choice (lambda () (get-buffer-create "*dashboard*"))) + (dashboard-setup-startup-hook) + (add-hook 'after-init-hook #'dashboard-open t)) +#+end_src + +** 主题 +#+begin_src emacs-lisp + (setq custom-safe-themes t) + (load-theme 'doom-Iosvkem) +#+end_src + +** 窗口大小 +#+begin_src emacs-lisp + (set-face-attribute + 'default nil + :height 120) +#+end_src + +** 新窗口平均其他左右窗口 +#+begin_src emacs-lisp + (setq window-combination-resize t) +#+end_src + +** 光标拉伸到字符宽度 +#+begin_src emacs-lisp + (setq x-stretch-cursor t) +#+end_src + +* MIT-Scheme +#+begin_src emacs-lisp + (setq scheme-program-name "mit-scheme") + + (require 'cmuscheme) + + (global-set-key (kbd "C-c C-e") 'scheme-send-last-sexp) ; 求值前一个表达式 + (global-set-key (kbd "C-c C-r") 'scheme-send-region) ; 求值选中区域 + (global-set-key (kbd "C-c C-l") 'scheme-load-file) ; 加载整个文件 + (global-set-key (kbd "C-c C-z") 'run-scheme) ; 启动/切换 REPL + + (add-to-list 'auto-mode-alist '("\\.scm\\'" . scheme-mode)) +#+end_src +* Markdown +** 优化环境 +#+begin_src emacs-lisp + (defun setup-markdown-writing-environment () + "为 Markdown 写作优化的环境:开启 Olivetti,关闭行号。" + (interactive) + (variable-pitch-mode 1) + (display-line-numbers-mode -1) + (pixel-scroll-precision-mode 1) + (olivetti-mode)) + + (add-hook 'markdown-mode-hook + (lambda () + ;; 取消 Evil 的 TAB 绑定,使用 markdown-cycle + (define-key evil-normal-state-local-map (kbd "TAB") 'markdown-cycle) + (define-key evil-insert-state-local-map (kbd "TAB") 'indent-for-tab-command))) +#+end_src +* Dired + +#+begin_src emacs-lisp + (setq dired-recursive-copies 'always) + (setq dired-recursive-deletes 'always) + (setq dired-dwim-target t) + + (put 'dired-find-alternate-file 'disabled nil) + (with-eval-after-load 'dired + (define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file) + (define-key dired-mode-map (kbd "^") (lambda () (interactive) (find-alternate-file "..")))) ; was dired-up-directory) + + (setq dired-dwim-target t) + + ;; dired-sort + (defun dired-sort-size () + "Dired sort by size." + (interactive) + (dired-sort-other (concat dired-listing-switches "S"))) + + (defun dired-sort-extension () + "Dired sort by extension." + (interactive) + (dired-sort-other (concat dired-listing-switches "X"))) + + (defun dired-sort-ctime () + "Dired sort by create time." + (interactive) + (dired-sort-other (concat dired-listing-switches "ct"))) + + (defun dired-sort-utime () + "Dired sort by access time." + (interactive) + (dired-sort-other (concat dired-listing-switches "ut"))) + + (defun dired-sort-time () + "Dired sort by time." + (interactive) + (dired-sort-other (concat dired-listing-switches "t"))) + + (defun dired-sort-name () + "Dired sort by name." + (interactive) + (dired-sort-other (concat dired-listing-switches ""))) + ;; 在 Dired 中,按`l`进入文件,按`h`回到上一级目录 + (add-hook 'evil-mode-hook (lambda () + (with-eval-after-load 'dired + (add-hook 'dired-mode-hook + (lambda () + (define-key evil-normal-state-local-map (kbd "h") nil) + (define-key evil-normal-state-local-map (kbd "l") nil) + (define-key evil-normal-state-local-map (kbd "h") #'dired-up-directory) + (define-key evil-normal-state-local-map (kbd "l") #'dired-find-file)))))) + + (add-hook 'dired-mode-hook + (lambda () + (define-key dired-mode-map (kbd "C-b") nil) + (define-key dired-mode-map (kbd "C-f") nil) + (define-key dired-mode-map (kbd "C-b") #'dired-up-directory) + (define-key dired-mode-map (kbd "C-f") #'dired-find-file))) + + (global-set-key (kbd "") #'my/keyboard-escape-quit) + + (global-auto-revert-mode t) ; 另一程序修改文件让 Emacs 及时刷新 Buffer +#+end_src +* Org +** org-bullets +#+begin_src emacs-lisp + (use-package org-bullets + :ensure t + :config + (setq org-bullets-bullet-list '("☰" "☷" "☯" "☭"))) + (org-bullets-mode) +#+end_src +** 边距 +#+begin_src emacs-lisp + (lambda () (progn + (setq left-margin-width 2) + (setq right-margin-width 2) + (set-window-buffer nil (current-buffer)))) +#+end_src +** 微调 +#+begin_src emacs-lisp + (setq org-startup-indented t + org-ellipsis "  " ;; folding symbol + org-pretty-entities t + org-hide-emphasis-markers t + ;; show actually italicized text instead of /italicized text/ + org-agenda-block-separator "" + org-fontify-whole-heading-line t + org-fontify-done-headline t + org-fontify-quote-and-verse-blocks t) +#+end_src +* 杂项 +** 自动保存 +#+begin_src emacs-lisp + (auto-save-mode 1) +#+end_src +** 时间显示 +#+begin_src emacs-lisp + (setq ring-bell-function 'ignore + custom-safe-themes t ; 禁用非法操作提示 + cursor-type 'box + fringes-outside-margins t + display-time-24hr-format t ; 时间使用 24 小时制 + display-time-day-and-date t ; 时间显示包括日期和时间 + display-time-interval 60 ; 刷新频率 + display-time-format "%a %b %-e %H:%M" ; 时间格式 + scroll-step 1 + scroll-conservatively 10000) + (when (display-graphic-p) + (set-frame-size (selected-frame) 143 40)) + (global-display-line-numbers-mode t) + (display-time-mode 1) +#+end_src + +** 关闭 Emacs 时询问 +#+begin_src emacs-lisp + (setq confirm-kill-emacs #'yes-or-no-p ; 关闭 Emacs 时询问 y or n + auto-save-visited-interval 5 + native-comp-async-report-warinings-errors nil + backup-directory-alist `((".*" . ,temporary-file-directory)) + auto-save-file-name-transforms `((".*" ,temporary-file-directory t)) + select-enable-clipboard t + select-enable-primary t + interprogram-cut-function + (lambda (text &optional push) + (let ((process-connection-type nil)) + (let ((proc (start-process "xclip" nil "xclip" "-selection" "clipboard"))) + (process-send-string proc text) + (process-send-eof proc)))) + interprogram-paste-function + (lambda () + (shell-command-to-string "xclip -o -selection clipboard"))) +#+end_src + +** 删除文件移动到垃圾箱 +#+begin_src emacs-lisp + (setq-default delete-by-moving-to-transh t) +#+end_src + +** 快速退出 minibuffer +#+begin_src emacs-lisp + (defun my/keyboard-escape-quit() + "快速的 Esc 退出 minibuffer" + (interactive) + (keyboard-escape-quit)) + + (global-set-key (kbd "") #'my/keyboard-escape-quit) +#+end_src + +** 减轻数括号的痛苦 +#+begin_src emacs-lisp + (show-paren-mode 1) ; 减轻数括号的痛苦 +#+end_src + +#+begin_src emacs-lisp + (provide 'config) +#+end_src + +** 编码风格 +#+begin_src emacs-lisp + (setq c-basic-offset 4 + tab-width 4) +#+end_src + +** counsel +#+begin_src emacs-lisp + (use-package counsel + :ensure t) +#+end_src diff --git a/core/core-basic.el b/core/core-basic.el deleted file mode 100644 index ce5375d..0000000 --- a/core/core-basic.el +++ /dev/null @@ -1,54 +0,0 @@ -;; -*- lexical-binding: t; -*- - -(setq confirm-kill-emacs #'yes-or-no-p ; 关闭 Emacs 时询问 y or n - auto-save-visited-interval 5 - native-comp-async-report-warinings-errors nil - backup-directory-alist `((".*" . ,temporary-file-directory)) - auto-save-file-name-transforms `((".*" ,temporary-file-directory t)) - select-enable-clipboard t - select-enable-primary t - interprogram-cut-function - (lambda (text &optional push) - (let ((process-connection-type nil)) - (let ((proc (start-process "xclip" nil "xclip" "-selection" "clipboard"))) - (process-send-string proc text) - (process-send-eof proc)))) - interprogram-paste-function - (lambda () - (shell-command-to-string "xclip -o -selection clipboard"))) - -(setq-default delete-by-moving-to-transh t ; 删除文件移动到垃圾箱 - window-combination-resize t ; 新窗口平均其他左右窗口 - x-stretch-cursor t ; 将光标拉伸到字符宽度 - ) - -(defun my/keyboard-escape-quit() - "快速的 Esc 退出 minibuffer" - (interactive) - (keyboard-escape-quit)) - -(global-set-key (kbd "") #'my/keyboard-escape-quit) - -;; 在 Dired 中,按`l`进入文件,按`h`回到上一级目录 -(add-hook 'evil-mode-hook (lambda () -(with-eval-after-load 'dired - (add-hook 'dired-mode-hook - (lambda () - (define-key evil-normal-state-local-map (kbd "h") nil) - (define-key evil-normal-state-local-map (kbd "l") nil) - (define-key evil-normal-state-local-map (kbd "h") #'dired-up-directory) - (define-key evil-normal-state-local-map (kbd "l") #'dired-find-file)))))) - - (add-hook 'dired-mode-hook - (lambda () - (define-key dired-mode-map (kbd "C-b") nil) - (define-key dired-mode-map (kbd "C-f") nil) - (define-key dired-mode-map (kbd "C-b") #'dired-up-directory) - (define-key dired-mode-map (kbd "C-f") #'dired-find-file))) - -(global-set-key (kbd "") #'my/keyboard-escape-quit) - -(global-auto-revert-mode t) ; 另一程序修改文件让 Emacs 及时刷新 Buffer -(auto-save-mode 1) - -(provide 'core-basic) diff --git a/core/core-dired.el b/core/core-dired.el deleted file mode 100644 index a68e2c4..0000000 --- a/core/core-dired.el +++ /dev/null @@ -1,45 +0,0 @@ -;; -*- lexical-binding: t; -*- - -(setq dired-recursive-copies 'always) -(setq dired-recursive-deletes 'always) -(setq dired-dwim-target t) - -(put 'dired-find-alternate-file 'disabled nil) -(with-eval-after-load 'dired - (define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file) - (define-key dired-mode-map (kbd "^") (lambda () (interactive) (find-alternate-file "..")))) ; was dired-up-directory) - -(setq dired-dwim-target t) - -;; dired-sort -(defun dired-sort-size () - "Dired sort by size." - (interactive) - (dired-sort-other (concat dired-listing-switches "S"))) - -(defun dired-sort-extension () - "Dired sort by extension." - (interactive) - (dired-sort-other (concat dired-listing-switches "X"))) - -(defun dired-sort-ctime () - "Dired sort by create time." - (interactive) - (dired-sort-other (concat dired-listing-switches "ct"))) - -(defun dired-sort-utime () - "Dired sort by access time." - (interactive) - (dired-sort-other (concat dired-listing-switches "ut"))) - -(defun dired-sort-time () - "Dired sort by time." - (interactive) - (dired-sort-other (concat dired-listing-switches "t"))) - -(defun dired-sort-name () - "Dired sort by name." - (interactive) - (dired-sort-other (concat dired-listing-switches ""))) - -(provide 'core-dired) diff --git a/core/core-editing.el b/core/core-editing.el deleted file mode 100644 index d6c10aa..0000000 --- a/core/core-editing.el +++ /dev/null @@ -1,25 +0,0 @@ -;; -*- lexical-binding: t; -*- -(show-paren-mode t) -(save-place-mode 1) -(global-subword-mode 1) -(add-hook 'prog-mode-hook #'show-paren-mode) - -(electric-pair-mode 1) - -(defun setup-markdown-writing-environment () - "为 Markdown 写作优化的环境:开启 Olivetti,关闭行号。" - (interactive) - (variable-pitch-mode 1) - (display-line-numbers-mode -1) - (pixel-scroll-precision-mode 1) - (olivetti-mode)) - -(add-hook 'markdown-mode-hook - (lambda () - ;; 取消 Evil 的 TAB 绑定,使用 markdown-cycle - (define-key evil-normal-state-local-map (kbd "TAB") 'markdown-cycle) - (define-key evil-insert-state-local-map (kbd "TAB") 'indent-for-tab-command))) - -(setq evil-want-C-u-scroll t) - -(provide 'core-editing) diff --git a/core/core-ui.el b/core/core-ui.el deleted file mode 100644 index f0190fd..0000000 --- a/core/core-ui.el +++ /dev/null @@ -1,21 +0,0 @@ -;; -*- lexical-binding: t; -*- -(display-time-mode 1) - -(setq custom-safe-themes t - ring-bell-function 'ignore - cursor-type 'box - fringes-outside-margins t - display-time-24hr-format t ; 时间使用 24 小时制 - display-time-day-and-date t ; 时间显示包括日期和时间 - display-time-interval 60 ; 刷新频率 - display-time-format "%a %b %-e %H:%M" ; 时间格式 - scroll-step 1 - scroll-conservatively 10000) - -(load-theme 'doom-Iosvkem) - -(when (display-graphic-p) - (set-frame-size (selected-frame) 143 40)) - -(global-display-line-numbers-mode t) -(provide 'core-ui) diff --git a/core/core.el b/core/core.el deleted file mode 100644 index 767ca63..0000000 --- a/core/core.el +++ /dev/null @@ -1,5 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'core-ui) -(require 'core-basic) -(require 'core-editing) -(provide 'core) diff --git a/init.el b/init.el index d373a05..c9cdceb 100644 --- a/init.el +++ b/init.el @@ -1,12 +1,16 @@ ;; -*- lexical-binding: t; -*- +(require 'org) (setq user-emacs-directory (expand-file-name "~/.emacs.d")) +(add-to-list 'load-path user-emacs-directory) +(let ((org-file (expand-file-name "config.org" user-emacs-directory)) + (el-file (expand-file-name "config.el" user-emacs-directory))) + (when (or (not (file-exists-p el-file)) + (file-newer-than-file-p org-file el-file)) + (org-babel-tangle-file org-file)) + (load-file el-file)) +(require 'config) -(add-to-list 'load-path (expand-file-name "core" user-emacs-directory)) -(add-to-list 'load-path (expand-file-name "packages" user-emacs-directory)) - -(require 'core) -(require 'packages) (custom-set-variables ;; custom-set-variables was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. @@ -14,9 +18,10 @@ ;; If there is more than one, they won't work right. '(package-selected-packages '(ace-window clang-format color-theme-sanityinc-solarized company - counsel dashboard diredfl doom-themes evil-collection - evil-mu4e hugoista ivy-posframe ivy-rich leetcode - lsp-ui olivetti yasnippet))) + counsel dashboard diredfl doom-themes evil-collection + evil-mu4e hugoista ivy-posframe ivy-rich leetcode + lsp-ui magit olivetti org-bullets ox-hugo sis + yasnippet))) (custom-set-faces ;; custom-set-faces was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. diff --git a/packages/.hugo_build.lock b/packages/.hugo_build.lock deleted file mode 100644 index e69de29..0000000 diff --git a/packages/packages-editing.el b/packages/packages-editing.el deleted file mode 100644 index c2e8b5c..0000000 --- a/packages/packages-editing.el +++ /dev/null @@ -1,95 +0,0 @@ -;; -*- lexical-binding: t; -*- -(use-package evil - :init - (setq evil-want-keybinding nil) - :config - (evil-mode) - :ensure t) - -(use-package evil-collection - :after evil - :ensure t - :config - (evil-collection-init)) - -(use-package olivetti) - -(use-package yasnippet - :ensure t - :config - (yas-global-mode 1)) - -(use-package ivy - :ensure t - :init - (ivy-mode 1) - (counsel-mode 1) - :config - (setq ivy-use-selectable-prompt t) - (setq ivy-use-preview t) - (setq ivy-fixed-height-minibuffer t) - (setq ivy-use-preview t) - (setq ivy-use-virtual-buffers t) - (setq search-default-mode #'char-fold-to-regexp) - (setq ivy-count-format "(%d/%d) ") - (setq ivy-initial-inputs-alist nil) ;; 不预设初始输入 - (setq ivy-use-selectable-prompt t) ;; 允许选择提示 - :bind - (("C-s" . 'swiper) - ("C-x b" . 'ivy-switch-buffer) - ("C-c v" . 'ivy-push-view) - ("C-c s" . 'ivy-switch-view) - ("C-c V" . 'ivy-pop-view) - ("C-x C-@" . 'counsel-mark-ring); 在某些终端上 C-x C-SPC 会被映射为 C-x C-@,比如在 macOS 上,所以要手动设置 - ("C-x C-SPC" . 'counsel-mark-ring) - :map minibuffer-local-map - ("C-r" . counsel-minibuffer-history))) - -;; 启用 company-mode 全局补全 -(use-package company - :ensure t - :config - (setq company-idle-delay 0.0 - company-minimum-prefix-length 1) - (global-company-mode) - - (with-eval-after-load 'company - ;; 补全列表背景 - (set-face-attribute 'company-tooltip nil - :foreground "white" :background "gray20") - ;; 选中项背景 - (set-face-attribute 'company-tooltip-selection nil - :foreground "blue" :background "gray20") - ;; 输入前缀高亮 - (set-face-attribute 'company-tooltip-common nil - :foreground "orange" :background "gray20") - ;; 右侧注释/类型 - (set-face-attribute 'company-tooltip-annotation nil - :foreground "cyan" :background "gray20"))) - -(use-package lsp-mode - :ensure t - :init - (setq read-process-output-max (* 1024 1024)) - :hook ( - (c-mode . lsp) - (go-mode .lsp) - (css-mode . lsp) - (html-mode . lsp)) - ) - -(use-package lsp-ui - :ensure t - :commands lsp-ui-mode) - -(use-package clang-format - :ensure t - :hook - (c-mode-common-hook . (lambda () (add-hook 'before-save-hook 'clang-format-buffer nil t))) - :bind - (:map c-mode-base-map - ("C-c C-f" . clang-format-buffer)) - :config - (setq clang-format-executable (executable-find "clang-format"))) - -(provide 'packages-editing) diff --git a/packages/packages-email.el b/packages/packages-email.el deleted file mode 100644 index a519c42..0000000 --- a/packages/packages-email.el +++ /dev/null @@ -1,153 +0,0 @@ -;; -*- lexical-binding: t; -*- - -(require 'smtpmail) - -(add-to-list 'load-path "/usr/share/emacs/site-lisp/elpa-src/mu4e-1.8.14") - -(setq gnutls-algorithm-priority "NORMAL:%COMPAT") - -(defun mu4e-goodies~break-cjk-word (word) - "Break CJK word into list of bi-grams like: 我爱你 -> 我爱 爱你" - (if (or (<= (length word) 2) - (equal (length word) (string-bytes word))) - word - (let ((pos nil) - (char-list nil) - (br-word nil)) - (if (setq pos (string-match ":" word)) - (concat (substring word 0 (+ 1 pos)) - (mu4e-goodies~break-cjk-word (substring word (+ 1 pos)))) - (if (memq 'ascii (find-charset-string word)) - word - (progn - (setq char-list (split-string word "" t)) - (while (cdr char-list) - (setq br-word (concat br-word (concat (car char-list) (cadr char-list)) " ")) - (setq char-list (cdr char-list))) - br-word)))))) - -(defun mu4e-goodies~break-cjk-query (expr) - "Break CJK strings into bi-grams in query." - (let ((word-list (split-string expr " " t)) - (new "")) - (dolist (word word-list new) - (setq new (concat new (mu4e-goodies~break-cjk-word word) " "))))) - -(setq mu4e-query-rewrite-function 'mu4e-goodies~break-cjk-query) - -(use-package mu4e - :ensure nil - :if (executable-find "mu") - :commands (mu4e) - :bind (:map mu4e-view-mode-map - ("9" . scroll-down-command) - ("0" . scroll-up-command) - :map mu4e-search-minor-mode-map - ("/" . mu4e-search-maildir) - :map mu4e-main-mode-map - ("g" . mu4e-update-mail-and-index) - :map mu4e-headers-mode-map - ("" . scroll-down-command) - ("j" . mu4e-headers-next) - ("k" . mu4e-headers-prev) - ("r" . mu4e-headers-mark-for-read) - ("!" . mu4e-headers-flag-all-read) - ("f" . mu4e-headers-mark-for-flag)) - - :custom - (mu4e-headers-fields '((:human-date . 12) - (:flags . 6) - (:from-or-to . 22) - (:thread-subject . nil))) - (mu4e-view-fields '(:from :to :cc :bcc :subject :flags - :date :maildir :mailing-list :tags)) - (mu4e-modeline-show-global nil) - (mu4e-hide-index-messages t) - - :init - (setq user-mail-address "im@verdant.ee" - user-full-name "Verdant" - mu4e-debug t) - - (setq message-send-mail-function 'sendmail-send-it - sendmail-program "/usr/bin/msmtp" - mail-specify-envelope-from t - mail-envelope-from 'header) - - (setq message-citation-line-format "\nOn %a, %b %d, %Y at %r %z, %N wrote:\n" - message-citation-line-function 'message-insert-formatted-citation-line - mm-discouraged-alternatives '("text/html" "text/richtext") - gnus-article-time-format "%a, %Y-%m-%d %T %z" - gnus-article-date-headers '(user-defined original)) - - :config - (require 'mu4e-contrib) - - (setq mail-user-agent 'mu4e-user-agent) - - (setq mu4e-contexts - (list - (make-mu4e-context - :name "Verdant" - :match-func (lambda (msg) - (when msg - (string-prefix-p "/ljc" (mu4e-message-field msg :maildir)))) - :vars '((mu4e-sent-folder . "/Verdant/Sent") - (mu4e-trash-folder . "/Verdant/Trash") - (mu4e-refile-folder . "/Verdant/Archive") - (mu4e-drafts-folder . "/Verdant/Drafts") - (user-mail-address . "im@verdant.ee"))))) - - (setq mu4e-compose-complete-only-personal t - mu4e-view-show-addresses t - mu4e-view-show-images nil - mu4e-attachment-dir "~/Downloads" - mu4e-sent-messages-behavior 'sent - mu4e-context-policy 'pick-first - mu4e-compose-context-policy 'ask-if-none - mu4e-compose-dont-reply-to-self t - mu4e-confirm-quit nil - mu4e-headers-date-format "%+4Y-%m-%d" - mu4e-view-html-plaintext-ratio-heuristic most-positive-fixnum - mu4e-update-interval (* 30 60) - mu4e-get-mail-command "true" - mu4e-compose-format-flowed t - mu4e-completing-read-function 'ido-completing-read) - - (setq mu4e-bookmarks '((:name "All Inbox" - :query "maildir:/Verdant/INBOX" - :key ?i) - (:name "Unread messages" - :query "flag:unread AND NOT flag:trashed" - :key ?u) - (:name "Today's messages" - :query "date:today..now AND NOT flag:trashed" - :key ?t) - (:name "Last 7 days" - :query "date:7d..now AND NOT flag:trashed" - :hide-unread t - :key ?w) - (:name "Flagged" - :query "flag:flagged" - :key ?f) - (:name "Sent" - :query "maildir:/Verdant/Sent" - :key ?s))) - - (add-to-list 'mu4e-view-actions '("browser" . mu4e-action-view-in-browser) t) - - (defun my/mu4e-pre-update-hook () - (let ((inhibit-message t)) - (message "Update and index mu4e at %s" (format-time-string "%D %-I:%M %p")))) - - (defun my/mu4e-stop-update-task () - (interactive) - (when mu4e--update-timer - (cancel-timer mu4e--update-timer) - (setq mu4e--update-timer nil))) - - (setq mu4e-update-pre-hook 'my/mu4e-pre-update-hook) - - (add-to-list 'mu4e-view-fields :bcc)) - -(provide 'packages-email) diff --git a/packages/packages-leetcode.el b/packages/packages-leetcode.el deleted file mode 100644 index a192b80..0000000 --- a/packages/packages-leetcode.el +++ /dev/null @@ -1,1386 +0,0 @@ -;;; leetcode.el --- An leetcode client -*- lexical-binding: t; no-byte-compile: t -*- - -;; Copyright (C) 2019 Wang Kai - -;; Author: Wang Kai -;; Keywords: extensions, tools -;; Package-Version: 20220206.1515 -;; Package-Commit: b3103bd08c8943091f702c66d674f0f27ef7fe0b -;; URL: https://github.com/kaiwk/leetcode.el -;; Package-Requires: ((emacs "26") (dash "2.16.0") (graphql "0.1.1") (spinner "1.7.3") (aio "1.0") (log4e "0.3.3")) -;; Version: 0.1.24 - -;; 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 . - -;;; Commentary: - -;; leetcode.el is an unofficial LeetCode client. -;; -;; Now it implements several API: -;; - Check problems list -;; - Try testcase -;; - Submit code -;; -;; Since most HTTP requests works asynchronously, it won't block Emacs. -;; -;;; Code: -(eval-when-compile - (require 'let-alist)) - -(require 'json) -(require 'shr) -(require 'seq) -(require 'subr-x) -(require 'mm-url) -(require 'cl-lib) - -(require 'dash) -(require 'graphql) ; Some requests of LeetCode use GraphQL -(require 'aio) -(require 'spinner) -(require 'log4e) - -(log4e:deflogger "leetcode" "%t [%l] %m" "%H:%M:%S" '((fatal . "fatal") - (error . "error") - (warn . "warn") - (info . "info") - (debug . "debug") - (trace . "trace"))) -(setq log4e--log-buffer-leetcode "*leetcode-log*") - -;;;###autoload -(defun leetcode-toggle-debug () - "Toggle debug." - (interactive) - (if (leetcode--log-debugging-p) - (progn - (leetcode--log-set-level 'info) - (leetcode--log-disable-debugging) - (message "leetcode disable debug")) - (progn - (leetcode--log-set-level 'debug) - (leetcode--log-enable-debugging) - (message "leetcode enable debug")))) - -(defun leetcode--install-my-cookie () - "Install leetcode dependencies." - (let ((async-shell-command-display-buffer t)) - (async-shell-command - "pip3 install my_cookies" - (get-buffer-create "*leetcode-install*")))) - -(defun leetcode--check-deps () - "Check if all dependencies installed." - (if (executable-find "my_cookies") - t - (leetcode--install-my-cookie) - nil)) - -(defgroup leetcode nil - "A Leetcode client." - :prefix 'leetcode- - :group 'tools) - -(defvar leetcode--user nil - "User object. -The object with following attributes: -:username String -:solved Number -:easy Number -:medium Number -:hard Number") - -(defvar leetcode--all-problems nil - "Problems info with a list of problem object. -The object with following attributes: -:num Number -:tag String -:problems List - -The elements of :problems has attributes: -:status String -:id Number -:backend-id Number -:title String -:acceptance String -:difficulty Number {1,2,3} -:paid-only Boolean {t|nil} -:tags List") - -(defvar leetcode--all-tags nil - "All problems tags.") - -(defvar leetcode--problem-titles nil - "Problem titles that have been open in solving layout.") - -(defvar leetcode-retry-threshold 20 "`leetcode-try' or `leetcode-submit' retry times.") -(defvar leetcode--filter-regex nil "Filter rows by regex.") -(defvar leetcode--filter-tag nil "Filter rows by tag.") -(defvar leetcode--filter-difficulty nil - "Filter rows by difficulty, it can be \"easy\", \"medium\" and \"hard\".") -(defconst leetcode--all-difficulties '("easy" "medium" "hard")) - -(defconst leetcode--paid "•" "Paid mark.") -(defconst leetcode--checkmark "✓" "Checkmark for accepted problem.") -(defconst leetcode--buffer-name "*leetcode*") -(defconst leetcode--description-buffer-name "*leetcode-description*") -(defconst leetcode--testcase-buffer-name "*leetcode-testcase*") -(defconst leetcode--result-buffer-name "*leetcode-result*") - -(defface leetcode-paid-face - '((t (:foreground "gold"))) - "Face for `leetcode--paid'." - :group 'leetcode) - -(defface leetcode-checkmark-face - '((t (:foreground "#5CB85C"))) - "Face for `leetcode--checkmark'." - :group 'leetcode) - -(defface leetcode-easy-face - '((t (:foreground "#5CB85C"))) - "Face for easy problems." - :group 'leetcode) - -(defface leetcode-medium-face - '((t (:foreground "#F0AD4E"))) - "Face for medium problems." - :group 'leetcode) - -(defface leetcode-hard-face - '((t (:foreground "#D9534E"))) - "Face for hard problems." - :group 'leetcode) - -;;; Login -;; URL -(defconst leetcode--domain "leetcode.cn") -(defconst leetcode--base-url "https://leetcode.cn") -(defconst leetcode--url-login (concat leetcode--base-url "/accounts/login")) - -;; Header -(defconst leetcode--User-Agent '("User-Agent" . - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:66.0) Gecko/20100101 Firefox/66.0")) -(defconst leetcode--X-Requested-With '("X-Requested-With" . "XMLHttpRequest")) -(defconst leetcode--X-CSRFToken "X-CSRFToken") - -;; API -(defconst leetcode--api-root (concat leetcode--base-url "/api")) -(defconst leetcode--api-graphql (concat leetcode--base-url "/graphql")) -(defconst leetcode--api-all-problems (concat leetcode--api-root "/problems/all/")) -(defconst leetcode--api-all-tags (concat leetcode--base-url "/problems/api/tags/")) -(defconst leetcode--api-daily-challenge - "query todayRecord { todayRecord { date question { questionFrontendId title titleSlug status } } }") -;; submit -(defconst leetcode--api-submit (concat leetcode--base-url "/problems/%s/submit/")) -(defconst leetcode--api-problems-submission (concat leetcode--base-url "/problems/%s/submissions/")) -(defconst leetcode--api-check-submission (concat leetcode--base-url "/submissions/detail/%s/check/")) -;; try testcase -(defconst leetcode--api-try (concat leetcode--base-url "/problems/%s/interpret_solution/")) -(defconst leetcode--api-try-referer (concat leetcode--base-url "/problems/%s/")) - -(defun to-list (vec) - "Convert VEC to list." - (append vec '())) - -(defmacro dovec (spec &rest body) - "Loop over a vector. -EVALUATE BODY with VAR bound to each element in VEC, in turn. -SPEC is just like (VAR VEC [RESULT]). Then evaluate RESULT to -get the return value (nil if RESULT is omitted). - -\(fn (VAR VEC [RESULT]) BODY...)" - (declare (indent 1)) - (let ((start 0) - (counter (gensym)) - (end (gensym))) - `(let ((,counter ,start) - (,(car spec) nil) - (,end (length ,(cadr spec)))) - (while (< ,counter ,end) - (setq ,(car spec) (aref ,(cadr spec) ,counter)) - ,@body - (setq ,counter (1+ ,counter))) - ,@(cddr spec)))) - -(defun leetcode--referer (value) - "It will return an alist as the HTTP Referer Header. -VALUE should be the referer." - (cons "Referer" value)) - -(defun leetcode--maybe-csrf-token () - "Return csrf token if it exists, otherwise return nil." - (if-let ((cookie (seq-find - (lambda (item) - (string= (aref item 1) - "csrftoken")) - (url-cookie-retrieve leetcode--domain "/" t)))) - (aref cookie 2))) - -(aio-defun leetcode--csrf-token () - "Return csrf token." - (unless (leetcode--maybe-csrf-token) - (aio-await (aio-url-retrieve leetcode--url-login))) - (leetcode--maybe-csrf-token)) - -(defun leetcode--credentials () - "Receive user account and password." - (let ((auth-source-creation-prompts - '((user . "LeetCode user: ") - (secret . "LeetCode password for %u: "))) - (found (car (auth-source-search - :max 1 - :host leetcode--domain - :require '(:user :secret) - :create t)))) - (if found - (list (plist-get found :user) - (let ((secret (plist-get found :secret))) - (if (functionp secret) - (funcall secret) - secret)) - (plist-get found :save-function))))) - -(defun leetcode--multipart-form-data (name value) - "Generate multipart form data with NAME and VALUE." - `("file" - ("name" . ,name) - ("filedata" . ,value) - ("filename" . "") - ("content-type" . ""))) - -(aio-defun leetcode--login () - "Steal LeetCode login session from local browser. -It also cleans LeetCode cookies in `url-cookie-file'." - (leetcode--loading-mode t) - (ignore-errors (url-cookie-delete-cookies leetcode--domain)) - (aio-await (leetcode--csrf-token)) ;knock knock, whisper me the mysterious information - (let* ((my-cookies (executable-find "my_cookies")) - (my-cookies-output (shell-command-to-string (concat (shell-quote-argument my-cookies) " cn"))) - (cookies-list (seq-filter - (lambda (s) (not (string-empty-p s))) - (split-string my-cookies-output "\n"))) - (cookies-pairs (seq-map - (lambda (s) (split-string s)) - cookies-list)) - (leetcode-session (cadr (assoc "LEETCODE_SESSION" cookies-pairs))) - (leetcode-csrftoken (cadr (assoc "csrftoken" cookies-pairs)))) - (leetcode--debug "login session: '%s'" leetcode-session) - (leetcode--debug "login csrftoken: '%s'" leetcode-csrftoken) - (url-cookie-store "LEETCODE_SESSION" leetcode-session nil leetcode--domain "/" t) - (url-cookie-store "csrftoken" leetcode-csrftoken nil leetcode--domain "/" t)) - (aio-await (leetcode--csrf-token)) ;knock knock, whisper me the mysterious information - (leetcode--loading-mode -1)) - -(defun leetcode--login-p () - "Whether user is login." - (let ((username (plist-get leetcode--user :username))) - (and username - (not (string-empty-p username)) - (seq-find - (lambda (item) - (string= (aref item 1) - "LEETCODE_SESSION")) - (url-cookie-retrieve leetcode--domain "/" t))))) - -(defun leetcode--set-user-and-problems (user-and-problems) - "Set `leetcode--user' and `leetcode--all-problems'. -If user isn't login, only `leetcode--all-problems' will be set. -USER-AND-PROBLEMS is an alist comes from -`leetcode--api-all-problems'." - ;; user - (let-alist user-and-problems - (setq leetcode--user - (list :username .user_name - :solved .num_solved - :easy .ac_easy - :medium .ac_medium - :hard .ac_hard)) - (leetcode--debug "set user: %s, solved %s in %s problems" .user_name .num_solved .num_total) - ;; problem list - (setq leetcode--all-problems - (list - :num .num_total - :tag "all" - :problems - (let* ((len .num_total) - (problems nil)) - (dotimes (i len) - (let-alist (aref .stat_status_pairs i) - (leetcode--debug "frontend_question_id: %s, question_id: %s, title: %s, status: %s" - .stat.frontend_question_id .stat.question_id .stat.question__title .status) - (push (list - :status .status - :id .stat.question_id - :backend-id .stat.question_id - :frontend-id .stat.frontend_question_id - :title .stat.question__title - :title-slug .stat.question__title_slug - :acceptance (format - "%.1f%%" - (* 100 - (/ (float .stat.total_acs) - .stat.total_submitted))) - :difficulty .difficulty.level - :paid-only (eq .paid_only t)) - problems))) - problems))))) - -(defun leetcode--set-tags (all-tags) - "Set `leetcode--all-tags' and `leetcode--all-problems' with ALL-TAGS." - (let ((tags-table (make-hash-table :size 3200))) - (let-alist all-tags - (dolist (topic (to-list .topics)) - (let-alist topic - ;; set leetcode--all-tags - (unless (member .slug leetcode--all-tags) - (push .slug leetcode--all-tags)) - ;; tags-table cache - (dolist (id (to-list .questions)) - (puthash id (cons .slug (gethash id tags-table)) tags-table))))) - ;; set problems tags with tags-table - (dolist (problem (plist-get leetcode--all-problems :problems)) - (let ((backend-id (plist-get problem :backend-id))) - (plist-put problem :tags (gethash backend-id tags-table)))))) - -(defun leetcode--slugify-title (title) - "Make TITLE a slug title. -Such as 'Two Sum' will be converted to 'two-sum'." - (let* ((str1 (replace-regexp-in-string "[\s-]+" "-" (downcase title))) - (res (replace-regexp-in-string "[(),]" "" str1))) - res)) - -(defun leetcode--problem-graphql-params (operation &optional vars) - "Construct a GraphQL parameter. -OPERATION and VARS are LeetCode GraphQL parameters." - (list - (cons "operationName" operation) - (cons "query" - (graphql-query - problemsetQuestionList - (:arguments - (($titleSlug . String!)) - (question - :arguments - ((titleSlug . ($ titleSlug))) - likes - dislikes - content - translatedContent - status - sampleTestCase - (topicTags slug) - (codeSnippets langSlug code))))) - (if vars (cons "variables" vars)))) - -(aio-defun leetcode--fetch-problem (title) - "Fetch single problem. -TITLE is a problem's title. -Return a object with following attributes: -:likes Number -:dislikes Number -:content String -:topicTags String" - (let* ((slug-title (leetcode--slugify-title title)) - (url-request-method "POST") - (url-request-extra-headers - `(,leetcode--User-Agent - ,(cons "Content-Type" "application/json"))) - (url-request-data - (json-encode (leetcode--problem-graphql-params - "problemsetQuestionList" - (list (cons "titleSlug" slug-title))))) - (result (aio-await (aio-url-retrieve leetcode--api-graphql)))) - (if-let ((error-info (plist-get (car result) :error))) - (progn - (switch-to-buffer (cdr result)) - (leetcode--warn "LeetCode fetch problem ERROR: %S" error-info)) - (with-current-buffer (cdr result) - (goto-char url-http-end-of-headers) - (let* ((json (json-read)) - (question (alist-get 'question (alist-get 'data json)))) - question))))) - -(defun leetcode--markdown-to-html (markdown-text) - "Convert simple Markdown to HTML for better rendering. -Supports: **bold**, *italic*, - lists, > blockquotes, code blocks." - (let ((html-text markdown-text)) - ;; Convert **text** to text - (setq html-text (replace-regexp-in-string "\\*\\*\\([^*]+\\)\\*\\*" "\\1" html-text)) - ;; Convert *text* to text - (setq html-text (replace-regexp-in-string "\\*\\([^*]+\\)\\*" "\\1" html-text)) - ;; Convert `text` to text - (setq html-text (replace-regexp-in-string "`\\([^`]+\\)`" "\\1" html-text)) - ;; Convert newlines to
- (setq html-text (replace-regexp-in-string "\n" "
" html-text)) - ;; Convert - list items (with indentation) - (setq html-text (replace-regexp-in-string "^- " "   • " html-text)) - ;; Convert > blockquotes to indented text - (setq html-text (replace-regexp-in-string "^> " "    " html-text)) - html-text)) - -(defun leetcode--replace-in-buffer (regex to) - "Replace string matched REGEX in `current-buffer' to TO." - (with-current-buffer (current-buffer) - (save-excursion - (goto-char (point-min)) - (save-match-data - (while (re-search-forward regex (point-max) t) - (replace-match to)))))) - -(defun leetcode--make-tabulated-headers (header-names rows) - "Calculate headers width. -Column width calculated by picking the max width of every cell -under that column and the HEADER-NAMES. HEADER-NAMES are a list -of header name, ROWS are a list of vector, each vector is one -row." - (let ((widths - (seq-reduce - (lambda (acc row) - (cl-mapcar - (lambda (a col) (max a (length col))) - acc - (append row '()))) - rows - (seq-map #'length header-names)))) - (vconcat - (cl-mapcar - (lambda (col size) (list col size nil)) - header-names widths)))) - -(defun leetcode--stringify-difficulty (difficulty) - "Stringify DIFFICULTY level (number) to 'easy', 'medium' or 'hard'." - (let ((easy-tag "easy") - (medium-tag "medium") - (hard-tag "hard")) - (cond - ((eq 1 difficulty) - (prog1 easy-tag - (put-text-property - 0 (length easy-tag) - 'font-lock-face 'leetcode-easy-face easy-tag))) - ((eq 2 difficulty) - (prog1 medium-tag - (put-text-property - 0 (length medium-tag) - 'font-lock-face 'leetcode-medium-face medium-tag))) - ((eq 3 difficulty) - (prog1 hard-tag - (put-text-property - 0 (length hard-tag) - 'font-lock-face 'leetcode-hard-face hard-tag)))))) - -(defun leetcode--problems-rows () - "Generate tabulated list rows from `leetcode--all-problems'. -Return a list of rows, each row is a vector: -\([ <acceptance> <difficulty>] ...)" - (let ((problems (plist-get leetcode--all-problems :problems)) - (easy-tag "easy") - (medium-tag "medium") - (hard-tag "hard") - rows) - (dolist (p problems) - (if (or leetcode--display-paid - (not (plist-get p :paid-only))) - (setq rows - (cons - (vector - ;; status - (if (equal (plist-get p :status) "ac") - (prog1 leetcode--checkmark - (put-text-property - 0 (length leetcode--checkmark) - 'font-lock-face 'leetcode-checkmark-face leetcode--checkmark)) - " ") - ;; id - (number-to-string (plist-get p :id)) - ;; title - (concat - (plist-get p :title) - " " - (if (eq (plist-get p :paid-only) t) - (prog1 leetcode--paid - (put-text-property - 0 (length leetcode--paid) - 'font-lock-face 'leetcode-paid-face leetcode--paid)) - " ")) - ;; acceptance - (plist-get p :acceptance) - ;; difficulty - (leetcode--stringify-difficulty (plist-get p :difficulty)) - ;; tags - (if leetcode--display-tags (string-join (plist-get p :tags) ", ") "")) - rows)))) - (reverse rows))) - -(defun leetcode--row-tags (row) - "Get tags from ROW." - (aref row 5)) - -(defun leetcode--row-difficulty (row) - "Get difficulty from ROW." - (aref row 4)) - -(defun leetcode--filter (rows) - "Filter ROWS by `leetcode--filter-regex', `leetcode--filter-tag' and `leetcode--filter-difficulty'." - (seq-filter - (lambda (row) - (and - (if leetcode--filter-regex - (let ((title (aref row 2))) - (string-match-p leetcode--filter-regex title)) - t) - (if leetcode--filter-tag - (let ((tags (split-string (leetcode--row-tags row) ", "))) - (member leetcode--filter-tag tags)) - t) - (if leetcode--filter-difficulty - (let ((difficulty (leetcode--row-difficulty row))) - (string= difficulty leetcode--filter-difficulty)) - t))) - rows)) - -(defun leetcode-reset-filter () - "Reset filter." - (interactive) - (setq leetcode--filter-regex nil) - (setq leetcode--filter-tag nil) - (setq leetcode--filter-difficulty nil) - (leetcode-refresh)) - -(defun leetcode-set-filter-regex (regex) - "Set `leetcode--filter-regex' as REGEX and refresh." - (interactive "sSearch: ") - (setq leetcode--filter-regex regex) - (leetcode-refresh)) - -(defun leetcode-set-filter-tag () - "Set `leetcode--filter-tag' from `leetcode--all-tags' and refresh." - (interactive) - (setq leetcode--filter-tag - (completing-read "Tags: " leetcode--all-tags)) - (leetcode-refresh)) - -(defun leetcode-set-prefer-language () - "Set `leetcode-prefer-language' from `leetcode--lang-suffixes' and refresh." - (interactive) - (setq leetcode-prefer-language - (completing-read "Language: " leetcode--lang-suffixes)) - (leetcode-refresh)) - -(defun leetcode-set-filter-difficulty () - "Set `leetcode--filter-difficulty' from `leetcode--all-difficulties' and refresh." - (interactive) - (setq leetcode--filter-difficulty - (completing-read "Difficulty: " leetcode--all-difficulties)) - (leetcode-refresh)) - -(defun leetcode-toggle-tag-display () - "Toggle `leetcode--display-tags` and refresh" - (interactive) - (setq leetcode--display-tags (not leetcode--display-tags)) - (leetcode-refresh)) - -(defun leetcode-toggle-paid-display () - "Toggle `leetcode--display-paid` and refresh" - (interactive) - (setq leetcode--display-paid (not leetcode--display-paid)) - (leetcode-refresh)) - -(aio-defun leetcode--fetch-all-tags () - (let* ((url-request-method "GET") - (url-request-extra-headers - `(,leetcode--User-Agent - ,leetcode--X-Requested-With - ,(leetcode--referer leetcode--url-login))) - (result (aio-await (aio-url-retrieve leetcode--api-all-tags)))) - (with-current-buffer (cdr result) - (goto-char url-http-end-of-headers) - (json-read)))) - -(aio-defun leetcode--fetch-user-and-problems () - "Fetch user and problems info." - (if leetcode--loading-mode - (message "LeetCode has been refreshing...") - (leetcode--loading-mode t) - (let ((url-request-method "GET") - (url-request-extra-headers - `(,leetcode--User-Agent - ,leetcode--X-Requested-With - ,(leetcode--referer leetcode--url-login))) - (result (aio-await (aio-url-retrieve leetcode--api-all-problems)))) - (leetcode--loading-mode -1) - (if-let ((error-info (plist-get (car result) :error))) - (progn - (switch-to-buffer (cdr result)) - (leetcode--warn "LeetCode fetch user and problems failed: %S" error-info)) - (with-current-buffer (cdr result) - (goto-char url-http-end-of-headers) - (json-read)))))) - -(defun leetcode-refresh () - "Make `tabulated-list-entries'." - (interactive) - (let* ((header-names (append '(" " "#" "Problem" "Acceptance" "Difficulty") - (if leetcode--display-tags '("Tags")))) - (rows (leetcode--filter (leetcode--problems-rows))) - (headers (leetcode--make-tabulated-headers header-names rows))) - (with-current-buffer (get-buffer-create leetcode--buffer-name) - (leetcode--problems-mode) - (setq tabulated-list-format headers) - (setq tabulated-list-entries - (cl-mapcar - (lambda (i x) (list i x)) - (number-sequence 0 (1- (length rows))) - rows)) - (tabulated-list-init-header) - (tabulated-list-print t) - (leetcode--loading-mode -1)))) - -(aio-defun leetcode-refresh-fetch () - "Refresh problems and update `tabulated-list-entries'." - (interactive) - (if-let ((users-and-problems (aio-await (leetcode--fetch-user-and-problems))) - (all-tags (aio-await (leetcode--fetch-all-tags)))) - (progn - (leetcode--set-user-and-problems users-and-problems) - (leetcode--set-tags all-tags)) - (leetcode--warn "LeetCode parse user and problems failed")) - (setq leetcode--display-tags leetcode-prefer-tag-display) - (leetcode-reset-filter) - (leetcode-refresh)) - -(aio-defun leetcode--async () - "Show leetcode problems buffer." - (if (get-buffer leetcode--buffer-name) - (switch-to-buffer leetcode--buffer-name) - (unless (leetcode--login-p) - (aio-await (leetcode--login))) - (aio-await (leetcode-refresh-fetch)) - (switch-to-buffer leetcode--buffer-name))) - -;;;###autoload -(defun leetcode () - "A wrapper for `leetcode--async', because emacs-aio can not be autoloaded. -see: https://github.com/skeeto/emacs-aio/issues/3." - (interactive) - (if (leetcode--check-deps) - (leetcode--async) - (message "installing leetcode dependencies..."))) - -;;;###autoload(autoload 'leetcode-daily "leetcode" nil t) -(aio-defun leetcode-daily () - "Open the daily challenge." - (interactive) - (unless (leetcode--login-p) - (aio-await (leetcode))) - (let* ((url-request-method "POST") - (url-request-extra-headers - `(,leetcode--User-Agent - ("Content-Type" . "application/json") - ,(leetcode--referer leetcode--url-login) - ,(cons leetcode--X-CSRFToken (leetcode--maybe-csrf-token)))) - (url-request-data - (json-encode - `((operationName . "todayRecord") - (query . ,leetcode--api-daily-challenge))))) - (with-current-buffer (url-retrieve-synchronously leetcode--api-graphql) - (goto-char url-http-end-of-headers) - (let-alist (json-read) - (let ((qid .data.todayRecord.0.question.questionFrontendId)) - (leetcode-show-problem (string-to-number qid))))))) - -(defun leetcode--buffer-content (buf) - "Get content without text properties of BUF." - (with-current-buffer buf - (buffer-substring-no-properties - (point-min) (point-max)))) - -(defun leetcode--get-slug-title-before-try/submit (code-buf) - "Get slug title before try or submit with CODE-BUF. -LeetCode require slug-title as the request parameters." - (with-current-buffer code-buf - (if leetcode-save-solutions - (file-name-base (cadr (split-string (buffer-name) "_"))) - (file-name-base (buffer-name))))) - -(aio-defun leetcode-try () - "Asynchronously test the code using customized testcase." - (interactive) - (leetcode--loading-mode t) - (aio-await (leetcode--csrf-token)) ;knock knock, whisper me the mysterious information - (leetcode--debug "csrf token: %s" (aio-await (leetcode--csrf-token))) - (let* ((code-buf (current-buffer)) - (testcase-buf (get-buffer leetcode--testcase-buffer-name)) - (slug-title (leetcode--get-slug-title-before-try/submit code-buf)) - (problem (seq-find (lambda (p) - (equal slug-title - (leetcode--slugify-title - (plist-get p :title)))) - (plist-get leetcode--all-problems :problems))) - (problem-id (plist-get problem :backend-id))) - (leetcode--debug "leetcode try slug-title: %s, problem-id: %s" slug-title problem-id) - (let* ((url-request-method "POST") - (url-request-extra-headers - `(,leetcode--User-Agent - ("Content-Type" . "application/json") - ,(leetcode--referer (format - leetcode--api-try-referer - slug-title)) - ,(cons leetcode--X-CSRFToken (aio-await (leetcode--csrf-token))))) - (url-request-data - (json-encode - `((data_input . ,(leetcode--buffer-content testcase-buf)) - (lang . ,leetcode--lang) - (question_id . ,problem-id) - (typed_code . ,(leetcode--buffer-content code-buf))))) - (result (aio-await (aio-url-retrieve (format leetcode--api-try slug-title))))) - (if-let ((error-info (plist-get (car result) :error))) - (progn - (switch-to-buffer (cdr result)) - (leetcode--warn "LeetCode try failed: %S" error-info)) - (let ((data (with-current-buffer (cdr result) - (goto-char url-http-end-of-headers) - (json-read))) - (res-buf (get-buffer leetcode--result-buffer-name))) - (let-alist data - (with-current-buffer res-buf - (erase-buffer) - (insert (concat "Your input:\n" .test_case "\n\n"))) - ;; poll expected - (leetcode--debug "interpret_expected_id: %s" .interpret_expected_id) - (let ((expect_res (aio-await (leetcode--check-submission .interpret_expected_id slug-title))) - (retry-times 0)) - (while (and (not expect_res) (< retry-times leetcode-retry-threshold)) - (aio-await (aio-sleep 0.5)) - (setq expect_res (aio-await (leetcode--check-submission .interpret_expected_id slug-title))) - (setq retry-times (1+ retry-times))) - (if (< retry-times leetcode-retry-threshold) - (let-alist expect_res - (with-current-buffer res-buf - (goto-char (point-max)) - (cond - ((eq .status_code 10) - (insert "Expected:\n") - (dotimes (i (length .code_answer)) - (insert (aref .code_answer i)) - (insert "\n")) - (insert "\n")) - ((not (eq .status_code 10)) - (insert "Expected:\nGot None\n")) - ))))) - - ;; poll interpreted - (let ((actual_res (aio-await (leetcode--check-submission .interpret_id slug-title))) - (retry-times 0)) - (while (and (not actual_res) (< retry-times leetcode-retry-threshold)) - (aio-await (aio-sleep 0.5)) - (setq actual_res (aio-await (leetcode--check-submission .interpret_id slug-title))) - (setq retry-times (1+ retry-times))) - (if (< retry-times leetcode-retry-threshold) - (let-alist actual_res - (with-current-buffer res-buf - (goto-char (point-max)) - (cond - ((eq .status_code 10) - (insert "Output:\n") - (dotimes (i (length .code_answer)) - (insert (aref .code_answer i)) - (insert "\n")) - (insert "\n") - (insert "Expected:\n") - (dotimes (i (length .expected_code_answer)) - (insert (aref .expected_code_answer i)) - (insert "\n")) - (insert "\n")) - ((eq .status_code 14) - (insert .status_msg)) - ((eq .status_code 15) - (insert .status_msg) - (insert "\n\n") - (insert .full_runtime_error)) - ((eq .status_code 20) - (insert .status_msg) - (insert "\n\n") - (insert .full_compile_error))) - (when (> (length .code_output) 0) - (insert "\n\n") - (insert "Code output:\n") - (dolist (item (append .code_output nil)) - (insert (concat item "\n")))) - (insert "\n\n"))) - (leetcode--warn "LeetCode try timeout."))) - (leetcode--loading-mode -1))))))) - -(aio-defun leetcode--check-submission (submission-id slug-title) - "Polling to check submission detail. -After each submission, either try testcase or submit, LeetCode -returns a SUBMISSION-ID. With the SUBMISSION-ID, client will poll -for the submission detail. SLUG-TITLE is a slugified problem -title. Return response data if submission success, otherwise -nil." - (leetcode--loading-mode t) - (let* ((url-request-method "GET") - (url-request-extra-headers - `(,leetcode--User-Agent - ,(leetcode--referer (format leetcode--api-problems-submission slug-title)) - ,(cons leetcode--X-CSRFToken (aio-await (leetcode--csrf-token))))) - (result (aio-await (aio-url-retrieve (format leetcode--api-check-submission submission-id))))) - (if-let ((error-info (plist-get (car result) :error))) - (progn - (leetcode--loading-mode -1) - (switch-to-buffer (cdr result)) - (leetcode--warn "LeetCode check submission failed: %S" error-info)) - (with-current-buffer (cdr result) - (let ((submission-res - (progn (goto-char url-http-end-of-headers) - (json-read)))) - (if (equal (alist-get 'state submission-res) "SUCCESS") - submission-res)))))) - -(defun leetcode--solving-layout () - "Specify layout for solving problem. -+---------------+----------------+ -| | | -| | Description | -| | | -| +----------------+ -| Code | Customize | -| | Testcases | -| +----------------+ -| |Submit/Testcases| -| | Result | -+---------------+----------------+" - (delete-other-windows) - (split-window-horizontally) - (other-window 1) - (split-window-below) - (other-window 1) - (split-window-below) - (other-window -1) - (other-window -1)) - -(defun leetcode--display-result (buffer &optional alist) - "Display function for LeetCode result. -BUFFER is the one to show LeetCode result. ALIST is a combined -alist specified in `display-buffer-alist'." - (let ((window (window-next-sibling - (window-next-sibling - (window-top-child - (window-next-sibling - (window-left-child - (frame-root-window)))))))) - (set-window-buffer window buffer) - window)) - -(defun leetcode--display-testcase (buffer &optional alist) - "Display function for LeetCode testcase. -BUFFER is the one to show LeetCode testcase. ALIST is a combined -alist specified in `display-buffer-alist'." - (let ((window (window-next-sibling - (window-top-child - (window-next-sibling - (window-left-child - (frame-root-window))))))) - (set-window-buffer window buffer) - window)) - -(defun leetcode--display-code (buffer &optional alist) - "Display function for LeetCode code. -BUFFER is the one to show LeetCode code. ALIST is a combined -alist specified in `display-buffer-alist'." - (let ((window (window-left-child (frame-root-window)))) - (set-window-buffer window buffer) - window)) - -(defun leetcode--show-submission-result (submission-detail) - "Show error info in `leetcode--result-buffer-name' based on status code. -Error info comes from SUBMISSION-DETAIL. STATUS_CODE has -following possible value: -- 10: Accepted -- 11: Wrong Anwser -- 14: Time Limit Exceeded -- 15: Runtime Error. full_runtime_error -- 20: Compile Error. full_compile_error" - (let-alist submission-detail - (with-current-buffer (get-buffer-create leetcode--result-buffer-name) - (erase-buffer) - (insert (format "Status: %s" .status_msg)) - (cond - ((eq .status_code 10) - (insert (format " (%s/%s)\n\n" .total_correct .total_testcases)) - (insert (format "Runtime: %s, faster than %.2f%% of %s submissions.\n\n" - .status_runtime .runtime_percentile .pretty_lang)) - (insert (format "Memory Usage: %s, less than %.2f%% of %s submissions." - .status_memory .memory_percentile .pretty_lang))) - ((eq .status_code 11) - (insert (format " (%s/%s)\n\n" .total_correct .total_testcases)) - (insert (format "Test Case: \n%s\n\n" .input)) - (insert (format "Answer: %s\n\n" .code_output)) - (insert (format "Expected Answer: %s\n\n" .expected_output)) - (insert (format "Stdout: \n%s\n" .std_output))) - ((eq .status_code 14) - (insert "\n")) - ((eq .status_code 15) - (insert "\n\n") - (insert (format (alist-get 'full_runtime_error submission-detail)))) - ((eq .status_code 20) - (insert "\n\n") - (insert (format (alist-get 'full_compile_error submission-detail))))) - (display-buffer (current-buffer) - '((display-buffer-reuse-window - leetcode--display-result) - (reusable-frames . visible)))))) - -(aio-defun leetcode-submit () - "Asynchronously submit the code and show result." - (interactive) - (leetcode--loading-mode t) - (let* ((code-buf (current-buffer)) - (code (leetcode--buffer-content code-buf)) - (slug-title (leetcode--get-slug-title-before-try/submit code-buf)) - (problem-id (plist-get (seq-find (lambda (p) - (equal slug-title - (leetcode--slugify-title - (plist-get p :title)))) - (plist-get leetcode--all-problems :problems)) - :backend-id))) - (leetcode--debug "leetcode submit slug-title: %s, problem-id: %s" slug-title problem-id) - (let* ((url-request-method "POST") - (url-request-extra-headers - `(,leetcode--User-Agent - ,(leetcode--referer (format - leetcode--api-problems-submission - slug-title)) - ,(cons "Content-Type" "application/json") - ,(cons leetcode--X-CSRFToken (aio-await (leetcode--csrf-token))))) - (url-request-data - (json-encode `((lang . ,leetcode--lang) - (question_id . ,problem-id) - (typed_code . ,code)))) - (result (aio-await (aio-url-retrieve (format leetcode--api-submit slug-title))))) - (if-let ((error-info (plist-get (car result) :error))) - (progn - (leetcode--loading-mode -1) - (switch-to-buffer (cdr result)) - (leetcode--warn "LeetCode check submit failed: %S" error-info)) - (let* ((resp - (with-current-buffer (cdr result) - (progn (goto-char url-http-end-of-headers) - (json-read)))) - (submission-id (alist-get 'submission_id resp)) - (submission-res (aio-await (leetcode--check-submission submission-id slug-title))) - (retry-times 0)) - ;; poll submission result - (while (and (not submission-res) (< retry-times leetcode-retry-threshold)) - (aio-await (aio-sleep 0.5)) - (setq submission-res (aio-await (leetcode--check-submission submission-id slug-title))) - (setq retry-times (1+ retry-times))) - (if (< retry-times leetcode-retry-threshold) - (leetcode--show-submission-result submission-res) - (leetcode--warn "LeetCode submit timeout.")) - (leetcode--loading-mode -1)))))) - -(defun leetcode--problem-link (title) - "Generate problem link from TITLE." - (concat leetcode--base-url "/problems/" (leetcode--slugify-title title))) - -(defun leetcode--show-problem (problem problem-info) - "Show the description of PROBLEM, whose meta data is PROBLEM-INFO. -Use `shr-render-buffer' to render problem description. This action -will show the description in other window and jump to it." - (let* ((problem-id (plist-get problem-info :id)) - (title (plist-get problem-info :title)) - (difficulty-level (plist-get problem-info :difficulty)) - (difficulty (leetcode--stringify-difficulty difficulty-level)) - (buf-name leetcode--description-buffer-name) - (html-margin "    ")) - (leetcode--debug "select title: %s" title) - (let-alist problem - (when (get-buffer buf-name) - (kill-buffer buf-name)))) - (let* ((content-val (alist-get 'content problem)) - (translated-val (alist-get 'translatedContent problem)) - (likes-val (alist-get 'likes problem)) - (dislikes-val (alist-get 'dislikes problem)) - (desc-content (if (and content-val - (string-match-p "English description is not available" content-val)) - translated-val - content-val))) - (with-temp-buffer - (insert (concat "<h1>" (number-to-string problem-id) ". " title "</h1>")) - (insert (concat (capitalize difficulty) html-margin - "likes: " (number-to-string likes-val) html-margin - "dislikes: " (number-to-string dislikes-val))) - (insert "<hr/>") - ;; Convert markdown to HTML - (let ((converted (leetcode--markdown-to-html (or desc-content "")))) - (insert converted)) - (setq shr-current-font t) - ;; NOTE: shr.el can't render "https://xxxx.png", so we use "http" - (leetcode--replace-in-buffer "https" "http") - (shr-render-buffer (current-buffer))) - (with-current-buffer "*html*" - (save-match-data - (re-search-forward "dislikes: .*" nil t) - (insert (make-string 4 ?\s)) - (insert-text-button "Solve it" - 'action (lambda (btn) - (leetcode--start-coding problem problem-info)) - 'help-echo "solve the problem.") - (insert (make-string 4 ?\s)) - (insert-text-button "Link" - 'action (lambda (btn) - (browse-url (leetcode--problem-link title))) - 'help-echo "open the problem in browser.") - (insert (make-string 4 ?\s)) - (insert-text-button "Solution" - 'action (lambda (btn) - (browse-url (concat (leetcode--problem-link title) "/solution"))) - 'help-echo "Open the problem solution page in browser.") - ;; Would be best to parse the solution in Emacs, but the url-retrieve-synchronously only get the Javascript which generate the solution in HTML, not directly text - ) - (rename-buffer buf-name) - (leetcode--problem-description-mode) - (switch-to-buffer (current-buffer)))))) - -(aio-defun leetcode-show-problem (problem-id) - "Show the description of problem with id PROBLEM-ID. -Get problem by id and use `shr-render-buffer' to render problem -description. This action will show the description in other -window and jump to it." - (interactive (list (read-number "Show problem by problem id: " - (leetcode--get-current-problem-id)))) - (let* ((problem-info (leetcode--get-problem-by-id problem-id)) - (title-slug (or (plist-get problem-info :title-slug) - (leetcode--slugify-title (plist-get problem-info :title)))) - (problem (aio-await (leetcode--fetch-problem title-slug)))) - (leetcode--show-problem problem problem-info))) - -(defun leetcode-show-problem-by-slug (slug-title) - "Show the description of problem with slug title. This function will work after first run M-x leetcode. This can be used with org-link elisp:(leetcode-show-problem-by-slug \"two-sum\"). -Get problem by slug title and use `shr-render-buffer' to render problem -description. This action will show the description in other -window and jump to it." - (interactive (list (completing-read "Problem slug (e.g., two-sum): " - (mapcar (lambda (p) - (leetcode--slugify-title (plist-get p :title))) - (plist-get leetcode--all-problems :problems))))) - (let* ((problem (seq-find (lambda (p) - (equal slug-title - (leetcode--slugify-title - (plist-get p :title)))) - (plist-get leetcode--all-problems :problems))) - (problem-id (plist-get problem :id)) - (problem-info (leetcode--get-problem-by-id problem-id)) - (title-slug (or (plist-get problem-info :title-slug) - (leetcode--slugify-title (plist-get problem-info :title)))) - (problem (leetcode--fetch-problem title-slug))) - (leetcode-show-problem problem-id))) - -(defun leetcode-show-current-problem () - "Show current problem's description. -Call `leetcode-show-problem' on the current problem id. This -action will show the description in other window and jump to it." - (interactive) - (leetcode-show-problem (leetcode--get-current-problem-id))) - -(aio-defun leetcode-view-problem (problem-id) - "View problem by PROBLEM-ID while staying in `LC Problems' window. -Similar with `leetcode-show-problem', but instead of jumping to the -description window, this action will jump back in `LC Problems'." - (interactive (list (read-number "View problem by problem id: " - (leetcode--get-current-problem-id)))) - (aio-await (leetcode-show-problem problem-id)) - (leetcode--jump-to-window-by-buffer-name leetcode--buffer-name)) - -(defun leetcode-view-current-problem () - "View current problem while staying in `LC Problems' window. -Similar with `leetcode-show-current-problem', but instead of jumping to -the description window, this action will jump back in `LC Problems'." - (interactive) - (leetcode-view-problem (leetcode--get-current-problem-id))) - -(defun leetcode-show-problem-in-browser (problem-id) - "Open the problem with id PROBLEM-ID in browser." - (interactive (list (read-number "Show in browser by problem id: " - (leetcode--get-current-problem-id)))) - (let* ((problem (leetcode--get-problem-by-id problem-id)) - (title (plist-get problem :title)) - (link (leetcode--problem-link title))) - (leetcode--debug "Open in browser: %s" link) - (browse-url link))) - -(defun leetcode-show-current-problem-in-browser () - "Open the current problem in browser. -Call `leetcode-show-problem-in-browser' on the current problem id." - (interactive) - (leetcode-show-problem-in-browser (leetcode--get-current-problem-id))) - -(aio-defun leetcode-solve-problem (problem-id) - "Start coding the problem with id PROBLEM-ID." - (interactive (list (read-number "Solve the problem with id: " - (leetcode--get-current-problem-id)))) - (let* ((problem-info (leetcode--get-problem-by-id problem-id)) - (title-slug (or (plist-get problem-info :title-slug) - (leetcode--slugify-title (plist-get problem-info :title)))) - (problem (aio-await (leetcode--fetch-problem title-slug)))) - (leetcode--show-problem problem problem-info) - (leetcode--start-coding problem problem-info))) - -(defun leetcode-solve-current-problem () - "Start coding the current problem. -Call `leetcode-solve-problem' on the current problem id." - (interactive) - (leetcode-solve-problem (leetcode--get-current-problem-id))) - -(defun leetcode--jump-to-window-by-buffer-name (buffer-name) - "Jump to window by BUFFER-NAME." - (select-window (get-buffer-window buffer-name))) - -(defun leetcode--kill-buff-and-delete-window (buf) - "Kill BUF and delete its window." - (delete-windows-on buf t) - (kill-buffer buf)) - -(defun leetcode-quit () - "Close and delete leetcode related buffers and windows." - (interactive) - (leetcode--kill-buff-and-delete-window (get-buffer leetcode--buffer-name)) - (leetcode--kill-buff-and-delete-window (get-buffer leetcode--description-buffer-name)) - (leetcode--kill-buff-and-delete-window (get-buffer leetcode--result-buffer-name)) - (leetcode--kill-buff-and-delete-window (get-buffer leetcode--testcase-buffer-name)) - (mapc (lambda (title) - (leetcode--kill-buff-and-delete-window - (get-buffer (leetcode--get-code-buffer-name title)))) - leetcode--problem-titles)) - -(defcustom leetcode-prefer-tag-display t - "Whether to display tags by default in the *leetcode* buffer." - :type :boolean) - -;;(defcustom leetcode-use-cn-source nil -;; "Whether to use leetcode-cn.com source." -;; :type 'boolean -;; :group 'leetcode) - -(defvar leetcode--display-tags leetcode-prefer-tag-display - "(Internal) Whether tags are displayed the *leetcode* buffer.") - -(defvar leetcode--display-paid nil - "(Internal) Whether paid problems are displayed the *leetcode* buffer.") - -(defvar leetcode-prefer-language "python3" - "LeetCode programming language. -c, cpp, csharp, golang, java, javascript, typescript, kotlin, php, python, -python3, ruby, rust, scala, swift.") - -(defvar leetcode-prefer-sql "mysql" - "LeetCode sql implementation. -mysql, mssql, oraclesql.") - -(defvar leetcode-directory "~/leetcode" - "Directory to save solutions.") - -(defvar leetcode-save-solutions nil - "If it's t, save leetcode solutions to `leetcode-directory'.") - -(defvar leetcode--lang leetcode-prefer-language - "LeetCode programming language or sql for current problem internally. -Default is programming language.") - -(defconst leetcode--lang-suffixes - '(("c" . ".c") ("cpp" . ".cpp") ("csharp" . ".cs") - ("golang" . ".go") ("java" . ".java") ("javascript" . ".js") - ("typescript" . ".ts") ("kotlin" . ".kt") ("php" . ".php") - ("python" . ".py") ("python3" . ".py") ("ruby" . ".rb") - ("rust" . ".rs") ("scala" . ".scala") ("swift" . ".swift") - ("mysql" . ".sql") ("mssql" . ".sql") ("oraclesql" . ".sql")) - "LeetCode programming language suffixes. -c, cpp, csharp, golang, java, javascript, typescript, kotlin, php, python, -python3, ruby, rust, scala, swift, mysql, mssql, oraclesql.") - -(defun leetcode--set-lang (snippets) - "Set `leetcode--lang' based on langSlug in SNIPPETS." - (setq leetcode--lang - (if (seq-find (lambda (s) - (equal (alist-get 'langSlug s) - leetcode-prefer-sql)) - snippets) - leetcode-prefer-sql - leetcode-prefer-language))) - -(defun leetcode--get-code-buffer-name (title) - "Get code buffer name by TITLE and `leetcode-prefer-language'." - (let* ((suffix (assoc-default - leetcode--lang - leetcode--lang-suffixes)) - (slug-title (leetcode--slugify-title title)) - (title-with-suffix (concat slug-title suffix))) - (if leetcode-save-solutions - (format "%04d_%s" (leetcode--get-problem-id slug-title) title-with-suffix) - title-with-suffix))) - -(defun leetcode--get-code-buffer (buf-name) - "Get code buffer by BUF-NAME." - (if (not leetcode-save-solutions) - (get-buffer-create buf-name) - (unless (file-directory-p leetcode-directory) - (make-directory leetcode-directory)) - (find-file-noselect - (concat (file-name-as-directory leetcode-directory) - buf-name)))) - -(defun leetcode--get-problem (slug-title) - "Get problem from `leetcode--all-problems' by SLUG-TITLE." - (seq-find (lambda (p) - (equal slug-title - (leetcode--slugify-title - (plist-get p :title)))) - (plist-get leetcode--all-problems :problems))) - -(defun leetcode--get-problem-by-id (id) - "Get problem from `leetcode--all-problems' by ID." - (seq-find (lambda (p) - (equal id (plist-get p :id))) - (plist-get leetcode--all-problems :problems))) - -(defun leetcode--get-problem-id (slug-title) - "Get problem id by SLUG-TITLE." - (let ((problem (leetcode--get-problem slug-title))) - (plist-get problem :id))) - -(defun leetcode--get-current-problem-id () - "Get id of the current problem." - (string-to-number (aref (tabulated-list-get-entry) 1))) - -(defun leetcode--start-coding (problem problem-info) - "Create a buffer for coding PROBLEM with meta-data PROBLEM-INFO. -The buffer will be not associated with any file. It will choose -major mode by `leetcode-prefer-language'and `auto-mode-alist'." - (let-alist problem - (let* ((title (plist-get problem-info :title)) - (snippets (append .codeSnippets nil)) - (testcase .sampleTestCase)) - (add-to-list 'leetcode--problem-titles title) - (leetcode--solving-layout) - (leetcode--set-lang snippets) - (let* ((slug-title (leetcode--slugify-title title)) - (buf-name (leetcode--get-code-buffer-name title)) - (code-buf (get-buffer buf-name)) - (suffix (assoc-default - leetcode--lang - leetcode--lang-suffixes))) - (unless code-buf - (with-current-buffer (leetcode--get-code-buffer buf-name) - (setq code-buf (current-buffer)) - (funcall (assoc-default suffix auto-mode-alist #'string-match-p)) - (let* ((snippet (seq-find (lambda (s) - (equal (alist-get 'langSlug s) - leetcode--lang)) - snippets)) - (template-code (alist-get 'code snippet))) - (unless (save-mark-and-excursion - (goto-char (point-min)) - (search-forward (string-trim template-code) nil t)) - (insert template-code)) - (leetcode--replace-in-buffer " -" "")))) - - (display-buffer code-buf - '((display-buffer-reuse-window - leetcode--display-code) - (reusable-frames . visible)))) - (with-current-buffer (get-buffer-create leetcode--testcase-buffer-name) - (erase-buffer) - (insert testcase) - (display-buffer (current-buffer) - '((display-buffer-reuse-window - leetcode--display-testcase) - (reusable-frames . visible)))) - (with-current-buffer (get-buffer-create leetcode--result-buffer-name) - (erase-buffer) - (display-buffer (current-buffer) - '((display-buffer-reuse-window - leetcode--display-result) - (reusable-frames . visible)))) - ))) - -(defvar leetcode--problems-mode-map - (let ((map (make-sparse-keymap))) - (prog1 map - (suppress-keymap map) - (define-key map (kbd "RET") #'leetcode-show-current-problem) - (define-key map (kbd "TAB") #'leetcode-view-current-problem) - (define-key map "o" #'leetcode-show-current-problem) - (define-key map "O" #'leetcode-show-problem) - (define-key map "v" #'leetcode-view-current-problem) - (define-key map "V" #'leetcode-view-problem) - (define-key map "b" #'leetcode-show-current-problem-in-browser) - (define-key map "B" #'leetcode-show-problem-in-browser) - (define-key map "c" #'leetcode-solve-current-problem) - (define-key map "C" #'leetcode-solve-problem) - (define-key map "n" #'next-line) - (define-key map "p" #'previous-line) - (define-key map "s" #'leetcode-set-filter-regex) - (define-key map "l" #'leetcode-set-prefer-language) - (define-key map "t" #'leetcode-set-filter-tag) - (define-key map "T" #'leetcode-toggle-tag-display) - (define-key map "P" #'leetcode-toggle-paid-display) - (define-key map "d" #'leetcode-set-filter-difficulty) - (define-key map "g" #'leetcode-refresh) - (define-key map "G" #'leetcode-refresh-fetch) - (define-key map "/" #'leetcode-reset-filter) - (define-key map "q" #'quit-window))) - "Keymap for `leetcode--problems-mode'.") - -(define-derived-mode leetcode--problems-mode - tabulated-list-mode "LC Problems" - "Major mode for browsing a list of problems." - (setq tabulated-list-padding 2) - (add-hook 'tabulated-list-revert-hook #'leetcode-refresh nil t) - :group 'leetcode) - -(add-hook 'leetcode--problems-mode-hook #'hl-line-mode) - -(defvar leetcode--problem-description-mode-map - (let ((map (make-sparse-keymap))) - (prog1 map - (suppress-keymap map) - (define-key map "n" #'next-line) - (define-key map "p" #'previous-line))) - "Keymap for `leetcode--problem-description-mode'.") - -(define-derived-mode leetcode--problem-description-mode - special-mode "LC Descri" - "Major mode for display problem description." - :group 'leetcode) - -;;; Use spinner.el to show progress indicator -(defvar leetcode--spinner (spinner-create 'progress-bar-filled) - "Progress indicator to show request progress.") -(defconst leetcode--loading-lighter - '(" [LeetCode" (:eval (spinner-print leetcode--spinner)) "]")) - -(define-minor-mode leetcode--loading-mode - "Minor mode to showing leetcode loading status." - :require 'leetcode - :lighter leetcode--loading-lighter - :group 'leetcode - (if leetcode--loading-mode - (spinner-start leetcode--spinner) - (spinner-stop leetcode--spinner))) - -(provide 'packages-leetcode) -;;; leetcode.el ends here diff --git a/packages/packages-misc.el b/packages/packages-misc.el deleted file mode 100644 index baa7537..0000000 --- a/packages/packages-misc.el +++ /dev/null @@ -1,8 +0,0 @@ -;; -*- lexical-binding: t; -*- -(use-package hugoista - :ensure t - :custom - (hugoista-site-dir "~/blog") - (hugoista-hugo-command "~/go/bin/hugo")) - -(provide 'packages-misc) diff --git a/packages/packages-ui.el b/packages/packages-ui.el deleted file mode 100644 index 7f7e303..0000000 --- a/packages/packages-ui.el +++ /dev/null @@ -1,41 +0,0 @@ -;; -*- lexical-binding: t; y-*- -(use-package ace-window - :ensure t - :bind - (("C-x o" . ace-window))) - -(use-package counsel - :ensure t) - -(use-package dashboard - :ensure t - :config - (setq dashboard-startup-banner 'logo - dashboard-banner-logo-title "Welcome to Verdant's Emacverse!!!" - dashboard-center-content t - dashboard-set-heading-icons t - dashboard-items '((recents . 10) - (bookmarks . 5)) - dashboard-footer-messages '("verdant.el")) - - ;; 核心三件套 - (setq initial-buffer-choice (lambda () (get-buffer-create "*dashboard*"))) - (dashboard-setup-startup-hook) - (add-hook 'after-init-hook #'dashboard-open t)) - -(use-package ivy-posframe - :ensure t - :config - (ivy-posframe-mode t)) - -(use-package ivy-rich - :ensure t - :config - (ivy-rich-mode t)) - -(use-package diredfl - :ensure t - :hook (dired-mode . diredfl-mode)) - - -(provide 'packages-ui) diff --git a/packages/packages.el b/packages/packages.el deleted file mode 100644 index ea0032b..0000000 --- a/packages/packages.el +++ /dev/null @@ -1,14 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'package) -(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) -(add-to-list 'package-archives '("gnu" . "https://https://elpa.gnu.org/packages/") t) -(add-to-list 'package-archives '("melpa" . "https://mirrors.ustc.edu.cn/elpa/melpa/") t) -(add-to-list 'package-archives '("nongnu" . "https://mirrors.ustc.edu.cn/elpa/nongnu/") t) -(package-initialize) - -(require 'packages-editing) -(require 'packages-ui) -(require 'packages-email) -(require 'packages-misc) -;; (require 'packages-leetcode) -(provide 'packages) diff --git a/tramp b/tramp index f0166e1..1c61d00 100644 --- a/tramp +++ b/tramp @@ -1,4 +1,4 @@ -;; -*- lisp-data -*- <26/04/25 10:36:12 ~/.emacs.d/tramp> +;; -*- lisp-data -*- <26/05/02 00:10:27 ~/.emacs.d/tramp> ;; Tramp connection history. Don't change this file. ;; Run `M-x tramp-cleanup-all-connections' instead. -- cgit v1.2.3