blob: aebca59a43da6c57f12f8dd102409244780d6548 (
plain) (
tree)
|
|
;;; hcel-outline.el --- shows package-module-identifier hierarchy in an outline mode -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Free Software Foundation, Inc.
;;
;; This file is part of hcel.
;;
;; hcel is free software: you can redistribute it and/or modify it under
;; the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; hcel 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 Affero General
;; Public License for more details.
;;
;; You should have received a copy of the GNU Affero General Public
;; License along with hcel. If not, see <https://www.gnu.org/licenses/>.
(require 'hcel-utils)
(require 'hcel-source)
(require 'outline)
(require 'text-property-search)
(defvar hcel-outline-buffer-name "*hcel-outline*")
(defvar hcel-outline-indentation 2)
(defvar hcel-outline-mode-map
(let ((kmap (make-sparse-keymap)))
(define-key kmap "n" #'next-logical-line)
(define-key kmap "p" #'previous-logical-line)
(define-key kmap "f" #'outline-forward-same-level)
(define-key kmap "F" #'hcel-outline-follow-mode)
(define-key kmap "b" #'outline-backward-same-level)
(define-key kmap "u" #'outline-up-heading)
(define-key kmap "\t" #'hcel-outline-toggle-children)
(define-key kmap "\r" #'hcel-outline-open-thing-at-point)
(define-key kmap "o" #'hcel-outline-open-thing-at-point-other-window)
(define-key kmap "q" #'quit-window)
kmap))
(define-derived-mode hcel-outline-mode outline-mode "hcel-outline"
"Major mode for browsing Haskell codebases."
(setq-local outline-regexp "\\( *\\)."
outline-level (lambda () (1+ (/ (length (match-string 1))
hcel-outline-indentation)))
buffer-read-only t))
(defun hcel ()
(interactive)
(let ((buffer (get-buffer hcel-outline-buffer-name)))
(if buffer (switch-to-buffer buffer)
(switch-to-buffer (get-buffer-create hcel-outline-buffer-name))
(save-excursion
(mapc
(lambda (package-id)
(insert
(concat (propertize
(hcel-format-package-id package-id)
'thing 'package
'package-id package-id
'children-loaded nil)
"\n")))
(hcel-api-packages)))
(hcel-outline-mode))))
(define-key hcel-mode-map "o" #'hcel)
(defun hcel-outline-package-module ()
(interactive)
(unless (derived-mode-p 'hcel-mode)
(error "Not in hcel mode!"))
(let ((package-id hcel-package-id)
(module-path hcel-module-path))
(hcel)
(hcel-outline-goto-package package-id)
(hcel-outline-load-modules-at-point)
(hcel-outline-goto-module module-path)
(hcel-outline-load-identifiers-at-point)))
(define-key hcel-mode-map "O" #'hcel-outline-package-module)
;; TODO: maybe remove
(defun hcel-outline-update-opened (package-id module-path)
"Update the outline tree depending on openness of packages and modules.
If a package is opened outside of the outline mode (mainly source mode), then
update in the outline mode too."
(with-current-buffer hcel-outline-buffer-name
(save-excursion
(hcel-outline-goto-package package-id)
(hcel-outline-load-modules-at-point)
(hcel-outline-goto-module module-path)
(hcel-outline-load-identifiers-at-point))))
(defun hcel-outline-goto-package (package-id)
(goto-char (point-min))
(re-search-forward
(format "^%s$" (hcel-format-package-id package-id)))
(beginning-of-line))
(defun hcel-outline-goto-module (module-path)
"Goto module, assuming point is on the package."
(re-search-forward
(format "^%s%s$" (make-string hcel-outline-indentation 32) module-path))
(beginning-of-line))
(defun hcel-outline-goto-identifier (position)
"Goto identifier declared at POSITION, assuming point is on the module."
(text-property-search-forward 'position position))
(defun hcel-outline-load-modules-at-point ()
(interactive)
(unless (eq (get-text-property (point) 'thing) 'package)
(error "Point is not at a package!"))
(unless (get-text-property (point) 'children-loaded)
(save-excursion
(let ((inhibit-read-only t)
(package-id (get-text-property (point) 'package-id)))
(put-text-property
(progn (beginning-of-line) (point))
(progn (end-of-line) (point))
'children-loaded t)
(beginning-of-line 2)
(mapc
(lambda (module)
(insert
(propertize (concat
(make-string hcel-outline-indentation 32) module "\n")
'thing 'module
'package-id package-id
'module-path module
'folded t)))
(hcel-list-modules package-id))))))
(defun hcel-outline-toggle-children ()
(interactive)
(let ((thing (get-text-property (point) 'thing))
(children-loaded (get-text-property (point) 'children-loaded)))
(cond (children-loaded (outline-toggle-children))
((eq thing 'package) (hcel-outline-load-modules-at-point))
((eq thing 'module) (hcel-outline-load-identifiers-at-point))
(t nil))))
(defun hcel-outline-toggle-exported ()
(interactive)
(if (memq 'hcel-unexported buffer-invisibility-spec)
(remove-from-invisibility-spec 'hcel-unexported)
(add-to-invisibility-spec 'hcel-unexported)))
(defun hcel-outline-load-identifiers-at-point ()
(interactive)
(unless (eq (get-text-property (point) 'thing) 'module)
(error "Point is not at a module!"))
(unless (get-text-property (point) 'children-loaded)
(save-excursion
(let* ((inhibit-read-only t)
(package-id (get-text-property (point) 'package-id))
(module-path (get-text-property (point) 'module-path))
(imenu-index))
(put-text-property
(progn (beginning-of-line) (point))
(progn (end-of-line) (point))
'children-loaded t)
(when (or (get-buffer (hcel-buffer-name package-id module-path))
(y-or-n-p "Open module source?"))
(with-current-buffer
(hcel-load-module-source package-id module-path)
(setq imenu-index
(save-excursion
(hcel-imenu-create-index))))
(beginning-of-line 2)
(mapc
(lambda (pair)
(insert
(propertize
(concat (make-string (* 2 hcel-outline-indentation) 32)
(car pair)
"\n")
'thing 'identifier
'package-id package-id
'module-path module-path
'position (cdr pair)
'invisible (unless
(get-text-property 0 'exported (car pair))
'hcel-unexported))))
imenu-index))))))
(defun hcel-outline-open-module-source-at-point (&optional other-window)
(interactive)
(let ((props (text-properties-at (point))))
(unless (eq (plist-get props 'thing) 'module)
(error "Point is not at a module!"))
(let ((buffer
(hcel-load-module-source
(plist-get props 'package-id)
(plist-get props 'module-path))))
(hcel-outline-load-identifiers-at-point)
(if other-window
(switch-to-buffer-other-window buffer)
(switch-to-buffer buffer)))))
(defun hcel-outline-open-identifier-at-point (&optional other-window)
(interactive)
(let ((props (text-properties-at (point))))
(unless (eq (plist-get props 'thing) 'identifier)
(error "Point is not at an identifier!"))
(let ((buffer
(hcel-load-module-source
(plist-get props 'package-id)
(plist-get props 'module-path))))
(if other-window
(switch-to-buffer-other-window buffer)
(switch-to-buffer buffer))
(goto-char (plist-get props 'position)))))
(defun hcel-outline-open-thing-at-point (&optional other-window)
(interactive)
(cond ((eq (get-text-property (point) 'thing) 'module)
(hcel-outline-open-module-source-at-point other-window))
((eq (get-text-property (point) 'thing) 'identifier)
(hcel-outline-open-identifier-at-point other-window))
(t nil)))
(defun hcel-outline-open-thing-at-point-other-window ()
(interactive)
(let ((current-window (car (window-list))))
(hcel-outline-open-thing-at-point t)
(select-window current-window)))
(define-minor-mode hcel-outline-follow-mode
"Display modules and identifiers as point moves."
:lighter " hcel-outline-follow"
:after-hook
(if hcel-outline-follow-mode
(if (not (derived-mode-p 'hcel-outline-mode))
(error "Not in hcel-outline mode!")
(add-hook 'post-command-hook
#'hcel-outline-open-thing-at-point-other-window nil t))
(remove-hook 'post-command-hook
#'hcel-outline-open-thing-at-point-other-window t)))
(provide 'hcel-outline)
;;; hcel-outline.el ends here.
|