From b0e43984d0820a9044e5cb6c003521b6a9f27dd5 Mon Sep 17 00:00:00 2001 From: Jimmy Yuen Ho Wong Date: Tue, 12 Jun 2018 14:43:02 +0100 Subject: [PATCH] Introduce `pyenv-local-mode` to set up keybinding and modeline. New global `pyenv-mode` will use projectile to set up PYENV_VERSION automatically. --- README.rst | 62 +++++++++++++++++++++++++++++-------------------- pyenv-mode.el | 64 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 96 insertions(+), 30 deletions(-) diff --git a/README.rst b/README.rst index 046c87d..3e4fad8 100644 --- a/README.rst +++ b/README.rst @@ -12,11 +12,14 @@ Integrate Fabián E. Gallina `python.el`_ with pyenv_ tool. This allow packages which already use python.el (like python-django_) got pyenv virtual environments support out-of-the-box. +Default automatic integration with `projectile`_ is available since 0.2.0. + Pyenv mode does... ~~~~~~~~~~~~~~~~~~ * Setup ``PYENV_VERSION`` environment variable and - ``python-shell-virtualenv-path`` custom variable based on user input + ``python-shell-virtualenv-path`` custom variable based on user input or + switching to a projectile project. Pyenv mode doesn't... ~~~~~~~~~~~~~~~~~~~~~ @@ -36,12 +39,30 @@ You can simply install package from Melpa_:: Usage ----- -Add following block to your emacs configuration - .. code:: lisp (pyenv-mode) +This is enough to turn on ``pyenv-mode`` 's for all ``python-mode`` buffers. If +you are using Projectile, switching to a Python project will setup its Pyenv +Python version for you using either the project's ``.python-version`` file or +the project's name. + +If you are not using Projectile, you will have to activate the appropriate +Python version like the following while visiting a ``python`` mode buffer:: + + M-x pyenv-mode-set + +Advanced Usage +-------------- + +If you don't want to use the global ``pyenv-mode`` for whatever reason, you can +still activate ``pyenv-mode``, but you have to use the local mode instead:: + +.. code:: lisp + + (add-hook 'python-mode-hook 'pyenv-local-mode) + Now you are available to specify pyenv python version:: M-x pyenv-mode-set @@ -55,6 +76,17 @@ unset current version with:: M-x pyenv-mode-unset +FAQ +--- + +**Q:** My project has multiple ``.python-version`` files in different +subdirectories, when I switch to a different subproject, ``pyenv-mode`` didn't +switch to a different Python version for me, how come? +**A:** By default Projectile only recognizes a couple types of projects by looking +at specific version control system directories or common configuration files. +You can either put an empty ``.projectile`` file in the directory or `register a +new project type`_. + Goodies ------- @@ -65,31 +97,11 @@ happens automatically * flycheck_ perform syntax checking according to python version you use * anaconda-mode_ search completions, definitions and references in chosen environment -Projectile integration -`````````````````````` - -You can switch python version together with current project. Drop -following lines into emacs init file. When use projectile switch -project with ``C-c p p`` key binding ``pyenv-mode`` will activate -environment matched project name. - -.. code:: lisp - - (require 'pyenv-mode) - - (defun projectile-pyenv-mode-set () - "Set pyenv version matching project name." - (let ((project (projectile-project-name))) - (if (member project (pyenv-mode-versions)) - (pyenv-mode-set project) - (pyenv-mode-unset)))) - - (add-hook 'projectile-after-switch-project-hook 'projectile-pyenv-mode-set) - -.. _python.el: http://repo.or.cz/w/emacs.git/blob_plain/master:/lisp/progmodes/python.el +. _python.el: http://repo.or.cz/w/emacs.git/blob_plain/master:/lisp/progmodes/python.el .. _pyenv: https://github.com/yyuu/pyenv .. _python-django: https://github.com/fgallina/python-django.el .. _Melpa: https://melpa.org .. _flycheck: https://github.com/flycheck/flycheck .. _anaconda-mode: https://github.com/proofit404/anaconda-mode .. _projectile: https://github.com/bbatsov/projectile +.. _register a new project type: http://projectile.readthedocs.io/en/latest/configuration/#adding-custom-project-types diff --git a/pyenv-mode.el b/pyenv-mode.el index 58b1ff7..701eda2 100644 --- a/pyenv-mode.el +++ b/pyenv-mode.el @@ -4,8 +4,8 @@ ;; Author: Artem Malyshev ;; URL: https://github.com/proofit404/pyenv-mode -;; Version: 0.1.0 -;; Package-Requires: ((pythonic "0.1.0")) +;; Version: 0.2.0 +;; Package-Requires: ((pythonic "0.1.0") (f "0.14")) ;; 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 @@ -26,6 +26,7 @@ ;;; Code: +(require 'f) (require 'pythonic) (defgroup pyenv nil @@ -61,6 +62,43 @@ "Read virtual environment from user input." (completing-read "Pyenv: " (pyenv-mode-versions))) +(defun pyenv-python-version-file-exists-p (dir) + "Return t if a .python-version file found under DIR." + (f-exists? (f-expand ".python-version" dir))) + +(defun pyenv-detect-project-python-version () + "Returns the Python version found for a project. + +Looks at `projectile-project-root' first, if a .python-version +file is found, returns its content. + +Otherwise, `projectile-project-name' if it is an installed Python +version. + +nil if none of the above is true. +" + (let* ((project-root (ignore-errors (projectile-project-root))) + (version-file (and project-root + (f-join project-root ".python-version"))) + (version-file-version (and project-root + (f-exists? version-file) + (f-readable? version-file) + (string-trim + (f-read-text version-file 'utf-8)))) + (project-name (projectile-project-name))) + (cond ((member version-file-version (pyenv-mode-versions)) + version-file-version) + ((member project-name (pyenv-mode-versions)) + project-name) + (t nil)))) + +(defun pyenv-set-project-python-version () + "Set Python version when opening a project." + (let ((python-version (pyenv-detect-project-python-version))) + (if python-version + (pyenv-mode-set python-version) + (pyenv-mode-unset)))) + ;;;###autoload (defun pyenv-mode-set (version) "Set python shell VERSION." @@ -85,18 +123,34 @@ "Keymap for pyenv-mode.") ;;;###autoload -(define-minor-mode pyenv-mode +(define-minor-mode pyenv-local-mode "Minor mode for pyenv interaction. \\{pyenv-mode-map}" - :global t :lighter "" :keymap pyenv-mode-map - (if pyenv-mode + :group 'pyenv + (if pyenv-local-mode (add-to-list 'mode-line-misc-info pyenv-mode-mode-line-format) (setq mode-line-misc-info (delete pyenv-mode-mode-line-format mode-line-misc-info)))) +(defun pyenv-local-mode-turn-on () + "Turn on `pyenv-local-mode' if the buffer's major mode is`python-mode'." + (when (derived-mode-p 'python-mode) + (pyenv-local-mode))) + +;;;###autoload +(define-globalized-minor-mode pyenv-mode pyenv-local-mode + pyenv-local-mode-turn-on + :group 'pyenv + :after-hook + (if pyenv-mode + (with-eval-after-load 'projectile + (add-hook 'projectile-after-switch-project-hook #'pyenv-set-project-python-version)) + (with-eval-after-load 'projectile + (remove-hook 'projectile-after-switch-project-hook #'pyenv-set-project-python-version)))) + (provide 'pyenv-mode) ;;; pyenv-mode.el ends here