102102 (interactive )
103103 (org-babel-execute-src-block )
104104 (let ((start-pos (point ))
105- (found nil ))
105+ (found nil )
106+ (iterations 0 ))
106107 (save-excursion
107108 (while (and (not found)
108- (condition-case nil
109- (progn (org-babel-next-src-block ) t )
110- (error nil )))
111- (let* ((info (org-babel-get-src-block-info ))
109+ (< iterations 10 ) ; Safety limit
110+ (condition-case err
111+ (progn
112+ (org-babel-next-src-block )
113+
114+ t )
115+ (error
116+
117+ nil )))
118+ (setq iterations (1+ iterations))
119+ (let* ((element (org-element-at-point ))
120+ (element-type (org-element-type element))
121+ (info (org-babel-get-src-block-info ))
112122 (lang (car info)))
123+
113124 (when (and lang (string= lang " python" ))
125+
114126 (setq found (point ))))))
127+
115128 (when found
116- (goto-char found))))
129+
130+ (goto-char found))
131+ (unless found
132+ )))
117133
118134; ;;; Cell timing and error handling
119135
@@ -327,7 +343,7 @@ except:
327343 :around #'ob-python-extras/wrap-org-babel-execute-python-mock-plt
328344 '((depth . -5 )))
329345
330- ; ; TODO Refactor these advice so that there is a single one!
346+ ; ; TODO[CepTwbh6GU] Refactor these advice so that there is a single one!
331347
332348
333349; ;;;;; Image Garbage collection
@@ -634,8 +650,8 @@ In regular org-mode, tries to view image or executes normal C-c C-c."
634650
635651; ;; Auto formatting
636652
637- ; ; TODO Get this to work in emacs batch
638- ; ; TODO Add ruff
653+ ; ; TODO[QdYCTfqIxS] Get this to work in emacs batch
654+ ; ; TODO[HlJlq2Q5Rb] Add ruff
639655
640656(defvar ob-python-extras/auto-format t
641657 " When non-nil, automatically format Python source blocks after execution." )
@@ -692,7 +708,7 @@ Applies black in buffer, then uses temp file for isort."
692708 (if (and (org-in-src-block-p )
693709 (string= " python" (org-element-property :language (org-element-at-point ))))
694710 (ob-python-extras/python-help)
695- ; ; TODO This is pretty doom specific, I think.
711+ ; ; TODO[CN6QJEyYIJ] This is pretty doom specific, I think.
696712 (call-interactively #'+lookup/documentation )))
697713
698714
@@ -714,7 +730,7 @@ print(__help_output.getvalue())
714730" symbol))
715731 ; ; it would be better if I could get the *path* to the documentation
716732 ; ; and open in a buffer with less
717- ; ; TODO Maybe look at eldoc at point in python.el ?
733+ ; ; TODO[Hq1PyRoIwW] Maybe look at eldoc at point in python.el ?
718734 (output (when (and session-buffer python-process symbol)
719735 (python-shell-send-string-no-output help-command python-process))))
720736
@@ -762,7 +778,7 @@ for __name, __value in __user_vars.items():
762778 (string= " python" (org-element-property :language (org-element-at-point ))))
763779 (ob-python-extras/python-goto-definition)
764780
765- ; ; TODO This is pretty doom specific, I think.
781+ ; ; TODO[JfUsvOaUkB] This is pretty doom specific, I think.
766782 (call-interactively #'+lookup/definition )))
767783
768784(defun ob-python-extras/python-goto-definition ()
@@ -796,26 +812,58 @@ except Exception as e:
796812; ;;; completion at point
797813
798814(defun my-python-completions-capf ()
815+ " Provide Python completions in org-mode source blocks using the session REPL."
799816 (when (and (derived-mode-p 'org-mode )
800817 (org-in-src-block-p )
801818 (string= (org-element-property :language (org-element-at-point )) " python" ))
802- (let* ((session-buffer (ob-python-extras/org-babel-get-session))
803- (python-process (when session-buffer
804- (or (get-buffer-process session-buffer)
805- (get-buffer-process (format " *%s * " session-buffer)))))
819+ (let* ((session-name (ob-python-extras/org-babel-get-session))
820+ (session-buffer (when session-name
821+ (format " *%s * " session-name)))
822+ (python-process (when (and session-buffer (get-buffer session-buffer))
823+ (get-buffer-process session-buffer)))
806824 ; ; Look backwards for the start of the expression
807825 (start (save-excursion
808- ; ; getting the right object at point
809- ; ; may need more fiddling
810826 (skip-chars-backward " [:alnum:]_." )
811827 (point )))
812828 (end (point ))
813829 (prefix (buffer-substring-no-properties start end)))
814- (when (and session-buffer python-process prefix)
815- (list start
816- end
817- (python-shell-completion-get-completions python-process prefix)
818- :exclusive 'no )))))
830+ (when (and python-process prefix (not (string-empty-p prefix)))
831+ (condition-case err
832+ (let* ((completions (ob-python-extras--get-completions python-process prefix)))
833+ (when completions
834+ (list start end completions :exclusive 'no )))
835+ (error nil ))))))
836+
837+ (defun ob-python-extras--get-completions (process prefix )
838+ " Get completions for PREFIX from Python PROCESS, handling prompt output correctly."
839+ (let* ((escaped-prefix (replace-regexp-in-string " '" " \\\\ '" prefix))
840+ (completion-code
841+ (format "
842+ import json
843+ import rlcompleter
844+ completer = rlcompleter.Completer(globals())
845+ completions = []
846+ i = 0
847+ while True:
848+ completion = completer.complete('%s ', i)
849+ if completion is None:
850+ break
851+ completions.append(completion)
852+ i += 1
853+ print('__COMPLETIONS_START__')
854+ print(json.dumps(completions))
855+ print('__COMPLETIONS_END__')
856+ " escaped-prefix))
857+ (output (python-shell-send-string-no-output completion-code process)))
858+ (when output
859+ ; ; Extract JSON between markers
860+ (let ((cleaned-output
861+ (when (string-match " __COMPLETIONS_START__\n \\ (\\ [.*?\\ ]\\ )\n __COMPLETIONS_END__" output)
862+ (match-string 1 output))))
863+ (when cleaned-output
864+ (condition-case err
865+ (json-parse-string cleaned-output :array-type 'list )
866+ (error nil )))))))
819867
820868(add-hook 'org-mode-hook
821869 (lambda ()
0 commit comments