You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

205 lines
7.8 KiB

;;; alchemist-scope.el --- Provides information about Elixir source code context -*- lexical-binding: t -*-
;; Copyright © 2015 Samuel Tonini
;; Author: Samuel Tonini <tonini.samuel@gmail.com
;; This file is not part of GNU Emacs.
;; 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 <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Provides information about the Elixir source code context.
;;; Code:
(require 'dash)
(defgroup alchemist-scope nil
"Provides information about the Elixir source code context."
:prefix "alchemist-scope-"
:group 'alchemist)
(defconst alchemist-scope-defmodule-regex "defmodule \\([A-Za-z\._]+\\)\s+"
"The regex for matching Elixir defmodule macro.")
(defconst alchemist-scope-alias-regex
"^\s+alias\s+\\([-:_A-Za-z0-9,\.\?!\]+\\)\\(\s*,\s*as:\s*\\)?\\([-_A-Za-z0-9,\.\?!\]+\\)?\n"
"The regex for matching Elixir alias definitions.
Example:
alias Phoenix.Router.Resource, as: Special")
(defconst alchemist-scope-alias-regex-two
"^\s+alias\s+\\([-:_A-Za-z0-9,\.\?!\]+\\)\.{\\([-:_A-Za-z0-9\s,\.\?!\]+\\)}\n"
"The regex for matching Elixir alias definitions.
Example:
alias List.Chars.{Atom, Float}")
(defconst alchemist-scope-use-regex
"^\s+use\s+\\([A-Za-z0-9\.]+\\)"
"The regex for matching Elixir use definitions.")
(defconst alchemist-scope-import-regex
"^\s+import\s+\\([A-Za-z0-9\.]+\\)"
"The regex for matching Elixir import definitions.")
(defun alchemist-scope-inside-string-p ()
"Return non-nil if `point' is inside a string or heredoc."
(let* ((pos (point))
(parse-info (syntax-ppss pos)))
(or (and (nth 3 parse-info)
(nth 8 parse-info))
(and (looking-at "\"\"\"\\|'''\\|\"\\|\'")
(match-beginning 0)))))
(defun alchemist-scope-inside-module-p ()
"Return non-nil if `point' is currently inside a module."
(save-excursion
(end-of-line)
(let ((found-flag-p nil)
(found-p nil))
(while (and (not found-flag-p)
(re-search-backward alchemist-scope-defmodule-regex nil t))
(when (not (alchemist-scope-inside-string-p))
(setq found-flag-p t)
(setq found-p t)))
found-p)))
(defun alchemist-scope-module ()
"Return name from the current defmodule."
(save-excursion
(let ((found-flag-p nil)
(module-name ""))
(save-match-data
(while (and (not found-flag-p)
(re-search-backward alchemist-scope-defmodule-regex nil t))
(when (not (alchemist-scope-inside-string-p))
(setq module-name (match-string 1))
(setq found-flag-p t))
(when (equal 1 (line-number-at-pos (point)))
(setq found-flag-p t)))
module-name))))
(defun alchemist-scope-aliases ()
"Return aliases from the current module."
(let* ((aliases '())
(context (alchemist-scope-module)))
(save-excursion
(when (alchemist-scope-inside-module-p)
(end-of-line)
;; alias definition like:
;;
;; alias Phoenix.Router.Resource, as: Special
(while (re-search-backward alchemist-scope-alias-regex nil t)
(when (and
(not (alchemist-scope-inside-string-p))
(equal context (alchemist-scope-module)))
(let* ((alias (match-string 1))
(as (if (match-string 3) (match-string 3) nil))
(as (if as as (car (last (split-string alias "\\."))))))
(setq aliases (append aliases (list (list (alchemist-utils-remove-dot-at-the-end alias)
(alchemist-utils-remove-dot-at-the-end as))))))))
;; alias definition like:
;;
;; alias List.Chars.{Atom, Float}
(while (re-search-backward alchemist-scope-alias-regex-two nil t)
(when (and
(not (alchemist-scope-inside-string-p))
(equal context (alchemist-scope-module)))
(let* ((prefix (match-string 1))
(alias-collection (if (match-string 2) (split-string (match-string 2) ",") nil)))
(-map (lambda (alias)
(let* ((alias (replace-regexp-in-string "\s+" "" alias))
(namespace (format "%s.%s" prefix alias)))
(setq aliases (append aliases (list (list (alchemist-utils-remove-dot-at-the-end namespace)
(alchemist-utils-remove-dot-at-the-end alias)))))))
alias-collection))))))
aliases))
(defun alchemist-scope--modules (regex)
(let ((modules '())
(context (alchemist-scope-module)))
(save-excursion
(when (not (alchemist-utils-empty-string-p context))
(while (re-search-backward regex nil t)
(when (and (match-string 1)
(not (alchemist-scope-inside-string-p))
(equal context (alchemist-scope-module)))
(cl-pushnew (substring-no-properties (match-string 1)) modules))))
modules)))
(defun alchemist-scope-use-modules ()
"Return `use' introduced module names from the current module."
(alchemist-scope--modules alchemist-scope-use-regex))
(defun alchemist-scope-import-modules ()
"Return `import' introduced module names from the current module."
(alchemist-scope--modules alchemist-scope-import-regex))
(defun alchemist-scope-all-modules ()
"Return `use' and `import' introduced modules from the current module."
(let ((current (alchemist-scope-module))
(use (alchemist-scope-use-modules))
(import (alchemist-scope-import-modules))
(modules '()))
(push current modules)
(push use modules)
(push import modules)
(-flatten modules)))
(defun alchemist-scope-extract-module (expr)
"Extract module from EXPR."
(let* ((parts (split-string expr "\\."))
(function (car (last parts)))
(case-fold-search nil))
(when (string-match-p "^[a-z_\?!]+" function)
(delete function parts))
(unless (string-match-p "^[a-z_\?!]+" (car parts))
(alchemist-utils-remove-dot-at-the-end (mapconcat 'concat parts ".")))))
(defun alchemist-scope-extract-function (expr)
"Extract function from EXPR."
(let* ((parts (split-string expr "\\."))
(function (car (last parts)))
(case-fold-search nil))
(when (and function
(string-match-p "^[a-z_\?!]+" function))
function)))
(defun alchemist-scope-alias-full-path (module)
"Solve the full path for the MODULE alias."
(if (not (alchemist-utils-empty-string-p module))
(let* ((aliases (-map (lambda (m)
(when (string-match-p (format "^%s" (car (cdr m))) module)
(replace-regexp-in-string (format "^%s" (car (cdr m))) (car m) module t)))
(alchemist-scope-aliases)))
(aliases (delete nil aliases)))
(if aliases
(car aliases)
module))))
(defun alchemist-scope-expression ()
"Return the expression under the cursor."
(let (p1 p2)
(save-excursion
(skip-chars-backward "-_A-Za-z0-9.?!:@")
(setq p1 (point))
(skip-chars-forward "-_A-Za-z0-9.?!:@")
(setq p2 (point))
(buffer-substring-no-properties p1 p2))))
(provide 'alchemist-scope)
;;; alchemist-scope.el ends here