;;; package --- Summary: Emacs init ;;; Commentary: ;; Main features are LSP support and Evil mode ;;; Code: ;; =========================== ;; Package Management Setup ;; =========================== (require 'package) ;; Add MELPA, Org, and GNU package archives (setq package-archives '(("melpa" . "https://melpa.org/packages/") ("org" . "https://orgmode.org/elpa/") ("gnu" . "https://elpa.gnu.org/packages/"))) (package-initialize) ;; Refresh package list if it's not already available (unless package-archive-contents (package-refresh-contents)) ;; Install use-package if it's not already installed (unless (package-installed-p 'use-package) (package-install 'use-package)) (require 'use-package) (setq use-package-always-ensure t) ;; Always ensure packages are installed ;; =========================== ;; Theme Configuration ;; =========================== ;; Define your preferred light and dark themes (setq dark-theme 'zenburn) (setq light-theme 'solarized-light) ;; Install Themes (use-package solarized-theme :ensure t) (use-package zenburn-theme :ensure t) (defun my/get-system-appearance () (cond ;; --------------------------- ;; macOS ;; --------------------------- ((eq system-type 'darwin) (if (string= (shell-command-to-string "defaults read -g AppleInterfaceStyle 2>/dev/null") "Dark\n") 'dark 'light)) ;; --------------------------- ;; Windows ;; --------------------------- ((eq system-type 'windows-nt) ;; NOTE: For Windows 10/11, “AppsUseLightTheme” is under: ;; HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize ;; Value of 1 => Light, 0 => Dark (let ((output (shell-command-to-string "reg query \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\" /v AppsUseLightTheme 2>nul"))) (cond ((string-match "0x1" output) 'light) ((string-match "0x0" output) 'dark) (t nil)))) ;; --------------------------- ;; GNOME-based Linux ;; --------------------------- ((eq system-type 'gnu/linux) ;; If you're on GNOME 42+, `color-scheme` often shows 'prefer-dark' or 'prefer-light'. ;; If the user is using an older system or a different DE, this may fail or return an empty string. (let ((gsettings-output (string-trim (shell-command-to-string "gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null")))) (cond ((string-match "dark" gsettings-output) 'dark) ((string-match "light" gsettings-output) 'light) (t nil)))) ;; --------------------------- ;; Otherwise, undetermined ;; --------------------------- (t nil))) (defun my/set-theme-based-on-appearance () "Set Emacs theme based on system appearance if possible, otherwise fallback." (let ((appearance (my/get-system-appearance))) ;; Disable all currently enabled themes to prevent conflicts (mapc #'disable-theme custom-enabled-themes) (pcase appearance ('dark (load-theme dark-theme t)) ('light (load-theme light-theme t)) (_ ;; If we cannot detect the theme, default to light (load-theme light-theme t))))) ;; Apply theme at startup (my/set-theme-based-on-appearance) ;; =========================== ;; UI Customizations ;; =========================== ;; Use ibuffer, instead of Buffer-menu-mode. (global-set-key (kbd "C-x C-b") 'ibuffer) ;; Replace yes-no prompts to y-n (defalias 'yes-or-no-p 'y-or-n-p) ;; Disable unnecessary UI elements (tool-bar-mode -1) ;; Hide the toolbar (menu-bar-mode -1) ;; Hide the menu bar (scroll-bar-mode -1) ;; Hide the scroll bar ;; Enable useful UI features (column-number-mode 1) ;; Show column numbers (global-display-line-numbers-mode 1) ;; Enable global line numbers ;; Enable visual line mode globally (global-visual-line-mode 1) ;; Disable line numbers for some modes (dolist (mode '(dired-mode-hook doc-view-mode-hook eshell-mode-hook comint-mode-hook inferior-python-mode-hook org-mode-hook shell-mode-hook term-mode-hook)) (add-hook mode (lambda () (display-line-numbers-mode 0)))) ;; Complete parentheses, quotes, etc (electric-pair-mode 1) ;; Set Default Font to Iosevka 18pt ;;(set-face-attribute 'default nil :family "Iosevka" :height 150) ;; Set font (set-face-attribute 'default nil :family "JetBrains Mono Nerd Font" :height 140 :weight 'normal :width 'normal) ;; Line spacing (setq-default line-spacing 0) ;; Backup files -> ~/.emacs.d/backups/ (setq backup-directory-alist `(("." . "~/.emacs.d/backups")) backup-by-copying t ;; Don't delink hardlinks version-control t ;; Use version numbers on backups delete-old-versions t ;; Automatically delete excess backups kept-new-versions 6 kept-old-versions 2) ;; =========================== ;; macOS Specific Settings ;; =========================== (when (eq system-type 'darwin) ;; Use Command key as Meta (setq mac-command-modifier 'meta mac-option-modifier 'none) ;; Better font rendering on macOS (setq ns-use-srgb-colorspace t) ;; Add homebrew (add-to-list 'exec-path "/opt/homebrew/bin") ) ;; =========================== ;; Evil Mode Configuration ;; =========================== (use-package evil :ensure t :init (setq evil-want-integration t evil-want-keybinding nil) :config (evil-mode 1) ;; Force modes to start in Emacs state ;;(dolist (mode '(;;Buffer-menu-mode ;;;;ibuffer-mode ;;dired-mode ;;help-mode ;;bookmark-bmenu-mode ;;gnus-summary-mode ;;gnus-group-mode ;;package-menu-mode)) ;;(evil-set-initial-state mode 'emacs)) ;; Correct keybindings for minibuffer maps using actual keymap objects (dolist (keymap '(minibuffer-local-map minibuffer-local-ns-map minibuffer-local-completion-map minibuffer-local-must-match-map minibuffer-local-isearch-map)) (define-key (symbol-value keymap) [escape] 'minibuffer-keyboard-quit)) ;; Set escape to quit in Evil states (with-eval-after-load 'evil-maps (define-key evil-normal-state-map [escape] 'keyboard-quit) (define-key evil-visual-state-map [escape] 'keyboard-quit)) (keymap-global-set "C-u" 'evil-scroll-up)) (use-package evil-collection :after evil :ensure t :config (evil-collection-init)) ;;(defun my/buffer-menu-evil-fix () ;;"Disable Evil's C-n and C-p in `Buffer-menu-mode'." ;;(evil-define-key 'normal Buffer-menu-mode-map ;;(kbd "C-n") nil ;;(kbd "C-p") nil)) ;; ;;(add-hook 'buffer-menu-mode-hook #'my/buffer-menu-evil-fix) ;; =========================== ;; Additional Packages ;; =========================== ;; Skip helm for now, since it interferes with evil ;;(use-package helm ;;:ensure t ;;:init ;;(setq helm-display-function #'helm-default-display-buffer) ;; Fix potential display issues ;;:config ;;(helm-mode 1) ;;(define-key helm-map (kbd "") 'helm-execute-persistent-action) ;; Better navigation ;;(define-key helm-map (kbd "C-z") 'helm-select-action)) ;; Show actions list ;; Install and enable Company Mode globally (use-package company :init (global-company-mode 1) :config ;; Integrate Company with LSP (setq company-minimum-prefix-length 1 company-idle-delay 0.0)) ;; Show completions immediately (use-package projectile :init (projectile-mode) :config (setq projectile-project-root-files '(".git" ".lsp-root")) ;;(setq projectile-project-root-files-bottom-up '(".git" ".clangd")) (projectile-mode +1)) ;; Install and bind Magit (use-package magit :bind ("C-x g" . magit-status)) ;; Install and enable Which-Key (use-package which-key :config (which-key-mode)) (use-package rainbow-delimiters :ensure t :hook (prog-mode . rainbow-delimiters-mode)) ;; =========================== ;; LSP Configuration ;; =========================== (use-package lsp-mode :hook ((python-mode . lsp) (kotlin-mode . lsp) (c++-mode . lsp) (c-mode . lsp) (rust-mode . lsp)) :commands lsp :config (setq lsp-prefer-flymake nil) (setq lsp-enable-snippet t) (setq lsp-headerline-breadcrumb-enable nil) ;; Enhanced project root detection with debug logging (defun my/lsp-get-project-root () "Find the project root by locating the nearest .git directory with validation." (let* ((current-file (or (buffer-file-name) default-directory)) (root-dir (locate-dominating-file current-file ".git")) (valid-root (when root-dir (expand-file-name root-dir)))) (when valid-root (message "[LSP] Detected project root: %s" valid-root) valid-root))) ;; Set project root detection for C/C++ before LSP initializes (add-hook 'c-mode-hook (lambda () (setq-local lsp-project-root-function #'my/lsp-get-project-root) (message "[LSP] C mode root: %s" (my/lsp-get-project-root)))) (add-hook 'c++-mode-hook (lambda () (setq-local lsp-project-root-function #'my/lsp-get-project-root) (message "[LSP] C++ mode root: %s" (my/lsp-get-project-root)))) ;; Configure ignored directories for better performance (setq lsp-file-watch-ignored '("[/\\\\]\\.git$" "[/\\\\]build$" "[/\\\\]bin$" "[/\\\\]tests$"))) (use-package lsp-ui :commands lsp-ui-mode :config (setq lsp-ui-doc-enable t lsp-ui-doc-delay 0.2 lsp-ui-sideline-enable t lsp-ui-imenu-enable t)) (use-package lsp-treemacs :commands lsp-treemacs-errors-list) (use-package flycheck :init (global-flycheck-mode)) ;; =========================== ;; Language-Specific Configurations ;; =========================== ;; Python (use-package lsp-pyright :ensure t :custom (lsp-pyright-langserver-command "pyright") ;; or basedpyright :hook (python-mode . (lambda () (require 'lsp-pyright) (lsp)))) ; or lsp-deferred (use-package python :ensure nil :hook (python-mode . lsp) :config (setq python-shell-interpreter "python3") ) (use-package poetry :ensure t) ;; Kotlin (use-package kotlin-mode :mode "\\.kt\\'" :config ) ;; C++ (use-package cc-mode :ensure nil :config ) ;; Rust (use-package rust-mode :hook (rust-mode . lsp) :config (setq rust-format-on-save t) ) ;; =========================== ;; C/C++ Line Length Configuration ;; =========================== (defun my/c-mode-line-limit () "Set 80-character line limit for C/C++ files." (setq fill-column 80) (display-fill-column-indicator-mode 1) ) (add-hook 'c-mode-hook #'my/c-mode-line-limit) (message "Emacs init loaded.") (custom-set-variables ;; custom-set-variables was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. '(helm-minibuffer-history-key "M-p") '(package-selected-packages '(helm-evil rainbow-delimiters rainbow-delimiter catppuccin-theme lsp-pyright zenburn-theme which-key solarized-theme rust-mode projectile poetry magit lsp-ui lsp-treemacs kotlin-mode helm flycheck evil-collection dracula-theme company better-defaults))) (custom-set-faces ;; custom-set-faces was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. )