From 39d2763cb1259240d67909db4e6d1495939647bb Mon Sep 17 00:00:00 2001 From: Clay Harrison Date: Fri, 22 Sep 2023 14:28:00 +0200 Subject: [PATCH 01/17] naive implementation of autoactivate mode from conda.el --- micromamba.el | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/micromamba.el b/micromamba.el index 23334ec..125be38 100644 --- a/micromamba.el +++ b/micromamba.el @@ -48,11 +48,27 @@ "Micromamba (environment manager) integration for Emacs." :group 'python) +(defcustom micromamba-home (expand-file-name "~/.micromamba") ;; adapted from conda.el + "The directory where micromamba stores its files." + :type 'directory + :group 'micromamba) + (defcustom micromamba-executable (executable-find "micromamba") "Path to micromamba executable." :type 'string :group 'micromamba) +(defcustom micromamba-message-on-environment-switch t ;;adapted from conda.el + "Whether to message when switching environments. Default true." + :type 'boolean + :group 'micromamba) + +(defcustom micromamba-activate-base-by-default nil ;;adapted from conda.el + "Whether to activate the base environment by default if no other is preferred. +Default nil." + :type 'boolean + :group 'micromamba) + (defcustom micromamba-preactivate-hook nil "Hook run before a micromamba environment is activated." :type 'hook @@ -78,6 +94,17 @@ (defvar eshell-path-env) +;; internal variables that you probably shouldn't mess with + +(defvar micromamba-env-executables-dir ;; copied from virtualenv.el b/w/o conda.el + (if (eq system-type 'windows-nt) "Scripts" "bin") + "Name of the directory containing executables. It is system dependent.") + +(defvar micromamba-env-meta-dir "conda-meta" ;; copied from conda.el + "Name of the directory containing metadata. +This should be consistent across platforms.") + +;; internal utility functions (defun micromamba--call-json (&rest args) "Call micromamba and parse the return value as JSON. @@ -89,6 +116,17 @@ Pass ARGS as arguments to the program." (goto-char (point-min)) (json-read))) +(defvar micromamba--config nil + "Cached copy of configuration that Micromamba sees (including `condarc', etc). +Set for the lifetime of the process.") + +(defun micromamba--get-config() + "Return current Conda configuration. Cached for the lifetime of the process." + (if (not (eq micromamba--config nil)) + micromamba--config + (let ((cfg (micromamba--call-json "config" "list" "--json"))) + (setq micromamba--config cfg)))) + (defun micromamba-envs () "Get micromamba environments. @@ -157,6 +195,51 @@ Returns an alist with the following keys: (vars-export . ,vars-export) (scripts . ,scripts)))) +(defun micromamba--env-dir-is-valid (candidate) + "Confirm that CANDIDATE is a valid conda environment." + (let ((dir (file-name-as-directory candidate))) + (and (not (s-blank? candidate)) + (f-directory? dir) + (or (f-directory? (concat dir micromamba-env-executables-dir)) + (f-directory? (concat dir micromamba-env-meta-dir)))))) + +(defun micromamba--contains-env-yml? (candidate) ;; adapted from conda.el + "Does CANDIDATE contain an environment.yml?" + (f-exists? (f-expand "environment.yml" candidate))) + +(defun micromamba--find-env-yml (dir) ;; adapted from conda.el + "Find an environment.yml in DIR or its parent directories." + ;; TODO: implement an optimized finder with e.g. projectile? Or a series of + ;; finder functions, that stop at the project root when traversing + (let ((containing-path (f-traverse-upwards 'micromamba--contains-env-yml? dir))) + (if containing-path + (f-expand "environment.yml" containing-path) + nil))) + +(defun micromamba--get-name-from-env-yml (filename) ;; adapted from conda.el + "Pull the `name` property out of the YAML file at FILENAME." + ;; TODO: find a better way than slurping it in and using a regex... + (when filename + (let ((env-yml-contents (f-read-text filename))) + (if (string-match "name:[ ]*\\([A-z0-9-_.]+\\)[ ]*$" env-yml-contents) + (match-string 1 env-yml-contents) + nil)))) + +(defun micromamba--infer-env-from-buffer () ;; adapted from conda.el + "Search up the project tree for an `environment.yml` defining a conda env." + (let* ((filename (buffer-file-name)) + (working-dir (if filename + (f-dirname filename) + default-directory))) + (when working-dir + (or + (micromamba--get-name-from-env-yml (micromamba--find-env-yml working-dir)) + (if (or + micromamba-activate-base-by-default + (alist-get 'auto_activate_base (micromamba--get-config))) + "base" + nil))))) + (defun micromamba--get-activation-parameters (prefix) "Get activation parameters for the environment PREFIX. @@ -194,6 +277,31 @@ The parameters value is an alist as defined by (setenv (car var) (cdr var))) (setq eshell-path-env (getenv "PATH"))) +;; "public" functions + +(defun micromamba-env-default-location () + "Default location of the conda environments -- under the Anaconda installation." + (let ((candidates (alist-get 'envs_dirs (micromamba--get-config)))) + (f-full (aref candidates 0)))) + + +(defun micromamba-env-name-to-dir (name) + "Translate NAME to the directory where the environment is located." + (if (and (string= name "base") + (micromamba--env-dir-is-valid micromamba-home)) + (file-name-as-directory (expand-file-name micromamba-home)) + (let* ((default-location (file-name-as-directory (micromamba-env-default-location))) + (initial-possibilities (list name (concat default-location name))) + (possibilities (if (boundp 'venv-location) + (if (stringp venv-location) + (cons venv-location initial-possibilities) + (nconc venv-location initial-possibilities)) + initial-possibilities)) + (matches (-filter 'micromamba--env-dir-is-valid possibilities))) + (if (> (length matches) 0) + (file-name-as-directory (expand-file-name (car matches))) + (error "No such conda environment: %s" name))))) + ;;;###autoload (defun micromamba-activate (prefix) "Switch to environment with PREFIX (path). Prompt if called interactively. @@ -235,5 +343,52 @@ full paths." (setq micromamba-env-current-prefix nil) (run-hooks 'micromamba-postdeactivate-hook))) +;;;###autoload +(defun micromamba-env-activate-for-buffer () + "Activate the conda environment implied by the current buffer. + +This can be set by a buffer-local or project-local variable (e.g. a +`.dir-locals.el` that defines `conda-project-env-path`), or inferred from an +`environment.yml` or similar at the project level." + (interactive) + (let* ((inferred-env (micromamba--infer-env-from-buffer)) + (env-path (cond + ((bound-and-true-p conda-project-env-path) conda-project-env-path) + ((not (eql inferred-env nil)) (micromamba-env-name-to-dir inferred-env)) + (t nil)))) + + (if (not (eql env-path nil)) + (micromamba-activate env-path) + (if micromamba-message-on-environment-switch + (message "No Conda environment found for <%s>" (buffer-file-name)))))) + +(defun micromamba--switch-buffer-auto-activate (&rest args) + "Add Conda environment activation if a buffer has a file, handling ARGS." + (let ((filename (buffer-file-name))) + (when filename + ;; (message "switch-buffer auto-activating on <%s>" filename) + (with-demoted-errors "Error: %S" + (micromamba-env-activate-for-buffer))))) + +;;;###autoload +(define-minor-mode micromamba-env-autoactivate-mode + "Toggle conda-env-autoactivate mode. + +This mode automatically tries to activate a conda environment for the current +buffer." + ;; The initial value. + :init-value nil + ;; The indicator for the mode line. + :lighter nil + ;; The minor mode bindings. + :keymap nil + ;; Kwargs + :group 'micromamba + :global t + ;; Forms + (if micromamba-env-autoactivate-mode ;; already on, now switching off + (advice-add 'switch-to-buffer :after #'micromamba--switch-buffer-auto-activate) + (advice-remove 'switch-to-buffer #'micromamba--switch-buffer-auto-activate))) + (provide 'micromamba) ;;; micromamba.el ends here From 443736121ad87e3c77f513b0ec1fb7788788a26d Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 16:06:57 +0100 Subject: [PATCH 02/17] Replace (if ... nil) with (when ...) --- micromamba.el | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/micromamba.el b/micromamba.el index b120ed7..3003a2f 100644 --- a/micromamba.el +++ b/micromamba.el @@ -213,18 +213,16 @@ Returns an alist with the following keys: ;; TODO: implement an optimized finder with e.g. projectile? Or a series of ;; finder functions, that stop at the project root when traversing (let ((containing-path (f-traverse-upwards 'micromamba--contains-env-yml? dir))) - (if containing-path - (f-expand "environment.yml" containing-path) - nil))) + (when containing-path + (f-expand "environment.yml" containing-path)))) (defun micromamba--get-name-from-env-yml (filename) ;; adapted from conda.el "Pull the `name` property out of the YAML file at FILENAME." ;; TODO: find a better way than slurping it in and using a regex... (when filename (let ((env-yml-contents (f-read-text filename))) - (if (string-match "name:[ ]*\\([A-z0-9-_.]+\\)[ ]*$" env-yml-contents) - (match-string 1 env-yml-contents) - nil)))) + (when (string-match "name:[ ]*\\([A-z0-9-_.]+\\)[ ]*$" env-yml-contents) + (match-string 1 env-yml-contents))))) (defun micromamba--infer-env-from-buffer () ;; adapted from conda.el "Search up the project tree for an `environment.yml` defining a conda env." @@ -235,11 +233,10 @@ Returns an alist with the following keys: (when working-dir (or (micromamba--get-name-from-env-yml (micromamba--find-env-yml working-dir)) - (if (or + (when (or micromamba-activate-base-by-default (alist-get 'auto_activate_base (micromamba--get-config))) - "base" - nil))))) + "base"))))) (defun micromamba--get-activation-parameters (prefix) "Get activation parameters for the environment PREFIX. From 4ae2caa892aefed4b39888aa4b51ce637aa62842 Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 19:03:32 +0100 Subject: [PATCH 03/17] if w/o alternate -> when --- micromamba.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micromamba.el b/micromamba.el index 3003a2f..f1ddb24 100644 --- a/micromamba.el +++ b/micromamba.el @@ -355,7 +355,7 @@ This can be set by a buffer-local or project-local variable (e.g. a ((not (eql inferred-env nil)) (micromamba-env-name-to-dir inferred-env)) (t nil)))) - (if (not (eql env-path nil)) + (when (not (eql env-path nil)) (micromamba-activate env-path) (if micromamba-message-on-environment-switch (message "No Conda environment found for <%s>" (buffer-file-name)))))) From 4ffe110f795939a7a37709211c3dc3f19e8fd175 Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 20:11:12 +0100 Subject: [PATCH 04/17] advice-add -> window-selection-change-functions --- micromamba.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micromamba.el b/micromamba.el index f1ddb24..6194c62 100644 --- a/micromamba.el +++ b/micromamba.el @@ -385,8 +385,8 @@ buffer." :global t ;; Forms (if micromamba-env-autoactivate-mode ;; already on, now switching off - (advice-add 'switch-to-buffer :after #'micromamba--switch-buffer-auto-activate) - (advice-remove 'switch-to-buffer #'micromamba--switch-buffer-auto-activate))) + (add-to-list 'window-selection-change-functions #'micromamba--switch-buffer-auto-activate) + (delete #'micromamba--switch-buffer-auto-activate 'window-selection-change-functions))) (provide 'micromamba) ;;; micromamba.el ends here From 9d2e9fce93eeba9c5d4247ca3483cd12923c8542 Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 21:28:58 +0100 Subject: [PATCH 05/17] Use fallback environment instead of base --- micromamba.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/micromamba.el b/micromamba.el index 6194c62..f8730a6 100644 --- a/micromamba.el +++ b/micromamba.el @@ -68,6 +68,9 @@ "Whether to activate the base environment by default if no other is preferred. Default nil." :type 'boolean +(defcustom micromamba-fallback-environment nil + "An environment that micromamba.el activates by default." + :type 'string :group 'micromamba) (defcustom micromamba-preactivate-hook nil @@ -233,10 +236,7 @@ Returns an alist with the following keys: (when working-dir (or (micromamba--get-name-from-env-yml (micromamba--find-env-yml working-dir)) - (when (or - micromamba-activate-base-by-default - (alist-get 'auto_activate_base (micromamba--get-config))) - "base"))))) + micromamba-fallback-environment)))) (defun micromamba--get-activation-parameters (prefix) "Get activation parameters for the environment PREFIX. From 12c9bb366e054300e96d8c8ea821b99126d3843c Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 21:29:35 +0100 Subject: [PATCH 06/17] Fix window-selection-change-functions add --- micromamba.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micromamba.el b/micromamba.el index f8730a6..b133f91 100644 --- a/micromamba.el +++ b/micromamba.el @@ -386,7 +386,7 @@ buffer." ;; Forms (if micromamba-env-autoactivate-mode ;; already on, now switching off (add-to-list 'window-selection-change-functions #'micromamba--switch-buffer-auto-activate) - (delete #'micromamba--switch-buffer-auto-activate 'window-selection-change-functions))) + (delete #'micromamba--switch-buffer-auto-activate window-selection-change-functions))) (provide 'micromamba) ;;; micromamba.el ends here From 452bd0fe1ec91a0c0db0345b8ba802f7b4b13ded Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 21:30:45 +0100 Subject: [PATCH 07/17] Use micromamba-activate on inferred env (duh) --- micromamba.el | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/micromamba.el b/micromamba.el index b133f91..17e4e19 100644 --- a/micromamba.el +++ b/micromamba.el @@ -349,16 +349,9 @@ This can be set by a buffer-local or project-local variable (e.g. a `.dir-locals.el` that defines `conda-project-env-path`), or inferred from an `environment.yml` or similar at the project level." (interactive) - (let* ((inferred-env (micromamba--infer-env-from-buffer)) - (env-path (cond - ((bound-and-true-p conda-project-env-path) conda-project-env-path) - ((not (eql inferred-env nil)) (micromamba-env-name-to-dir inferred-env)) - (t nil)))) - - (when (not (eql env-path nil)) - (micromamba-activate env-path) - (if micromamba-message-on-environment-switch - (message "No Conda environment found for <%s>" (buffer-file-name)))))) + (let ((inferred-env (micromamba--infer-env-from-buffer))) + (when inferred-env + (micromamba-activate inferred-env)))) (defun micromamba--switch-buffer-auto-activate (&rest args) "Add Conda environment activation if a buffer has a file, handling ARGS." From 148e36e32630249b12f8cac2d04b9093ace02ecd Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 21:54:02 +0100 Subject: [PATCH 08/17] Remove dependency on f.el --- micromamba.el | 67 +++++++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 45 deletions(-) diff --git a/micromamba.el b/micromamba.el index 17e4e19..795d36f 100644 --- a/micromamba.el +++ b/micromamba.el @@ -199,39 +199,40 @@ Returns an alist with the following keys: (vars-export . ,vars-export) (scripts . ,scripts)))) -(defun micromamba--env-dir-is-valid (candidate) - "Confirm that CANDIDATE is a valid conda environment." - (let ((dir (file-name-as-directory candidate))) - (and (not (s-blank? candidate)) - (f-directory? dir) - (or (f-directory? (concat dir micromamba-env-executables-dir)) - (f-directory? (concat dir micromamba-env-meta-dir)))))) - -(defun micromamba--contains-env-yml? (candidate) ;; adapted from conda.el - "Does CANDIDATE contain an environment.yml?" - (f-exists? (f-expand "environment.yml" candidate))) - (defun micromamba--find-env-yml (dir) ;; adapted from conda.el - "Find an environment.yml in DIR or its parent directories." - ;; TODO: implement an optimized finder with e.g. projectile? Or a series of - ;; finder functions, that stop at the project root when traversing - (let ((containing-path (f-traverse-upwards 'micromamba--contains-env-yml? dir))) + "Find an environment.yml or .yaml in DIR or its parent directories." + (let ((containing-path (locate-dominating-file dir + (lambda (parent) + (directory-files parent nil "environment.[yml|yaml]"))))) (when containing-path - (f-expand "environment.yml" containing-path)))) + (let ((yml-candidate + (concat (file-name-as-directory containing-path) "environment.yml")) + (yaml-candidate + (concat (file-name-as-directory containing-path) "environment.yaml"))) + (or (when (file-readable-p yml-candidate) yml-candidate) + (when (file-readable-p yaml-candidate) yaml-candidate)))))) + +(defun micromamba--read-file-into-string (filename) + "Read the contents of FILENAME into a string." + (with-temp-buffer + (let ((coding-system-for-read 'utf-8)) + (insert-file-contents filename) + (buffer-string)))) (defun micromamba--get-name-from-env-yml (filename) ;; adapted from conda.el "Pull the `name` property out of the YAML file at FILENAME." - ;; TODO: find a better way than slurping it in and using a regex... (when filename - (let ((env-yml-contents (f-read-text filename))) + (let ((env-yml-contents (micromamba--read-file-into-string filename))) (when (string-match "name:[ ]*\\([A-z0-9-_.]+\\)[ ]*$" env-yml-contents) (match-string 1 env-yml-contents))))) (defun micromamba--infer-env-from-buffer () ;; adapted from conda.el - "Search up the project tree for an `environment.yml` defining a conda env." + "Search up the project tree for an `environment.yml` defining a conda env. + +Return `micromamba-fallback-environment' if not found." (let* ((filename (buffer-file-name)) (working-dir (if filename - (f-dirname filename) + (file-name-directory filename) default-directory))) (when working-dir (or @@ -276,30 +277,6 @@ The parameters value is an alist as defined by (setq eshell-path-env (getenv "PATH"))) ;; "public" functions - -(defun micromamba-env-default-location () - "Default location of the conda environments -- under the Anaconda installation." - (let ((candidates (alist-get 'envs_dirs (micromamba--get-config)))) - (f-full (aref candidates 0)))) - - -(defun micromamba-env-name-to-dir (name) - "Translate NAME to the directory where the environment is located." - (if (and (string= name "base") - (micromamba--env-dir-is-valid micromamba-home)) - (file-name-as-directory (expand-file-name micromamba-home)) - (let* ((default-location (file-name-as-directory (micromamba-env-default-location))) - (initial-possibilities (list name (concat default-location name))) - (possibilities (if (boundp 'venv-location) - (if (stringp venv-location) - (cons venv-location initial-possibilities) - (nconc venv-location initial-possibilities)) - initial-possibilities)) - (matches (-filter 'micromamba--env-dir-is-valid possibilities))) - (if (> (length matches) 0) - (file-name-as-directory (expand-file-name (car matches))) - (error "No such conda environment: %s" name))))) - ;;;###autoload (defun micromamba-activate (prefix) "Switch to environment with PREFIX (path). Prompt if called interactively. From ffbe41a3a421c99d41830444f9d9dee60a3286bf Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 21:54:29 +0100 Subject: [PATCH 09/17] Remove obsolete code --- micromamba.el | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/micromamba.el b/micromamba.el index 795d36f..4b12044 100644 --- a/micromamba.el +++ b/micromamba.el @@ -49,25 +49,11 @@ "Micromamba (environment manager) integration for Emacs." :group 'python) -(defcustom micromamba-home (expand-file-name "~/.micromamba") ;; adapted from conda.el - "The directory where micromamba stores its files." - :type 'directory - :group 'micromamba) - (defcustom micromamba-executable (executable-find "micromamba") "Path to micromamba executable." :type 'string :group 'micromamba) -(defcustom micromamba-message-on-environment-switch t ;;adapted from conda.el - "Whether to message when switching environments. Default true." - :type 'boolean - :group 'micromamba) - -(defcustom micromamba-activate-base-by-default nil ;;adapted from conda.el - "Whether to activate the base environment by default if no other is preferred. -Default nil." - :type 'boolean (defcustom micromamba-fallback-environment nil "An environment that micromamba.el activates by default." :type 'string @@ -98,17 +84,6 @@ Default nil." (defvar eshell-path-env) -;; internal variables that you probably shouldn't mess with - -(defvar micromamba-env-executables-dir ;; copied from virtualenv.el b/w/o conda.el - (if (eq system-type 'windows-nt) "Scripts" "bin") - "Name of the directory containing executables. It is system dependent.") - -(defvar micromamba-env-meta-dir "conda-meta" ;; copied from conda.el - "Name of the directory containing metadata. -This should be consistent across platforms.") - -;; internal utility functions (defun micromamba--call-json (&rest args) "Call micromamba and parse the return value as JSON. @@ -120,17 +95,6 @@ Pass ARGS as arguments to the program." (goto-char (point-min)) (json-read))) -(defvar micromamba--config nil - "Cached copy of configuration that Micromamba sees (including `condarc', etc). -Set for the lifetime of the process.") - -(defun micromamba--get-config() - "Return current Conda configuration. Cached for the lifetime of the process." - (if (not (eq micromamba--config nil)) - micromamba--config - (let ((cfg (micromamba--call-json "config" "list" "--json"))) - (setq micromamba--config cfg)))) - (defun micromamba-envs () "Get micromamba environments. From 9619d1be5b665bb59cd0a0228f2d1b3b3bee60f4 Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 21:55:00 +0100 Subject: [PATCH 10/17] Formatting --- micromamba.el | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/micromamba.el b/micromamba.el index 4b12044..333d065 100644 --- a/micromamba.el +++ b/micromamba.el @@ -319,8 +319,10 @@ buffer." :global t ;; Forms (if micromamba-env-autoactivate-mode ;; already on, now switching off - (add-to-list 'window-selection-change-functions #'micromamba--switch-buffer-auto-activate) - (delete #'micromamba--switch-buffer-auto-activate window-selection-change-functions))) + (add-to-list 'window-selection-change-functions + #'micromamba--switch-buffer-auto-activate) + (delete #'micromamba--switch-buffer-auto-activate + window-selection-change-functions))) (provide 'micromamba) ;;; micromamba.el ends here From 359eb819c8220ea5051ad4b325592e4e98eb700e Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 22:22:43 +0100 Subject: [PATCH 11/17] Fix deletion frm window-selection-change-functions --- micromamba.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/micromamba.el b/micromamba.el index 333d065..5246b64 100644 --- a/micromamba.el +++ b/micromamba.el @@ -321,8 +321,9 @@ buffer." (if micromamba-env-autoactivate-mode ;; already on, now switching off (add-to-list 'window-selection-change-functions #'micromamba--switch-buffer-auto-activate) - (delete #'micromamba--switch-buffer-auto-activate - window-selection-change-functions))) + (setq window-selection-change-functions + (delete #'micromamba--switch-buffer-auto-activate + window-selection-change-functions)))) (provide 'micromamba) ;;; micromamba.el ends here From 497497537f3e2f22ebb49722ded63d1faedaf169 Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 22:23:25 +0100 Subject: [PATCH 12/17] Only activate if same env not active --- micromamba.el | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/micromamba.el b/micromamba.el index 5246b64..41eaad6 100644 --- a/micromamba.el +++ b/micromamba.el @@ -240,6 +240,11 @@ The parameters value is an alist as defined by (setenv (car var) (cdr var))) (setq eshell-path-env (getenv "PATH"))) +(defun micromamba--env-name-to-prefix (name) + "Get the prefix of an environment with NAME." + (let ((envs (micromamba-envs))) + (alist-get name envs nil nil #'equal))) + ;; "public" functions ;;;###autoload (defun micromamba-activate (prefix) @@ -255,8 +260,7 @@ full paths." envs nil nil #'equal)))) ;; To allow calling the function with env name as well (unless (string-match-p (rx bos "/") prefix) - (let ((envs (micromamba-envs))) - (setq prefix (alist-get prefix envs nil nil #'equal))) + (setq prefix (micromamba--env-name-to-prefix prefix)) (unless prefix (user-error "Environment %s not found" prefix))) (micromamba-deactivate) @@ -291,14 +295,15 @@ This can be set by a buffer-local or project-local variable (e.g. a `environment.yml` or similar at the project level." (interactive) (let ((inferred-env (micromamba--infer-env-from-buffer))) - (when inferred-env - (micromamba-activate inferred-env)))) + (when (and inferred-env + (not (string= micromamba-env-current-prefix + (micromamba--env-name-to-prefix inferred-env)))) + (micromamba-activate inferred-env)))) (defun micromamba--switch-buffer-auto-activate (&rest args) "Add Conda environment activation if a buffer has a file, handling ARGS." (let ((filename (buffer-file-name))) (when filename - ;; (message "switch-buffer auto-activating on <%s>" filename) (with-demoted-errors "Error: %S" (micromamba-env-activate-for-buffer))))) From 0fe1ed0c6371eddb7e9c775d38e904ff9863a936 Mon Sep 17 00:00:00 2001 From: claytharrison Date: Sat, 21 Dec 2024 23:00:36 +0100 Subject: [PATCH 13/17] Add caching for yml contents. --- micromamba.el | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/micromamba.el b/micromamba.el index 41eaad6..a7acd37 100644 --- a/micromamba.el +++ b/micromamba.el @@ -82,6 +82,9 @@ (defvar micromamba-env-current-prefix nil "Current activated micromamba environment.") +(defvar micromamba--yml-cache nil + "Stores contents of yaml files.") + (defvar eshell-path-env) (defun micromamba--call-json (&rest args) @@ -186,7 +189,12 @@ Returns an alist with the following keys: (defun micromamba--get-name-from-env-yml (filename) ;; adapted from conda.el "Pull the `name` property out of the YAML file at FILENAME." (when filename - (let ((env-yml-contents (micromamba--read-file-into-string filename))) + (let ((env-yml-contents + (progn + (when (file-has-changed-p filename) + (add-to-list 'micromamba--yml-cache + `(,filename . ,(micromamba--read-file-into-string filename)))) + (cdr (assoc filename micromamba--yml-cache))))) (when (string-match "name:[ ]*\\([A-z0-9-_.]+\\)[ ]*$" env-yml-contents) (match-string 1 env-yml-contents))))) From 10355eec3302af5b933d8497c032a1b5ed95f8e3 Mon Sep 17 00:00:00 2001 From: claytharrison Date: Mon, 6 Jan 2025 19:26:10 +0100 Subject: [PATCH 14/17] Optimize micromamba--get-name-from-env-yml 1. Use setf and alist-get instead of add-to-list 2. Store only env name for buffer in cache 3. Wrap string-match and match-string in save-match-data 4. Pull the string-matching into its own function --- micromamba.el | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/micromamba.el b/micromamba.el index a7acd37..1283ad1 100644 --- a/micromamba.el +++ b/micromamba.el @@ -186,17 +186,21 @@ Returns an alist with the following keys: (insert-file-contents filename) (buffer-string)))) +(defun micromamba--get-name-from-env-yml-contents (env-yml-contents) + "Pull the `name` property out of a stringified YAML file" + (save-match-data + (when (string-match "name:[ ]*\\([A-z0-9-_.]+\\)[ ]*$" env-yml-contents) + (match-string 1 env-yml-contents)))) + (defun micromamba--get-name-from-env-yml (filename) ;; adapted from conda.el "Pull the `name` property out of the YAML file at FILENAME." (when filename - (let ((env-yml-contents - (progn - (when (file-has-changed-p filename) - (add-to-list 'micromamba--yml-cache - `(,filename . ,(micromamba--read-file-into-string filename)))) - (cdr (assoc filename micromamba--yml-cache))))) - (when (string-match "name:[ ]*\\([A-z0-9-_.]+\\)[ ]*$" env-yml-contents) - (match-string 1 env-yml-contents))))) + (let ((filename (file-truename filename))) + (or (unless (file-has-changed-p filename)) + (cdr (assoc filename micromamba--yml-cache)) + (setf (alist-get filename micromamba--yml-cache) + (micromamba--get-name-from-env-yml-contents + (micromamba--read-file-into-string filename))))))) (defun micromamba--infer-env-from-buffer () ;; adapted from conda.el "Search up the project tree for an `environment.yml` defining a conda env. From 335804a49429e1c89dd98394528947be97f510cb Mon Sep 17 00:00:00 2001 From: claytharrison Date: Mon, 6 Jan 2025 19:28:43 +0100 Subject: [PATCH 15/17] Use add-hook/remove-hook instead of setq/delete --- micromamba.el | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/micromamba.el b/micromamba.el index 1283ad1..7f45bc0 100644 --- a/micromamba.el +++ b/micromamba.el @@ -336,11 +336,9 @@ buffer." :global t ;; Forms (if micromamba-env-autoactivate-mode ;; already on, now switching off - (add-to-list 'window-selection-change-functions + (add-hook 'window-selection-change-functions #'micromamba--switch-buffer-auto-activate) - (setq window-selection-change-functions - (delete #'micromamba--switch-buffer-auto-activate - window-selection-change-functions)))) + (remove-hook 'window-selection-change-functions #'micromamba--switch-buffer-auto-activate))) (provide 'micromamba) ;;; micromamba.el ends here From f7933808a65321ad07ef74db6fc5384ade5db3e9 Mon Sep 17 00:00:00 2001 From: claytharrison Date: Mon, 6 Jan 2025 19:30:18 +0100 Subject: [PATCH 16/17] Cache micromamba--buffer-env, check file-remote-p --- micromamba.el | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/micromamba.el b/micromamba.el index 7f45bc0..e630c62 100644 --- a/micromamba.el +++ b/micromamba.el @@ -202,18 +202,25 @@ Returns an alist with the following keys: (micromamba--get-name-from-env-yml-contents (micromamba--read-file-into-string filename))))))) +(defvar-local micromamba--buffer-env nil + "The environment to autoactivate for the buffer.") + (defun micromamba--infer-env-from-buffer () ;; adapted from conda.el "Search up the project tree for an `environment.yml` defining a conda env. Return `micromamba-fallback-environment' if not found." - (let* ((filename (buffer-file-name)) - (working-dir (if filename - (file-name-directory filename) - default-directory))) - (when working-dir - (or - (micromamba--get-name-from-env-yml (micromamba--find-env-yml working-dir)) - micromamba-fallback-environment)))) + (if (file-remote-p buffer-file-name) + micromamba-fallback-environment + (or micromamba--buffer-env + (setq micromamba--buffer-env + (let* ((filename (buffer-file-name)) + (working-dir (if filename + (file-name-directory filename) + default-directory))) + (when working-dir + (or + (micromamba--get-name-from-env-yml (micromamba--find-env-yml working-dir)) + micromamba-fallback-environment))))))) (defun micromamba--get-activation-parameters (prefix) "Get activation parameters for the environment PREFIX. From 9ecd740ec9963e5773f8335f988978ee5dd28bc0 Mon Sep 17 00:00:00 2001 From: claytharrison Date: Tue, 25 Mar 2025 21:38:57 +0100 Subject: [PATCH 17/17] Address changes --- micromamba.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/micromamba.el b/micromamba.el index e630c62..69c7268 100644 --- a/micromamba.el +++ b/micromamba.el @@ -196,9 +196,9 @@ Returns an alist with the following keys: "Pull the `name` property out of the YAML file at FILENAME." (when filename (let ((filename (file-truename filename))) - (or (unless (file-has-changed-p filename)) - (cdr (assoc filename micromamba--yml-cache)) - (setf (alist-get filename micromamba--yml-cache) + (or (unless (file-has-changed-p filename) + (cdr (assoc filename micromamba--yml-cache))) + (setf (alist-get filename micromamba--yml-cache nil nil #'equal) (micromamba--get-name-from-env-yml-contents (micromamba--read-file-into-string filename)))))))