diff options
Diffstat (limited to 'emacs')
| -rw-r--r-- | emacs/.emacs.d/init.org | 420 | ||||
| -rwxr-xr-x | emacs/.emacs.d/scripts/find-php-class | 74 |
2 files changed, 447 insertions, 47 deletions
diff --git a/emacs/.emacs.d/init.org b/emacs/.emacs.d/init.org index 1091549..bed40b1 100644 --- a/emacs/.emacs.d/init.org +++ b/emacs/.emacs.d/init.org @@ -118,7 +118,357 @@ I currently develop in PHP. #+begin_src emacs-lisp (use-package php-mode - :ensure-system-package php) + :ensure-system-package php + :config + (require 'align) + (require 'grep) + (require 'hydra) + (require 'map) + (require 'php-mode) + (require 'project) + (require 'whitespace) + + (defconst oni-php-scripts-dir + (expand-file-name "scripts" user-emacs-directory) + "The directory where I store scripts.") + + (defun oni-php--set-require-final-newline () + "Set `require-final-newline' to t. + This is necessary because the PHP mode configuration sets this to nil + for some reason." + (setq require-final-newline t)) + + (defun oni-php--whitespace-mode () + "Enable whitespace mode with only tabs showing." + (setq-local whitespace-style '(face tabs)) + (whitespace-mode)) + + (defun oni-php--auto-fill-mode () + "Enable ‘auto-fill-mode’ only for comments." + (setq-local comment-auto-fill-only-comments t) + (auto-fill-mode)) + + (defun oni-php-set-rainbow-identifier-faces () + "Set the faces to override for ‘rainbow-identifiers-mode’." + (setq-local rainbow-identifiers-faces-to-override + '(font-lock-type-face php-property-name php-function-call-traditional + php-doc-variable-sigil php-method-call-traditional + php-variable-sigil php-variable-name))) + + (defun oni-php-add-use (class) + "Try to add a use statement for CLASS under point." + (interactive (list (thing-at-point 'symbol))) + (let* ((default-directory (project-root (project-current))) + (classes (read (shell-command-to-string (concat (expand-file-name "find-php-class" oni-php-scripts-dir) " " class)))) + (class (cond + ((null classes) + (user-error "Class ‘%s’ not found" class)) + ((> (length classes) 1) + (completing-read "Use: " classes nil t)) + (t (car classes))))) + (save-excursion + (goto-char (point-min)) + + (if (search-forward "use " nil t) + (forward-paragraph) + (search-forward "namespace ") + (forward-line) + (insert "\n")) + + (unless (re-search-backward (rx "use " (literal class) ";") nil t) + (let ((start (point))) + (insert "use " class ";\n") + (pulse-momentary-highlight-region start (point))))))) + + (defun oni-php--syntax-in-string-p (syntax) + "Does SYNTAX indicate point is inside a string?" + (nth 3 syntax)) + + (defun oni-php--syntax-in-comment-p (syntax) + "Does SYNTAX indicate point is inside a comment?" + (nth 4 syntax)) + + (defun oni-php--in-string-or-comment-p () + "Return whether or not point is within a string or comment." + (let ((syntax (syntax-ppss))) + (or (oni-php--syntax-in-string-p syntax) + (oni-php--syntax-in-comment-p syntax)))) + + (defun oni-php-insert-dot-dwim (N) + "Insert either a concatenation or access operator depending on context. + + Do the insert N times." + (interactive "p") + (if (or (oni-php--in-string-or-comment-p) + (save-excursion + (skip-syntax-backward " ") + (nth 3 (syntax-ppss (1- (point))))) + (save-excursion + (skip-syntax-forward " ") + (nth 3 (syntax-ppss (1+ (point)))))) + (self-insert-command N) + (let ((op (if (looking-back (rx (or "$" ")") (zero-or-more (any whitespace "\n" alnum "(" ")" "->"))) + (save-excursion (backward-up-list) (1+ (point)))) + "->" + "::"))) + (dotimes (_ N) (insert op))))) + + (defun oni-php-doc-comment () + "Insert a PHP documentation comment at point." + (interactive) + (let ((start (point))) + (insert "/**\n * ") + (let ((insert-marker (point-marker))) + (insert "\n */") + (indent-region start (point)) + (goto-char insert-marker)))) + + (defun oni-php-doc-use-comment () + "Insert a PHP documentation comment for a use statement." + (interactive) + (let ((start (point)) + (class (save-excursion + (save-match-data + (search-forward-regexp (rx "use " (group (minimal-match (zero-or-more alnum))) ";")) + (match-string 1))))) + (insert "/**\n * @use " class) + (let ((insert-marker (point-marker))) + (insert "\n */") + (indent-region start (point)) + (goto-char insert-marker)))) + + (defun oni-php-return-belongs-to-comment () + "Insert a PHP documentation comment for a BelongsTo return type." + (interactive) + (let ((start (point))) + (insert "/**\n * @return BelongsTo<") + (save-excursion + (insert ", $this>\n */") + (indent-region start (point))))) + + (defun oni-php-comment-dwim (func &rest args) + "See if a PHP documentation comment should be added and add it. + Otherwise call FUNC with ARGS. This is meant as advice around + ‘comment-dwim’ to make it smarter for PHP code." + (cond ((looking-at (rx (minimal-match (zero-or-more (any whitespace "\n"))) + "use ")) + (oni-php-doc-use-comment)) + ((looking-at (rx (minimal-match (zero-or-more (any whitespace "\n"))) + (one-or-more nonl) ": BelongsTo" eol)) + (oni-php-return-belongs-to-comment)) + ((and (derived-mode-p 'php-mode) + (not (region-active-p)) + (looking-back (rx (minimal-match (zero-or-more blank))) (line-beginning-position)) + (looking-at (rx (minimal-match (zero-or-more (any whitespace "\n"))) + (or (regexp php-beginning-of-defun-regexp) + (regexp php--re-classlike-pattern))))) + (oni-php-doc-comment)) + (t (apply func args)))) + + (defun oni-php-generate-namespace () + (string-join + (mapcar + (lambda (s) + (let ((case-fold-search nil)) + (if (string-match-p (rx upper-case) s) + s + (capitalize s)))) + (cdr (split-string + (directory-file-name + (file-name-directory + (file-relative-name + buffer-file-name + (project-root (project-current))))) + "/"))) + "\\")) + + (defun oni-php-set-dabbrev-settings () + "Set any settings for dabbrev that make sense for PHP code." + (setq-local dabbrev-abbrev-skip-leading-regexp (rx "$"))) + + (defun oni-php-ignore-error-at-point (error-type) + (interactive + (list (let* ((error-types + (seq-uniq + (mapcar (lambda (e) + (let ((str (flycheck-error-message e))) + (string-match (rx "🪪" (one-or-more whitespace) (group (one-or-more (any alnum ".")))) str) + (match-string 1 str))) + (seq-filter (lambda (e) (not (flycheck-error-checker e))) + (flycheck-overlay-errors-at (point)))) + 'string=))) + (if (> 1 (length error-types)) + (completing-read "Ignore type: " error-types) + (car error-types))))) + (save-excursion + (goto-char (line-beginning-position)) + (indent-for-tab-command) + (insert "/** @phpstan-ignore " error-type " */\n") + (indent-for-tab-command))) + + (defconst +oni-php-import-display+ "…" + "What to display instead of imports.") + + (defun oni-php-toggle-import-display () + (interactive) + (let* ((overlay (seq-find (lambda (o) (overlay-get o 'oni-php-hidden-imports)) + (overlays-at (point)))) + (visiblep (not (string= +oni-php-import-display+ + (overlay-get overlay 'display))))) + (overlay-put overlay 'display (if visiblep +oni-php-import-display+ nil)))) + + (defvar oni-php-hidden-imports-keymap + (let ((keymap (make-sparse-keymap))) + (define-key keymap (kbd "TAB") 'oni-php-toggle-import-display) + keymap)) + + (defun oni-php-hide-imports () + "Hide the imports at the start of the file." + (save-excursion + (let (start end) + (goto-char (point-min)) + (when (re-search-forward (rx bol "use ") nil t) + (setq start (point)) + (while (re-search-forward (rx bol "use ") nil t)) + (setq end (line-end-position)) + (dolist (o (overlays-in start end)) + (when (overlay-get o 'oni-php-hidden-imports) + (delete-overlay o))) + (let ((overlay (make-overlay start end nil t t))) + (overlay-put overlay 'oni-php-hidden-imports t) + (overlay-put overlay 'display +oni-php-import-display+) + (overlay-put overlay 'keymap oni-php-hidden-imports-keymap)))))) + + (defhydra php-mode-hydra (:color blue) + ("a" align-current "Align current selection")) + + (advice-add 'comment-dwim :around #'oni-php-comment-dwim) + + (add-hook 'php-mode-hook #'oni-php--set-require-final-newline) + (add-hook 'php-mode-hook #'oni-php--whitespace-mode) + (add-hook 'php-mode-hook 'corfu-mode) + (add-hook 'php-mode-hook 'display-fill-column-indicator-mode) + (add-hook 'php-mode-hook 'electric-indent-local-mode) + (add-hook 'php-mode-hook 'fic-mode) + (add-hook 'php-mode-hook 'flycheck-mode) + (add-hook 'php-mode-hook 'oni-php--auto-fill-mode) + (add-hook 'php-mode-hook 'oni-php-hide-imports) + (add-hook 'php-mode-hook 'oni-php-set-dabbrev-settings) + (add-hook 'php-mode-hook 'oni-php-set-rainbow-identifier-faces) + (add-hook 'php-mode-hook 'rainbow-delimiters-mode) + (add-hook 'php-mode-hook 'rainbow-identifiers-mode) + (add-hook 'php-mode-hook 'smartparens-mode) + (add-hook 'php-mode-hook 'subword-mode) + (add-hook 'php-mode-hook 'yas-minor-mode) + + (define-key php-mode-map (kbd "C-c m") #'php-mode-hydra/body) + (define-key php-mode-map (kbd ".") #'oni-php-insert-dot-dwim) + + ;; In PHP code it's nice to have any ~=>~ aligned. + + ;; <?php + ;; array( + ;; 'foo' => 'bar', + ;; 'frob' => 'baz' + ;; ); + ;; ?> + + (add-to-list 'align-rules-list + `(php-array-arrow + (regexp . ,(rx any (group (zero-or-more whitespace)) "=>" any)) + (group . (1)) + (modes . '(php-mode web-mode php-mode)) + (repeat . t))) + + ;; The WordPress coding standards specify that multiple assignments + ;; should have their assignment operators aligned. + + ;; <?php + ;; $variable = "value"; + ;; $other_variable = "other value"; + ;; $one_more_variable = "one more variable"; + + (add-to-list 'align-rules-list + `(php-assignment-equals + (regexp . ,(rx any (group (zero-or-more whitespace)) "=" + (zero-or-more whitespace) any)) + (group . (1)) + (modes . '(php-mode web-mode php-ts-mode)) + (repeat . t))) + + ;;;###autoload + (add-to-list 'auto-mode-alist '("\\.inc\\'" . php-mode)) + + ;;;###autoload + (add-to-list 'auto-mode-alist '("\\.module\\'" . php-mode)) + + ;;;###autoload + (with-eval-after-load 'grep + (add-to-list 'grep-files-aliases '("php" . "*.php *.inc *.module"))) + + (with-eval-after-load 'autoinsert + (setf (map-elt auto-insert-alist (rx ".php" eos)) + '(nil + "<?php\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace " (oni-php-generate-namespace) ";\n" + "\n" + "class " (file-name-sans-extension (file-name-nondirectory (buffer-file-name))) "\n" + "{\n" + > _ "\n" + "}\n") + (map-elt auto-insert-alist (rx "Test.php" eos)) + '(nil + "<?php\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace " (oni-php-generate-namespace) ";\n" + "\n" + "use Illuminate\\Foundation\\Testing\\RefreshDatabase;\n" + "use Illuminate\\Foundation\\Testing\\WithFaker;\n" + "use Tests\\TestCase;\n" + "\n" + "class " (file-name-sans-extension (file-name-nondirectory (buffer-file-name))) " extends TestCase\n" + "{\n" + > "use RefreshDatabase;\n" + > "use WithFaker;\n" + "\n" + > "/** @test */\n" + > "public function " (replace-regexp-in-string (rx whitespace) "_" (downcase (skeleton-read "Test function name: "))) "(): void\n" + "{" > "\n" + > _ "\n" + "}" > "\n" + "}"))) + + (defun oni-php-in-expression-context-p () + (not (oni-php-in-static-call-context-p))) + + (defun oni-php-in-test-file () + (string-suffix-p "Test.php" buffer-file-name)) + + (defun oni-php-in-static-call-context-p () + (looking-back "::\\w+" (- (point) (line-beginning-position)))) + + (defun oni-php-grep-symbol (symbol) + "Use ‘rgrep’ to find SYMBOL. + If there is an active region when this command is called interactively + SYMBOL will be the text in the region. Otherwise it's the symbol at + point." + (interactive (list + (if (region-active-p) + (buffer-substring-no-properties (region-beginning) (region-end)) + (thing-at-point 'symbol)))) + (rgrep (rx (literal symbol)) + (map-elt grep-files-aliases "php") + (project-root (project-current)))) + + (define-key php-mode-map (kbd "C-c .") nil t) + (define-key php-mode-map (kbd "C-c . g") '("Search for symbol at point" . oni-php-grep-symbol)) + (define-key php-mode-map (kbd "C-c . a") '("Import symbol at point" . oni-php-add-use)) + (define-key php-mode-map (kbd "C-c . i") '("Ignore error at point" . oni-php-ignore-error-at-point))) #+end_src * Magit @@ -931,6 +1281,39 @@ Now the pull app. 'pull-app)) #+end_src +social api: + +#+begin_src emacs-lisp + (use-package files + :ensure nil + :config + (dir-locals-set-class-variables + 'social-api + '((nil . ((grep-find-ignored-directories . (".git" "vendor" "cdk.out" "storage" "punt/tmp" "chanced/tmp")) + (eglot-workspace-configuration + . (:language_server_phpstan (:enabled t + :mem_limit "2G" + :bin "chanced/vendor/bin/phpstan"))))) + (php-mode . ((fill-column . 80) + (eglot-server-programs . (((php-mode phps-mode php-ts-mode) + "localhost" 4564))) + (php-project-root . git) + (phpstan-executable . (root . "../chanced-scripts/phpstan")) + (phpstan-config-file . "/app/chanced/phpstan.neon") + (phpstan-memory-limit . "4G") + (phpstan-level . 9) + (eval . (setq-local flycheck-phpstan-executable (expand-file-name "../chanced-scripts/phpstan" (project-root (project-current))))) + (php-ts-mode-php-executable . "docker exec -it chanced-backend php artisan tinker"))) + (typescript-mode . ((typescript-indent-level . 2))) + ("punt" + . ((php-mode . ((phpstan-config-file . "/app/punt/phpstan.neon"))))) + ("filament" + . ((php-mode . ((phpstan-config-file . "/app/filament/phpstan.neon"))))))) + (dir-locals-set-directory-class + (expand-file-name "~/code/diamond-interactive/social-api") + 'social-api)) +#+end_src + * Eglot Set up a server program for Eglot for org-mode. @@ -1007,7 +1390,7 @@ The only way to edit lispy languages. This turns Emacs into a structural editor #+begin_src emacs-lisp (use-package paredit - :hook (emacs-lisp-mode fennel-mode clojure-mode lisp-mode)) + :hook (lisp-data-mode fennel-mode clojure-mode)) #+end_src * Yasnippet @@ -1027,6 +1410,7 @@ Snippets! (define-key yas-minor-mode-map (kbd "SPC") yas-maybe-expand) (setq-default yas-buffer-local-condition yas-not-string-or-comment-condition)) +#+end_src * Help @@ -1040,3 +1424,35 @@ That got me looking at [[elisp:(customize-group "help")]] and discover [[help:he (help-window-select t) (help-enable-variable-value-editing t)) #+end_src + +* Denote + +I need to try taking notes with Denote. + +#+begin_src emacs-lisp + (use-package denote + :hook (dired-mode . denote-dired-mode) + :bind + (("C-c n n" . denote) + ("C-c n r" . denote-rename-file) + ("C-c n l" . denote-link) + ("C-c n b" . denote-backlinks) + ("C-c n d" . denote-dired) + ("C-c n g" . denote-grep)) + :config + (setq denote-directory (expand-file-name "~/Documents/denote-notes/")) + (denote-rename-buffer-mode 1)) +#+end_src + +* Dabbrev + +Dynamic abbreviations are one of the completion mechanisms that Emacs supports. I mainly use this with corfu and cape-dabbrev, but when I'm looking for completions I want to see any variant with different letter casings, and also maintain the letter casing of which option I pick. + +#+begin_src emacs-lisp + (use-package dabbrev + :ensure nil + :config + (setq dabbrev-case-replace nil) + (setq dabbrev-case-distinction nil) + (setq dabbrev-case-fold-search t)) +#+end_src diff --git a/emacs/.emacs.d/scripts/find-php-class b/emacs/.emacs.d/scripts/find-php-class index 4268dcf..3564f00 100755 --- a/emacs/.emacs.d/scripts/find-php-class +++ b/emacs/.emacs.d/scripts/find-php-class @@ -1,48 +1,32 @@ -#!/usr/bin/env bash -# ;; #!/usr/bin/env -S scsh -e main -o srfi-1 -s -# ;; !# +#!/usr/bin/env -S scsh -e main -o srfi-1 -s +;; -*- mode: scheme; -*- +!# -# ;; (define (main args) -# ;; (let* ((class-name (cadr args)) -# ;; (file-names -# ;; (run/strings (find "." -# ;; -path "./cdk.out" -prune -o -# ;; -type f -# ;; -name ,(string-append class-name ".php") -# ;; -exec grep "^namespace" "{}" ";") -# ;; (> 2 "/dev/null")))) -# ;; (format #t "~s" -# ;; (delete-duplicates -# ;; (map (lambda (n) -# ;; (string-append -# ;; (match:substring -# ;; (regexp-search (rx "namespace " (submatch (+ any)) ";") n) -# ;; 1) -# ;; "\\" -# ;; class-name)) -# ;; file-names))))) +(define (find-class class-name) + (run/strings (find "." "(" + -path "./cdk.out" + -o -path "./*/tmp" + -o -path "./*/node_modules" + -o -path "./*/.phpunit.cache" + -o -path "./*/storage" + -o -path "./.git" + -o -path "./data" + ")" -prune + -o -type f -name ,(string-append class-name ".php") + -exec grep "^namespace" "{}" ";") + (> 2 "/dev/null"))) -main() { - local classname=$1 - find . \( \ - -path "./cdk.out" \ - -o -path "./punt/tmp" \ - -o -path "./punt/vendor" \ - -o -path "./filament/storage/larastan/cache" \ - -o -path "./filament/vendor" \ - -o -path "./chanced/tmp" \ - -o -path "./chanced/node_modules" \ - -o -path "./chanced/.phpunit.cache" \ - -o -path "./.git" \ - -o -path "./chanced/storage" \ - \) -prune \ - -o -type f -name "${classname}.php" \ - -exec grep '^namespace' '{}' \; \ - | awk "{ print substr(\$0, 11, length(\$0) - 11) \"\\\\${classname}\" }" - # for f in "$(git ls-files | grep "${classname}.php")"; do - # grep '^namespace' "$f" | awk '{ print substr($0, 11, length($0) - 11) }' - # done -} - -main "$@" +(define (main args) + (let* ((class-name (cadr args)) + (file-names (find-class class-name))) + (format #t "~s" + (delete-duplicates + (map (lambda (n) + (string-append + (match:substring + (regexp-search (rx "namespace " (submatch (+ any)) ";") n) + 1) + "\\" + class-name)) + file-names))))) |
