unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: Liu Hui <liuhui1610@gmail.com>
To: kobarity <kobarity@gmail.com>
Cc: Eli Zaretskii <eliz@gnu.org>, 68559@debbugs.gnu.org
Subject: bug#68559: [PATCH] Improve Python shell completion
Date: Tue, 23 Jan 2024 19:31:38 +0800	[thread overview]
Message-ID: <CAOQTW-OR=t7SgS_=+y3B0ixe97YMpaD7AceAiuA5it4ZS45xww@mail.gmail.com> (raw)
In-Reply-To: <eke7edeb6ncy.wl-kobarity@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 754 bytes --]

kobarity <kobarity@gmail.com> 于2024年1月21日周日 17:34写道:

> Is it possible to allow completion of keyword arguments in a
> multi-line function call like the following?
>
> #+begin_src python
> re.split(
>     'b',
>     'abc',
>     maxs
> #+end_src

I have added experimental support in the attached patch, can you test
it please?

> I am not sure if Python snippets should be separated.  Do other
> language supports keep them separate?

I think it allows to reduce code duplication (e.g. the IPython support
code existing in both python-shell-completion-setup-code and
python-shell-readline-ipython-setup-code) and make code more simple.
It seems other languages do not contain as many code snippets as
python.el.

[-- Attachment #2: 0001-Improve-Python-shell-completion.patch --]
[-- Type: text/x-patch, Size: 24720 bytes --]

From 17be2968b6c53dd9fe323b8c28c3ce45efc63318 Mon Sep 17 00:00:00 2001
From: Liu Hui <liuhui1610@gmail.com>
Date: Thu, 18 Jan 2024 12:00:00 +0800
Subject: [PATCH] Improve Python shell completion

* lisp/progmodes/python.el (python-shell-completion-setup-code): Fix
the completion code of IPython.  Change the return value to JSON
string and ...
(python-shell-completion-get-completions): ... simplify parsing.
(inferior-python-mode): Update docstring.
(python-shell-readline-completer): New option.
(python-shell-readline-completer-delims)
(python-shell-readline-jedi-setup-code)
(python-shell-readline-ipython-setup-code): New variables.
(python-shell-completion-native-setup): Setup a suitable readline
completer and set the completer delimiter.
(python-shell-completion-native-get-completions): Convert output
string to completions properly.
(python-shell--get-multiline-input):
(python-shell--extra-completion-context): New functions.
(python-shell-completion-at-point): Send text beginning from the line
start if the completion backend does not need word splitting.  Remove
the detection of import statement because it is not needed anymore.
Create proper completion table based on completions returned from
different backends.

* test/lisp/progmodes/python-tests.el (python-shell-completion-module):
(python-shell-completion-parameters):
(python-shell-completion-multi-line-function-call): New functions.
(python-shell-completion-at-point-jedi-completer):
(python-shell-completion-at-point-ipython): New tests.

* etc/NEWS: Announce the change.
---
 etc/NEWS                            |   6 +
 lisp/progmodes/python.el            | 292 +++++++++++++++++++++++-----
 test/lisp/progmodes/python-tests.el |  78 ++++++++
 3 files changed, 324 insertions(+), 52 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 03b8c3b517a..6fd337727c5 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1029,6 +1029,12 @@ instead of:
 This allows the user to specify command line arguments to the non
 interactive Python interpreter specified by 'python-interpreter'.
 
+*** New user option 'python-shell-readline-completer'.
+This allows the user to specify the readline completer used for Python
+shell completion.  The default is 'auto', which means a suitable
+completer will be configured automatically according to the Python
+interpreter.
+
 ** use-package
 
 +++
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index ff799e1e662..f54b1b29104 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -128,9 +128,9 @@
 ;; receiving escape sequences (with some limitations, i.e. completion
 ;; in blocks does not work).  The code executed for the "fallback"
 ;; completion can be found in `python-shell-completion-setup-code' and
-;; `python-shell-completion-string-code' variables.  Their default
-;; values enable completion for both CPython and IPython, and probably
-;; any readline based shell (it's known to work with PyPy).  If your
+;; `python-shell-completion-get-completions'.  Their default values
+;; enable completion for both CPython and IPython, and probably any
+;; readline based shell (it's known to work with PyPy).  If your
 ;; Python installation lacks readline (like CPython for Windows),
 ;; installing pyreadline (URL `https://ipython.org/pyreadline.html')
 ;; should suffice.  To troubleshoot why you are not getting any
@@ -3601,7 +3601,6 @@ inferior-python-mode
 `python-shell-prompt-block-regexp',
 `python-shell-font-lock-enable',
 `python-shell-completion-setup-code',
-`python-shell-completion-string-code',
 `python-eldoc-setup-code',
 `python-ffap-setup-code' can
 customize this mode for different Python interpreters.
@@ -4319,8 +4318,9 @@ python-shell-completion-setup-code
     completions = []
     completer = None
 
+    import json
     try:
-        import readline
+        import readline, re
 
         try:
             import __builtin__
@@ -4331,16 +4331,29 @@ python-shell-completion-setup-code
 
         is_ipython = ('__IPYTHON__' in builtins or
                       '__IPYTHON__active' in builtins)
-        splits = text.split()
-        is_module = splits and splits[0] in ('from', 'import')
-
-        if is_ipython and is_module:
-            from IPython.core.completerlib import module_completion
-            completions = module_completion(text.strip())
-        elif is_ipython and '__IP' in builtins:
-            completions = __IP.complete(text)
-        elif is_ipython and 'get_ipython' in builtins:
-            completions = get_ipython().Completer.all_completions(text)
+
+        if is_ipython and 'get_ipython' in builtins:
+            def filter_c(prefix, c):
+                if re.match('_+(i?[0-9]+)?$', c):
+                    return False
+                elif c[0] == '%' and not re.match('[%a-zA-Z]+$', prefix):
+                    return False
+                return True
+
+            import IPython
+            try:
+                if IPython.version_info[0] >= 6:
+                    from IPython.core.completer import provisionalcompleter
+                    with provisionalcompleter():
+                        completions = [
+                            [c.text, c.start, c.end, c.type or '?', c.signature or '']
+                             for c in get_ipython().Completer.completions(text, len(text))
+                             if filter_c(text, c.text)]
+                else:
+                    part, matches = get_ipython().Completer.complete(line_buffer=text)
+                    completions = [text + m[len(part):] for m in matches if filter_c(text, m)]
+            except:
+                pass
         else:
             # Try to reuse current completer.
             completer = readline.get_completer()
@@ -4363,7 +4376,7 @@ python-shell-completion-setup-code
     finally:
         if getattr(completer, 'PYTHON_EL_WRAPPED', False):
             completer.print_mode = True
-    return completions"
+    return json.dumps(completions)"
   "Code used to setup completion in inferior Python processes."
   :type 'string)
 
@@ -4404,6 +4417,79 @@ python-shell-completion-native-try-output-timeout
   :version "25.1"
   :type 'float)
 
+(defcustom python-shell-readline-completer 'auto
+  "The readline completer used for Python shell completion.
+If the value is non-nil, Python shell will setup the readline
+completer unless it has been set elsewhere (e.g. in the
+PYTHONSTARTUP file).  Below are possible values:
+- `auto': the completer is determined according to the
+interpreter.  Specifically, the IPython completer, defined in
+`python-shell-readline-ipython-setup-code', is used when the
+interpreter is ipython, otherwise the Jedi completer is used.
+- a string: Python code to setup the readline. It should define
+the function `__PYTHON_EL_setup_readline_completer'.  See
+`python-shell-readline-jedi-setup-code' for reference.
+- `nil': Python shell will do nothing.
+
+In any case, if the completer is not set successfully in the end,
+fallback to the built-in rlcompleter."
+  :type '(choice (const  :tag "Automatic" auto)
+                 (const  :tag "No configuration" nil)
+                 (string :tag "Python setup code"))
+  :version "30.1")
+
+(defvar python-shell-readline-completer-delims nil
+  "Word delimiters used by the readline completer.
+It is automatically set by Python shell.")
+
+(defconst python-shell-readline-jedi-setup-code
+  "
+def __PYTHON_EL_setup_readline_completer():
+    from jedi.utils import setup_readline
+    setup_readline()"
+  "Code used to setup readline completer with Jedi.")
+
+(defconst python-shell-readline-ipython-setup-code
+  "
+def __PYTHON_EL_setup_readline_completer():
+    import readline, re, json, IPython
+
+    class __ipython_RL:
+        def __init__(self, v):
+            self.version = v
+
+        def filter(self, prefix, c):
+            if re.match('_+(i?[0-9]+)?$', c):
+                return False
+            elif c[0] == '%' and not re.match('[%a-zA-Z]+$', prefix):
+                return False
+            return True
+
+        def complete(self, text, state):
+            if state == 0:
+                try:
+                    if self.version >= 6:
+                        from IPython.core.completer import provisionalcompleter
+                        with provisionalcompleter():
+                            self.matches = [json.dumps([
+                                [c.text, c.start, c.end, c.type or '?', c.signature or '']
+                                for c in get_ipython().Completer.completions(text, len(text))
+                                if self.filter(text, c.text)])]
+                    else:
+                        part, matches = get_ipython().Completer.complete(line_buffer=text)
+                        self.matches = [text + m[len(part):] for m in matches
+                                        if self.filter(text, m)]
+                except Exception:
+                    pass
+            try:
+                return self.matches[state]
+            except IndexError:
+                return None
+
+    readline.set_completer(__ipython_RL(IPython.version_info[0]).complete)
+    readline.set_completer_delims('')"
+  "Code used to setup readline completer for IPython.")
+
 (defvar python-shell-completion-native-redirect-buffer
   " *Python completions redirect*"
   "Buffer to be used to redirect output of readline commands.")
@@ -4427,7 +4513,20 @@ python-shell-completion-native-try
 (defun python-shell-completion-native-setup ()
   "Try to setup native completion, return non-nil on success."
   (let* ((process (python-shell-get-process))
-         (output (python-shell-send-string-no-output "
+         (completer (pcase python-shell-readline-completer
+                      ('auto
+                       (if (string-match-p "ipython[23]?\\'" python-shell-interpreter)
+                           python-shell-readline-ipython-setup-code
+                         python-shell-readline-jedi-setup-code))
+                      ((pred stringp) python-shell-readline-completer)
+                      (_ "")))
+         (output (python-shell-send-string-no-output
+                  (concat "
+try:
+    del __PYTHON_EL_setup_readline_completer
+except:
+    pass
+" completer "
 def __PYTHON_EL_native_completion_setup():
     try:
         import readline
@@ -4500,11 +4599,23 @@ python-shell-completion-native-setup
                 else:
                     return completion
 
+        def is_rlcompleter(completer):
+            try:
+                if completer.__self__.__module__ == 'rlcompleter':
+                    return True
+                else:
+                    return False
+            except Exception:
+                return False
+
         completer = readline.get_completer()
 
-        if not completer:
-            # Used as last resort to avoid breaking customizations.
-            import rlcompleter
+        if not completer or is_rlcompleter(completer):
+            try:
+                __PYTHON_EL_setup_readline_completer()
+            except:
+                # Used as last resort to avoid breaking customizations.
+                import rlcompleter
             completer = readline.get_completer()
 
         if completer and not getattr(completer, 'PYTHON_EL_WRAPPED', False):
@@ -4539,9 +4650,13 @@ python-shell-completion-native-setup
         print ('python.el: native completion setup failed, %s: %s'
                % sys.exc_info()[:2])
 
-__PYTHON_EL_native_completion_setup()" process)))
+__PYTHON_EL_native_completion_setup()") process)))
     (when (string-match-p "python\\.el: native completion setup loaded"
                           output)
+      (setq-local python-shell-readline-completer-delims
+                  (string-trim-right
+                   (python-shell-send-string-no-output
+                    "import readline; print(readline.get_completer_delims())")))
       (python-shell-completion-native-try))))
 
 (defun python-shell-completion-native-turn-off (&optional msg)
@@ -4609,6 +4724,8 @@ python-shell-completion-native-get-completions
     (let* ((original-filter-fn (process-filter process))
            (redirect-buffer (get-buffer-create
                              python-shell-completion-native-redirect-buffer))
+           (sep (if (string= python-shell-readline-completer-delims "")
+                    "[\n\r]+" "[ \f\t\n\r\v()]+"))
            (trigger "\t")
            (new-input (concat input trigger))
            (input-length
@@ -4651,32 +4768,63 @@ python-shell-completion-native-get-completions
                      process python-shell-completion-native-output-timeout
                      comint-redirect-finished-regexp)
                 (re-search-backward "0__dummy_completion__" nil t)
-                (cl-remove-duplicates
-                 (split-string
-                  (buffer-substring-no-properties
-                   (line-beginning-position) (point-min))
-                  "[ \f\t\n\r\v()]+" t)
-                 :test #'string=))))
+                (let ((str (buffer-substring-no-properties
+                            (line-beginning-position) (point-min))))
+                  (if (string= "[" (substring str 0 1))
+                      (condition-case nil
+                          (python--parse-json-array str)
+                        (t (cl-remove-duplicates (split-string str sep t)
+                                                 :test #'string=)))
+                    (cl-remove-duplicates (split-string str sep t)
+                                          :test #'string=))))))
         (set-process-filter process original-filter-fn)))))
 
 (defun python-shell-completion-get-completions (process input)
   "Get completions of INPUT using PROCESS."
   (with-current-buffer (process-buffer process)
-    (let ((completions
-           (python-util-strip-string
-            (python-shell-send-string-no-output
-             (format
-              "%s\nprint(';'.join(__PYTHON_EL_get_completions(%s)))"
+    (python--parse-json-array
+     (python-shell-send-string-no-output
+      (format "%s\nprint(__PYTHON_EL_get_completions(%s))"
               python-shell-completion-setup-code
               (python-shell--encode-string input))
-             process))))
-      (when (> (length completions) 2)
-        (split-string completions
-                      "^'\\|^\"\\|;\\|'$\\|\"$" t)))))
+      process))))
 
 (defvar-local python-shell--capf-cache nil
   "Variable to store cached completions and invalidation keys.")
 
+(defun python-shell--get-multiline-input ()
+  "Return lines at a multi-line input in Python shell."
+  (save-excursion
+    (let ((p (point)) lines)
+      (when (progn
+              (beginning-of-line)
+              (looking-back python-shell-prompt-block-regexp (pos-bol)))
+        (push (buffer-substring-no-properties (point) p) lines)
+        (while (progn (comint-previous-prompt 1)
+                      (looking-back python-shell-prompt-block-regexp (pos-bol)))
+          (push (buffer-substring-no-properties (point) (pos-eol)) lines))
+        (push (buffer-substring-no-properties (point) (pos-eol)) lines))
+      lines)))
+
+(defun python-shell--extra-completion-context ()
+  "Return extra completion context of current line in Python shell.
+It currently supports multi-line function call."
+  (let ((lines (python-shell--get-multiline-input)) beg)
+    (when (not (zerop (length lines)))
+      (with-temp-buffer
+        (delay-mode-hooks
+          (insert (string-join lines "\n"))
+          (python-mode)
+          (setq beg (pos-bol))
+          (python-nav-up-list -1)
+          (when (< (point) beg)
+            (while (re-search-backward
+                    (python-rx symbol-name) (pos-bol) t))
+            (string-replace
+             "\n" "" (buffer-substring-no-properties
+                      (point)
+                      (progn (goto-char (point-max)) (pos-eol 0))))))))))
+
 (defun python-shell-completion-at-point (&optional process)
   "Function for `completion-at-point-functions' in `inferior-python-mode'.
 Optional argument PROCESS forces completions to be retrieved
@@ -4687,21 +4835,21 @@ python-shell-completion-at-point
                          ;; Working on a shell buffer: use prompt end.
                          (cdr (python-util-comint-last-prompt))
                        (line-beginning-position)))
-         (import-statement
-          (when (string-match-p
-                 (rx (* space) word-start (or "from" "import") word-end space)
-                 (buffer-substring-no-properties line-start (point)))
-            (buffer-substring-no-properties line-start (point))))
+         (no-delims (with-current-buffer (process-buffer process)
+                      (if python-shell-completion-native-enable
+                          (string= python-shell-readline-completer-delims "")
+                        (string-match-p "ipython[23]?\\'" python-shell-interpreter))))
          (start
           (if (< (point) line-start)
               (point)
             (save-excursion
-              (if (not (re-search-backward
-                        (python-rx
-                         (or whitespace open-paren close-paren
-                             string-delimiter simple-operator))
-                        line-start
-                        t 1))
+              (if (or no-delims
+                      (not (re-search-backward
+                            (python-rx
+                             (or whitespace open-paren close-paren
+                                 string-delimiter simple-operator))
+                            line-start
+                            t 1)))
                   line-start
                 (forward-char (length (match-string-no-properties 0)))
                 (point)))))
@@ -4741,18 +4889,58 @@ python-shell-completion-at-point
                   (t #'python-shell-completion-native-get-completions))))
          (prev-prompt (car python-shell--capf-cache))
          (re (or (cadr python-shell--capf-cache) regexp-unmatchable))
-         (prefix (buffer-substring-no-properties start end)))
+         (prefix (buffer-substring-no-properties start end))
+         (prefix-offset 0)
+         ;; Send extra context for cases like completing function
+         ;; parameters for multi-line function call.
+         (extra-context (and no-delims
+                             (python-shell--extra-completion-context)))
+         (extra-offset (length extra-context)))
+    (unless (zerop extra-offset)
+      (setq prefix (concat extra-context prefix)))
     ;; To invalidate the cache, we check if the prompt position or the
     ;; completion prefix changed.
     (unless (and (equal prev-prompt (car prompt-boundaries))
-                 (string-match re prefix))
+                 (string-match re prefix)
+                 (setq prefix-offset (- (length prefix) (match-end 1))))
       (setq python-shell--capf-cache
             `(,(car prompt-boundaries)
               ,(if (string-empty-p prefix)
                    regexp-unmatchable
-                 (concat "\\`" (regexp-quote prefix) "\\(?:\\sw\\|\\s_\\)*\\'"))
-              ,@(funcall completion-fn process (or import-statement prefix)))))
-    (list start end (cddr python-shell--capf-cache))))
+                 (concat "\\`\\(" (regexp-quote prefix) "\\)\\(?:\\sw\\|\\s_\\)*\\'"))
+              ,@(funcall completion-fn process prefix))))
+    (let ((cands (cddr python-shell--capf-cache)))
+      (cond
+       ((stringp (car cands))
+        (if no-delims
+            ;; Reduce completion candidates due to long prefix.
+            (if-let ((Lp (length prefix))
+                     ((string-match "\\(\\sw\\|\\s_\\)+\\'" prefix))
+                     (L (match-beginning 0)))
+                ;; If extra-offset is not zero:
+                ;;                  start              end
+                ;; o------------------o---------o-------o
+                ;; |<- extra-offset ->|
+                ;; |<----------- L ------------>|
+                ;;                          new-start
+                (list (+ start L (- extra-offset)) end
+                      (mapcar (lambda (s) (substring s L)) cands))
+              (list end end (mapcar (lambda (s) (substring s Lp)) cands)))
+          (list start end cands)))
+       ;; python-shell-completion(-native)-get-completions may produce
+       ;; a list of (text start end type signature) for completion.
+       ;; See `python-shell-readline-ipython-setup-code' and
+       ;; `python-shell-completion-setup-code'.
+       ((consp (car cands))
+        (list (+ start (nth 1 (car cands)) (- extra-offset))
+              ;; Candidates may be cached, so the end position should
+              ;; be adjusted according to current completion prefix.
+              (+ start (nth 2 (car cands)) (- extra-offset) prefix-offset)
+              cands
+              :annotation-function
+              (lambda (c) (concat " " (nth 3 (assoc c cands))))
+              :company-docsig
+              (lambda (c) (nth 4 (assoc c cands)))))))))
 
 (define-obsolete-function-alias
   'python-shell-completion-complete-at-point
diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el
index 97ffd5fe20f..f6926c421fe 100644
--- a/test/lisp/progmodes/python-tests.el
+++ b/test/lisp/progmodes/python-tests.el
@@ -4787,6 +4787,84 @@ python-shell-completion-at-point-native-1
      (end-of-line 0)
      (should-not (nth 2 (python-shell-completion-at-point))))))
 
+(defun python-shell-completion-module ()
+  "Check if modules can be completed in Python shell."
+  (insert "import datet")
+  (completion-at-point)
+  (beginning-of-line)
+  (should (looking-at-p "import datetime"))
+  (kill-line)
+  (insert "from datet")
+  (completion-at-point)
+  (beginning-of-line)
+  (should (looking-at-p "from datetime"))
+  (end-of-line)
+  (insert " import timed")
+  (completion-at-point)
+  (beginning-of-line)
+  (should (looking-at-p "from datetime import timedelta"))
+  (kill-line))
+
+(defun python-shell-completion-parameters ()
+  "Check if parameters can be completed in Python shell."
+  (insert "import re")
+  (comint-send-input)
+  (python-tests-shell-wait-for-prompt)
+  (insert "re.split('b', 'abc', maxs")
+  (completion-at-point)
+  (should (string= "re.split('b', 'abc', maxsplit="
+                   (buffer-substring (line-beginning-position) (point))))
+  (insert "0, ")
+  (should (python-shell-completion-at-point))
+  ;; Test if cache is used.
+  (cl-letf (((symbol-function 'python-shell-completion-get-completions)
+             'ignore)
+            ((symbol-function 'python-shell-completion-native-get-completions)
+             'ignore))
+    (insert "fla")
+    (completion-at-point)
+    (should (string= "re.split('b', 'abc', maxsplit=0, flags="
+                     (buffer-substring (line-beginning-position) (point)))))
+  (beginning-of-line)
+  (kill-line))
+
+(defun python-shell-completion-multi-line-function-call ()
+  "Check if parameters can be completed in multi-line function call."
+  (insert "re.split('b', 'abc',")
+  (comint-send-input)
+  (python-tests-shell-wait-for-prompt)
+  (insert "maxs")
+  (completion-at-point)
+  (should (string= "maxsplit="
+                   (buffer-substring (line-beginning-position) (point)))))
+
+(ert-deftest python-shell-completion-at-point-jedi-completer ()
+  "Check if Python shell completion works with Jedi."
+  (skip-unless (executable-find python-tests-shell-interpreter))
+  (python-tests-with-temp-buffer-with-shell
+   ""
+   (python-shell-with-shell-buffer
+     (python-shell-completion-native-turn-on)
+     (skip-unless (string= python-shell-readline-completer-delims ""))
+     (python-shell-completion-module)
+     (python-shell-completion-parameters)
+     (python-shell-completion-multi-line-function-call))))
+
+(ert-deftest python-shell-completion-at-point-ipython ()
+  "Check if Python shell completion works for IPython."
+  (let ((python-shell-interpreter "ipython")
+        (python-shell-interpreter-args "-i --simple-prompt"))
+    (skip-unless (executable-find python-shell-interpreter))
+    (python-tests-with-temp-buffer-with-shell
+     ""
+     (python-shell-with-shell-buffer
+       (python-shell-completion-native-turn-off)
+       (python-shell-completion-module)
+       (python-shell-completion-parameters)
+       (python-shell-completion-native-turn-on)
+       (python-shell-completion-module)
+       (python-shell-completion-parameters)
+       (python-shell-completion-multi-line-function-call)))))
 
 \f
 ;;; PDB Track integration
-- 
2.25.1


  reply	other threads:[~2024-01-23 11:31 UTC|newest]

Thread overview: 68+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-18  4:48 bug#68559: [PATCH] Improve Python shell completion Liu Hui
2024-01-18  6:39 ` Eli Zaretskii
2024-01-21  9:34   ` kobarity
2024-01-23 11:31     ` Liu Hui [this message]
2024-01-23 14:15       ` kobarity
2024-01-24 10:07         ` Liu Hui
2024-01-25 15:38           ` kobarity
2024-01-26 10:12             ` Liu Hui
2024-01-28 13:22               ` kobarity
2024-01-29 13:15                 ` kobarity
2024-02-01  9:52                   ` Eli Zaretskii
2024-02-01 14:39                     ` kobarity
2024-02-01 15:02                       ` Liu Hui
2024-02-04 12:09                 ` Liu Hui
2024-02-04 14:35                   ` kobarity
2024-02-05 15:03                     ` Liu Hui
2024-02-06  1:25                       ` Liu Hui
2024-02-06 15:12                         ` kobarity
2024-02-07 13:22                           ` Liu Hui
2024-02-07 15:19                             ` kobarity
2024-02-08 12:13                               ` Eli Zaretskii
2024-02-08 13:33                                 ` Liu Hui
2024-02-08 13:46                                   ` Eli Zaretskii
2024-02-08 14:16                                     ` Liu Hui
2024-02-08 16:43                                       ` Eli Zaretskii
2024-02-15 14:43 ` Mattias Engdegård
2024-02-15 16:37   ` Eli Zaretskii
2024-02-15 16:48     ` Eli Zaretskii
2024-02-15 17:21       ` Mattias Engdegård
2024-02-19 13:18       ` Basil L. Contovounesios
2024-02-20  4:46         ` Liu Hui
2024-02-20 13:15           ` Basil L. Contovounesios
2024-02-21 10:00             ` Liu Hui
2024-02-21 14:55               ` Basil L. Contovounesios
2024-02-22 10:31                 ` Liu Hui
2024-02-22 13:56                   ` Basil L. Contovounesios
2024-02-23 13:07                     ` Liu Hui
2024-02-28 14:47                       ` Basil L. Contovounesios
2024-02-16  4:06     ` Liu Hui
2024-02-16  7:41       ` Eli Zaretskii
2024-02-16 12:51         ` Eli Zaretskii
2024-02-16  3:24   ` Liu Hui
2024-02-16  9:34     ` kobarity
2024-02-16 11:45       ` Mattias Engdegård
2024-02-16 15:24         ` kobarity
2024-02-16 15:52           ` Eli Zaretskii
2024-02-16 20:10           ` Mattias Engdegård
2024-02-17 13:33             ` kobarity
2024-02-20 10:16               ` Mattias Engdegård
2024-02-21 13:13                 ` kobarity
2024-02-21 18:20                   ` Mattias Engdegård
2024-02-22 16:15                     ` kobarity
2024-02-23 11:00                       ` Mattias Engdegård
2024-02-23 14:39                         ` kobarity
2024-02-26 11:06                           ` Liu Hui
2024-02-26 12:16                             ` Mattias Engdegård
2024-02-26 15:08                               ` kobarity
2024-02-28 14:49                             ` Basil L. Contovounesios
2024-03-06 10:14                               ` Liu Hui
2024-03-08 15:44                                 ` Basil L. Contovounesios
2024-03-11 11:35                                   ` Liu Hui
2024-03-11 16:02                                     ` Basil L. Contovounesios
2024-03-13 10:21                                       ` Liu Hui
2024-03-14 14:24                                         ` Basil L. Contovounesios
2024-03-16  6:49                                           ` Liu Hui
2024-03-16  8:27                                             ` Eli Zaretskii
2024-02-17  4:36           ` Liu Hui
2024-02-17 13:20             ` Mattias Engdegård

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAOQTW-OR=t7SgS_=+y3B0ixe97YMpaD7AceAiuA5it4ZS45xww@mail.gmail.com' \
    --to=liuhui1610@gmail.com \
    --cc=68559@debbugs.gnu.org \
    --cc=eliz@gnu.org \
    --cc=kobarity@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).