diff --git a/mistty-term.el b/mistty-term.el index bdcdb66..3423c0a 100644 --- a/mistty-term.el +++ b/mistty-term.el @@ -98,6 +98,21 @@ For example: :group 'mistty :type 'boolean) +(defcustom mistty-multi-line-continue-prompts + '("^ *\\.\\.\\.: " ; ipython + ) + "Regexp used to identify multi-line command prompts. + +These regexps identifies prompts that tell the user that they can +type more, while still allowing them to edit what's above. MisTTY +uses these regexps to identify sections of texts it should ignore +when editing. + +Note that bash \"> \" is not a continuation prompt, with this +definition, because it doesn't allow editing what's above." + :group 'mistty + :type '(list regexp)) + (defconst mistty-right-str "\eOC" "Sequence to send to the process when the rightarrow is pressed.") @@ -772,8 +787,9 @@ properties, detecting the following regions in a prompt line: (eol (pos-eol))) (when (> eol bol) (unless (mistty--detect-right-prompt bol eol) - (let ((indent (mistty--detect-indent bol eol))) - (mistty--detect-trailing-spaces indent eol))))) + (let ((end (or (mistty--detect-continue-prompt bol) + (mistty--detect-indent bol eol)))) + (mistty--detect-trailing-spaces end eol))))) ;; process next line? (forward-line 1) @@ -800,6 +816,22 @@ BOL and EOL define the region to look in." pos))))) +(defun mistty--detect-continue-prompt (bol) + "Detect continue prompt and return its right position or nil. + +BOL define the start of the region to look in." + (catch 'mistty-return + (save-excursion + (goto-char bol) + (dolist (prompt mistty-multi-line-continue-prompts) + (when (looking-at prompt) + (let ((end (match-end 0))) + (when (> end bol) + (add-text-properties + bol end + '(mistty-skip continue-prompt yank-handler (nil "" nil nil))) + (throw 'mistty-return end)))))))) + (defun mistty--detect-indent (bol eol) "Detect line indentation and return its right position or nil. diff --git a/mistty.el b/mistty.el index e10abd9..5729ced 100644 --- a/mistty.el +++ b/mistty.el @@ -3239,7 +3239,7 @@ This is meant to be added to `pre-redisplay-functions'" (when (eq (car last-state) (current-buffer)) (setq last-pos (cdr last-state)))) (pcase-dolist (`(,beg . ,end) (mistty--cursor-skip-ranges - pos '(indent right-prompt))) + pos '(indent right-prompt continue-prompt))) (unless move-to (setq move-to diff --git a/test/mistty-term-test.el b/test/mistty-term-test.el index 2f76002..3673d97 100644 --- a/test/mistty-term-test.el +++ b/test/mistty-term-test.el @@ -212,3 +212,26 @@ (should-not (text-property-any (point-min) (point-max) 'mistty-skip 'trailing)) (should (string-match "^\\[ + < right \\]$" (mistty-test-content :show-property '(mistty-skip right-prompt)))))) + +(ert-deftest mistty-test-prepare-term-for-refresh-ipython-continue-prompt () + (ert-with-test-buffer () + (setq-local term-width 80) + + (insert (concat "In [3]: for i in (1, 2, 3):" (propertize " " 'mistty-maybe-skip t) "\n")) + (insert (concat " ...: if i > 1: " (propertize " " 'mistty-maybe-skip t) "\n")) + (insert (concat " ...: print(i) " (propertize " " 'mistty-maybe-skip t) "\n")) + (insert (concat "In [133]: for i in (1, 2, 3):\n")) + (insert (concat " ...: print(i)\n")) + + (put-text-property (point-min) (point-max) 'mistty-changed t) + (mistty--prepare-term-for-refresh (current-buffer) (point-min)) + + (should-not (text-property-any (point-min) (point-max) 'mistty-skip 'right-prompt)) + (should-not (text-property-any (point-min) (point-max) 'mistty-skip 'indent)) + (should (equal + (concat "In [3]: for i in (1, 2, 3):\n" + "[ ...: ] if i > 1:\n" + "[ ...: ] print(i)\n" + "In [133]: for i in (1, 2, 3):\n" + "[ ...: ] print(i)") + (mistty-test-content :show-property '(mistty-skip continue-prompt)))))) diff --git a/test/mistty-test.el b/test/mistty-test.el index 5b7b362..ad8885c 100644 --- a/test/mistty-test.el +++ b/test/mistty-test.el @@ -4356,3 +4356,42 @@ (mistty-with-test-buffer (:shell ipython) (mistty-send-text "print('hello')") (should (equal "hello" (mistty-send-and-capture-command-output))))) + +(ert-deftest mistty-test-ipython-detect-continue-prompt () + (mistty-with-test-buffer (:shell ipython) + (mistty--send-string mistty-proc "for i in (1, 2, 3):\nif i > 2:\nprint(i)") + (mistty-wait-for-output :test (lambda () (save-excursion + (goto-char (point-min)) + (and (search-forward "...:" nil 'noerror) + (search-forward "...:" nil 'noerror))))) + + (should (equal (concat "In [1]: for i in (1, 2, 3):\n" + "[ ...: ] if i > 2:\n" + "[ ...: ] print(i)") + (mistty-test-content + :start (save-excursion + (goto-char (point-min)) + (search-forward "In [") + (match-beginning 0)) + :show-property '(mistty-skip continue-prompt)))))) + +(ert-deftest mistty-test-ipython-skip-continue-prompt () + (mistty-with-test-buffer (:shell ipython :selected t) + (let ((win (selected-window)) + (mistty-skip-empty-spaces t)) + (mistty--send-string mistty-proc "for i in (1, 2, 3):\nif i > 2:\nprint(i)") + (mistty-wait-for-output :test (lambda () (save-excursion + (goto-char (point-min)) + (and (search-forward "...:" nil 'noerror) + (search-forward "...:" nil 'noerror))))) + + (mistty-test-goto " if i > 2") + (mistty--cursor-skip win) + (goto-char (1- (point))) + (mistty--cursor-skip win) + + (should (equal + "In [1]: for i in (1, 2, 3):<>" + (mistty-test-content + :show (point) + :start (pos-bol) :end (pos-eol)))))))