;; Start of oni-php (use-package php-mode :config (require 'align) (require 'hydra) (require 'map) (require 'project) (require 'whitespace) (require 'grep) (defconst oni-php-root (file-name-directory (or load-file-name (buffer-file-name))) "The directory where ‘oni-php’ was loaded from.") (defconst oni-php-snippets-dir (expand-file-name "snippets" oni-php-root) "The directory where ‘oni-php’ stores its snippets.") (defconst oni-php-scripts-dir (expand-file-name "scripts" oni-php-root) "The directory where ‘oni-php’ stores its 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 (string-split (string-trim-right (shell-command-to-string (concat (expand-file-name "find-php-class" oni-php-scripts-dir) " " class))) "\n")) (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-paragraph) (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))) (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 'display-fill-column-indicator-mode) (add-hook 'php-mode-hook 'electric-indent-local-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-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) (add-hook 'php-mode-hook 'corfu-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. ;; '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. ;; _ "\n" "}\n") (map-elt auto-insert-alist (rx "Test.php" eos)) '(nil " "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))) (use-package rainbow-identifiers) ;; End of oni-php