mirror of
https://github.com/SqrtMinusOne/dotfiles.git
synced 2025-12-10 19:23:03 +03:00
emacs: the Bolsheviks can rest in peace
In what's left of it, anyway.
This commit is contained in:
parent
b1392141b5
commit
952fe0ae5d
2 changed files with 241 additions and 220 deletions
207
.emacs.d/init.el
207
.emacs.d/init.el
|
|
@ -536,6 +536,8 @@ then it takes a second \\[keyboard-quit] to abort the minibuffer."
|
|||
|
||||
(general-imap "M-TAB" 'company-yasnippet)
|
||||
|
||||
(setq default-input-method "russian-computer")
|
||||
|
||||
(use-package smartparens
|
||||
:straight t)
|
||||
|
||||
|
|
@ -2865,6 +2867,107 @@ Returns (<buffer> . <workspace-index>) or nil."
|
|||
(with-eval-after-load 'org
|
||||
(org-link-set-parameters "rel" :follow #'browse-url :export #'my/export-rel-url))
|
||||
|
||||
(with-eval-after-load-norem 'org
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
"C-c d" 'org-decrypt-entry
|
||||
"C-c e" 'org-encrypt-entry
|
||||
"M-p" 'org-latex-preview
|
||||
"M-o" 'org-redisplay-inline-images)
|
||||
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
:states '(normal emacs)
|
||||
"L" 'org-shiftright
|
||||
"H" 'org-shiftleft
|
||||
"S-<next>" 'org-next-visible-heading
|
||||
"S-<prior>" 'org-previous-visible-heading
|
||||
"M-0" 'org-next-visible-heading
|
||||
"M-9" 'org-previous-visible-heading
|
||||
"M-]" 'org-babel-next-src-block
|
||||
"M-[" 'org-babel-previous-src-block)
|
||||
|
||||
(general-define-key
|
||||
:keymaps 'org-agenda-mode-map
|
||||
"M-]" 'org-agenda-later
|
||||
"M-[" 'org-agenda-earlier)
|
||||
|
||||
(general-nmap :keymaps 'org-mode-map "RET" 'org-ctrl-c-ctrl-c))
|
||||
|
||||
(defun my/org-link-copy (&optional arg)
|
||||
"Extract URL from org-mode link and add it to kill ring."
|
||||
(interactive "P")
|
||||
(let* ((link (org-element-lineage (org-element-context) '(link) t))
|
||||
(type (org-element-property :type link))
|
||||
(url (org-element-property :path link))
|
||||
(url (concat type ":" url)))
|
||||
(kill-new url)
|
||||
(message (concat "Copied URL: " url))))
|
||||
|
||||
(with-eval-after-load-norem 'org
|
||||
(general-nmap :keymaps 'org-mode-map
|
||||
"C-x C-l" 'my/org-link-copy))
|
||||
|
||||
(defun my/org-babel-next-visible-src-block (arg)
|
||||
"Move to the next visible source block.
|
||||
|
||||
With ARG, repeats or can move backward if negative."
|
||||
(interactive "p")
|
||||
(let ((regexp org-babel-src-block-regexp))
|
||||
(if (< arg 0)
|
||||
(beginning-of-line)
|
||||
(end-of-line))
|
||||
(while (and (< arg 0) (re-search-backward regexp nil :move))
|
||||
(unless (bobp)
|
||||
(while (pcase (get-char-property-and-overlay (point) 'invisible)
|
||||
(`(outline . ,o)
|
||||
(goto-char (overlay-start o))
|
||||
(re-search-backward regexp nil :move))
|
||||
(_ nil))))
|
||||
(cl-incf arg))
|
||||
(while (and (> arg 0) (re-search-forward regexp nil t))
|
||||
(while (pcase (get-char-property-and-overlay (point) 'invisible)
|
||||
(`(outline . ,o)
|
||||
(goto-char (overlay-end o))
|
||||
(re-search-forward regexp nil :move))
|
||||
(_ (end-of-line) nil)))
|
||||
(re-search-backward regexp nil :move)
|
||||
(cl-decf arg))
|
||||
(if (> arg 0) (goto-char (point-max)) (beginning-of-line))))
|
||||
|
||||
(defun my/org-babel-previous-visible-src-block (arg)
|
||||
"Move to the prevous visible source block.
|
||||
|
||||
With ARG, repeats or can move backward if negative."
|
||||
(interactive "p")
|
||||
(my/org-babel-next-visible-src-block (- arg)))
|
||||
|
||||
(with-eval-after-load 'org
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
:states '(normal emacs)
|
||||
"M-]" #'my/org-babel-next-visible-src-block
|
||||
"M-[" #'my/org-babel-previous-visible-src-block))
|
||||
|
||||
(defun my/org-file-open ()
|
||||
(interactive)
|
||||
(let* ((files
|
||||
(thread-last
|
||||
'("projects" "misc")
|
||||
(mapcar (lambda (f)
|
||||
(directory-files (concat org-directory "/" f) t (rx ".org" eos))))
|
||||
(apply #'append)
|
||||
(mapcar (lambda (file)
|
||||
(string-replace (concat org-directory "/") "" file)))
|
||||
(append
|
||||
'("inbox.org" "contacts.org")))))
|
||||
(find-file
|
||||
(concat org-directory "/"
|
||||
(completing-read "Org file: " files)))))
|
||||
|
||||
(my-leader-def
|
||||
"o o" 'my/org-file-open)
|
||||
|
||||
(use-package jupyter
|
||||
:straight t
|
||||
:after (org)
|
||||
|
|
@ -3284,7 +3387,8 @@ Returns (<buffer> . <workspace-index>) or nil."
|
|||
:straight (:host github :repo "SqrtMinusOne/org-clock-agg")
|
||||
:commands (org-clock-agg)
|
||||
:init
|
||||
(my-leader-def "ol" #'org-clock-agg))
|
||||
(with-eval-after-load 'org
|
||||
(my-leader-def "ol" #'org-clock-agg)))
|
||||
|
||||
(with-eval-after-load 'org
|
||||
(setq org-clock-persist 'clock)
|
||||
|
|
@ -4620,107 +4724,6 @@ KEYS is a list of cons cells like (<label> . <time>)."
|
|||
collect (list "ru" :default (plist-get (cdr entry) :utf-8))
|
||||
else collect entry)))))
|
||||
|
||||
(with-eval-after-load-norem 'org
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
"C-c d" 'org-decrypt-entry
|
||||
"C-c e" 'org-encrypt-entry
|
||||
"M-p" 'org-latex-preview
|
||||
"M-o" 'org-redisplay-inline-images)
|
||||
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
:states '(normal emacs)
|
||||
"L" 'org-shiftright
|
||||
"H" 'org-shiftleft
|
||||
"S-<next>" 'org-next-visible-heading
|
||||
"S-<prior>" 'org-previous-visible-heading
|
||||
"M-0" 'org-next-visible-heading
|
||||
"M-9" 'org-previous-visible-heading
|
||||
"M-]" 'org-babel-next-src-block
|
||||
"M-[" 'org-babel-previous-src-block)
|
||||
|
||||
(general-define-key
|
||||
:keymaps 'org-agenda-mode-map
|
||||
"M-]" 'org-agenda-later
|
||||
"M-[" 'org-agenda-earlier)
|
||||
|
||||
(general-nmap :keymaps 'org-mode-map "RET" 'org-ctrl-c-ctrl-c))
|
||||
|
||||
(defun my/org-link-copy (&optional arg)
|
||||
"Extract URL from org-mode link and add it to kill ring."
|
||||
(interactive "P")
|
||||
(let* ((link (org-element-lineage (org-element-context) '(link) t))
|
||||
(type (org-element-property :type link))
|
||||
(url (org-element-property :path link))
|
||||
(url (concat type ":" url)))
|
||||
(kill-new url)
|
||||
(message (concat "Copied URL: " url))))
|
||||
|
||||
(with-eval-after-load-norem 'org
|
||||
(general-nmap :keymaps 'org-mode-map
|
||||
"C-x C-l" 'my/org-link-copy))
|
||||
|
||||
(defun my/org-babel-next-visible-src-block (arg)
|
||||
"Move to the next visible source block.
|
||||
|
||||
With ARG, repeats or can move backward if negative."
|
||||
(interactive "p")
|
||||
(let ((regexp org-babel-src-block-regexp))
|
||||
(if (< arg 0)
|
||||
(beginning-of-line)
|
||||
(end-of-line))
|
||||
(while (and (< arg 0) (re-search-backward regexp nil :move))
|
||||
(unless (bobp)
|
||||
(while (pcase (get-char-property-and-overlay (point) 'invisible)
|
||||
(`(outline . ,o)
|
||||
(goto-char (overlay-start o))
|
||||
(re-search-backward regexp nil :move))
|
||||
(_ nil))))
|
||||
(cl-incf arg))
|
||||
(while (and (> arg 0) (re-search-forward regexp nil t))
|
||||
(while (pcase (get-char-property-and-overlay (point) 'invisible)
|
||||
(`(outline . ,o)
|
||||
(goto-char (overlay-end o))
|
||||
(re-search-forward regexp nil :move))
|
||||
(_ (end-of-line) nil)))
|
||||
(re-search-backward regexp nil :move)
|
||||
(cl-decf arg))
|
||||
(if (> arg 0) (goto-char (point-max)) (beginning-of-line))))
|
||||
|
||||
(defun my/org-babel-previous-visible-src-block (arg)
|
||||
"Move to the prevous visible source block.
|
||||
|
||||
With ARG, repeats or can move backward if negative."
|
||||
(interactive "p")
|
||||
(my/org-babel-next-visible-src-block (- arg)))
|
||||
|
||||
(with-eval-after-load 'org
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
:states '(normal emacs)
|
||||
"M-]" #'my/org-babel-next-visible-src-block
|
||||
"M-[" #'my/org-babel-previous-visible-src-block))
|
||||
|
||||
(defun my/org-file-open ()
|
||||
(interactive)
|
||||
(let* ((files
|
||||
(thread-last
|
||||
'("projects" "misc")
|
||||
(mapcar (lambda (f)
|
||||
(directory-files (concat org-directory "/" f) t (rx ".org" eos))))
|
||||
(apply #'append)
|
||||
(mapcar (lambda (file)
|
||||
(string-replace (concat org-directory "/") "" file)))
|
||||
(append
|
||||
'("inbox.org" "contacts.org")))))
|
||||
(find-file
|
||||
(concat org-directory "/"
|
||||
(completing-read "Org file: " files)))))
|
||||
|
||||
(my-leader-def
|
||||
"o o" 'my/org-file-open)
|
||||
|
||||
(defun my/extract-guix-dependencies (&optional category)
|
||||
(let ((dependencies '()))
|
||||
(org-table-map-tables
|
||||
|
|
|
|||
254
Emacs.org
254
Emacs.org
|
|
@ -945,6 +945,21 @@ References:
|
|||
|
||||
(general-imap "M-TAB" 'company-yasnippet)
|
||||
#+end_src
|
||||
*** Input Method
|
||||
#+begin_quote
|
||||
I have to switch layouts all the time, especially in LaTeX documents, because for some reason the Bolsheviks abandoned the idea of replacing Russian Cyrillic letters with Latin ones.
|
||||
#+end_quote
|
||||
- Me, [2021-04-24 Sat], in a commit to [[https://github.com/SystemCrafters/crafter-configs][SystemCrafters/crafter-configs]].
|
||||
|
||||
Fortunately, Emacs offers a way out of the above with input methods.
|
||||
|
||||
References:
|
||||
- https://protesilaos.com/codelog/2023-12-12-emacs-multilingual-editing/ - A video by Prot from which I learned about this feature.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq default-input-method "russian-computer")
|
||||
#+end_src
|
||||
|
||||
*** Other small packages
|
||||
**** Managing parentheses (smartparens)
|
||||
A minor mode to deal with pairs. Its functionality overlaps with evil-surround, but smartparens provides the most comfortable way to do stuff like automatically insert pairs.
|
||||
|
|
@ -3970,6 +3985,124 @@ Source: https://emacs.stackexchange.com/questions/9807/org-mode-dont-change-rela
|
|||
(org-link-set-parameters "rel" :follow #'browse-url :export #'my/export-rel-url))
|
||||
#+end_src
|
||||
|
||||
** Keybindings & stuff
|
||||
I've moved this block above because the =my-leader-def= expression in the next block seems to override the previous ones. So it has to be on the top.
|
||||
|
||||
*** General keybindings
|
||||
#+begin_src emacs-lisp
|
||||
(with-eval-after-load-norem 'org
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
"C-c d" 'org-decrypt-entry
|
||||
"C-c e" 'org-encrypt-entry
|
||||
"M-p" 'org-latex-preview
|
||||
"M-o" 'org-redisplay-inline-images)
|
||||
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
:states '(normal emacs)
|
||||
"L" 'org-shiftright
|
||||
"H" 'org-shiftleft
|
||||
"S-<next>" 'org-next-visible-heading
|
||||
"S-<prior>" 'org-previous-visible-heading
|
||||
"M-0" 'org-next-visible-heading
|
||||
"M-9" 'org-previous-visible-heading
|
||||
"M-]" 'org-babel-next-src-block
|
||||
"M-[" 'org-babel-previous-src-block)
|
||||
|
||||
(general-define-key
|
||||
:keymaps 'org-agenda-mode-map
|
||||
"M-]" 'org-agenda-later
|
||||
"M-[" 'org-agenda-earlier)
|
||||
|
||||
(general-nmap :keymaps 'org-mode-map "RET" 'org-ctrl-c-ctrl-c))
|
||||
#+end_src
|
||||
*** Copy a link
|
||||
#+begin_src emacs-lisp
|
||||
(defun my/org-link-copy (&optional arg)
|
||||
"Extract URL from org-mode link and add it to kill ring."
|
||||
(interactive "P")
|
||||
(let* ((link (org-element-lineage (org-element-context) '(link) t))
|
||||
(type (org-element-property :type link))
|
||||
(url (org-element-property :path link))
|
||||
(url (concat type ":" url)))
|
||||
(kill-new url)
|
||||
(message (concat "Copied URL: " url))))
|
||||
|
||||
(with-eval-after-load-norem 'org
|
||||
(general-nmap :keymaps 'org-mode-map
|
||||
"C-x C-l" 'my/org-link-copy))
|
||||
#+end_src
|
||||
*** Navigating source blocks
|
||||
An idea born from discussing Org Mode navigation with @Infu.
|
||||
|
||||
Modifying =org-babel-next-src-block= and =org-babel-previous-src-block= to ignore hidden source blocks.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun my/org-babel-next-visible-src-block (arg)
|
||||
"Move to the next visible source block.
|
||||
|
||||
With ARG, repeats or can move backward if negative."
|
||||
(interactive "p")
|
||||
(let ((regexp org-babel-src-block-regexp))
|
||||
(if (< arg 0)
|
||||
(beginning-of-line)
|
||||
(end-of-line))
|
||||
(while (and (< arg 0) (re-search-backward regexp nil :move))
|
||||
(unless (bobp)
|
||||
(while (pcase (get-char-property-and-overlay (point) 'invisible)
|
||||
(`(outline . ,o)
|
||||
(goto-char (overlay-start o))
|
||||
(re-search-backward regexp nil :move))
|
||||
(_ nil))))
|
||||
(cl-incf arg))
|
||||
(while (and (> arg 0) (re-search-forward regexp nil t))
|
||||
(while (pcase (get-char-property-and-overlay (point) 'invisible)
|
||||
(`(outline . ,o)
|
||||
(goto-char (overlay-end o))
|
||||
(re-search-forward regexp nil :move))
|
||||
(_ (end-of-line) nil)))
|
||||
(re-search-backward regexp nil :move)
|
||||
(cl-decf arg))
|
||||
(if (> arg 0) (goto-char (point-max)) (beginning-of-line))))
|
||||
|
||||
(defun my/org-babel-previous-visible-src-block (arg)
|
||||
"Move to the prevous visible source block.
|
||||
|
||||
With ARG, repeats or can move backward if negative."
|
||||
(interactive "p")
|
||||
(my/org-babel-next-visible-src-block (- arg)))
|
||||
|
||||
(with-eval-after-load 'org
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
:states '(normal emacs)
|
||||
"M-]" #'my/org-babel-next-visible-src-block
|
||||
"M-[" #'my/org-babel-previous-visible-src-block))
|
||||
#+end_src
|
||||
*** Open a file from =org-directory=
|
||||
A function to open a file from =org-directory=, excluding a few directories like =roam= and =journal=.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun my/org-file-open ()
|
||||
(interactive)
|
||||
(let* ((files
|
||||
(thread-last
|
||||
'("projects" "misc")
|
||||
(mapcar (lambda (f)
|
||||
(directory-files (concat org-directory "/" f) t (rx ".org" eos))))
|
||||
(apply #'append)
|
||||
(mapcar (lambda (file)
|
||||
(string-replace (concat org-directory "/") "" file)))
|
||||
(append
|
||||
'("inbox.org" "contacts.org")))))
|
||||
(find-file
|
||||
(concat org-directory "/"
|
||||
(completing-read "Org file: " files)))))
|
||||
|
||||
(my-leader-def
|
||||
"o o" 'my/org-file-open)
|
||||
#+end_src
|
||||
** Literate programing
|
||||
*** Python & Jupyter
|
||||
Use jupyter kernels for Org Mode.
|
||||
|
|
@ -4592,7 +4725,8 @@ It's been somewhat complicated to integrate into my workflow, but I think it's b
|
|||
:straight (:host github :repo "SqrtMinusOne/org-clock-agg")
|
||||
:commands (org-clock-agg)
|
||||
:init
|
||||
(my-leader-def "ol" #'org-clock-agg))
|
||||
(with-eval-after-load 'org
|
||||
(my-leader-def "ol" #'org-clock-agg)))
|
||||
#+end_src
|
||||
|
||||
The following enables org-clock persistence between Emacs sessions.
|
||||
|
|
@ -6336,7 +6470,7 @@ Add a custom LaTeX template without default packages. Packages are indented to b
|
|||
#+end_src
|
||||
|
||||
**** Fix Russian dictionary
|
||||
No idea why, but somehow the exported uses english words if there isn't =:default= key in the dictionary.
|
||||
No idea why, but somehow the exported file uses english words if there isn't =:default= key in the dictionary.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(with-eval-after-load 'ox
|
||||
|
|
@ -6351,122 +6485,6 @@ No idea why, but somehow the exported uses english words if there isn't =:defaul
|
|||
else collect entry)))))
|
||||
#+end_src
|
||||
|
||||
** Keybindings & stuff
|
||||
*** General keybindings
|
||||
#+begin_src emacs-lisp
|
||||
(with-eval-after-load-norem 'org
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
"C-c d" 'org-decrypt-entry
|
||||
"C-c e" 'org-encrypt-entry
|
||||
"M-p" 'org-latex-preview
|
||||
"M-o" 'org-redisplay-inline-images)
|
||||
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
:states '(normal emacs)
|
||||
"L" 'org-shiftright
|
||||
"H" 'org-shiftleft
|
||||
"S-<next>" 'org-next-visible-heading
|
||||
"S-<prior>" 'org-previous-visible-heading
|
||||
"M-0" 'org-next-visible-heading
|
||||
"M-9" 'org-previous-visible-heading
|
||||
"M-]" 'org-babel-next-src-block
|
||||
"M-[" 'org-babel-previous-src-block)
|
||||
|
||||
(general-define-key
|
||||
:keymaps 'org-agenda-mode-map
|
||||
"M-]" 'org-agenda-later
|
||||
"M-[" 'org-agenda-earlier)
|
||||
|
||||
(general-nmap :keymaps 'org-mode-map "RET" 'org-ctrl-c-ctrl-c))
|
||||
#+end_src
|
||||
*** Copy a link
|
||||
#+begin_src emacs-lisp
|
||||
(defun my/org-link-copy (&optional arg)
|
||||
"Extract URL from org-mode link and add it to kill ring."
|
||||
(interactive "P")
|
||||
(let* ((link (org-element-lineage (org-element-context) '(link) t))
|
||||
(type (org-element-property :type link))
|
||||
(url (org-element-property :path link))
|
||||
(url (concat type ":" url)))
|
||||
(kill-new url)
|
||||
(message (concat "Copied URL: " url))))
|
||||
|
||||
(with-eval-after-load-norem 'org
|
||||
(general-nmap :keymaps 'org-mode-map
|
||||
"C-x C-l" 'my/org-link-copy))
|
||||
#+end_src
|
||||
*** Navigating source blocks
|
||||
An idea born from discussing Org Mode navigation with @Infu.
|
||||
|
||||
Modifying =org-babel-next-src-block= and =org-babel-previous-src-block= to ignore hidden source blocks.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun my/org-babel-next-visible-src-block (arg)
|
||||
"Move to the next visible source block.
|
||||
|
||||
With ARG, repeats or can move backward if negative."
|
||||
(interactive "p")
|
||||
(let ((regexp org-babel-src-block-regexp))
|
||||
(if (< arg 0)
|
||||
(beginning-of-line)
|
||||
(end-of-line))
|
||||
(while (and (< arg 0) (re-search-backward regexp nil :move))
|
||||
(unless (bobp)
|
||||
(while (pcase (get-char-property-and-overlay (point) 'invisible)
|
||||
(`(outline . ,o)
|
||||
(goto-char (overlay-start o))
|
||||
(re-search-backward regexp nil :move))
|
||||
(_ nil))))
|
||||
(cl-incf arg))
|
||||
(while (and (> arg 0) (re-search-forward regexp nil t))
|
||||
(while (pcase (get-char-property-and-overlay (point) 'invisible)
|
||||
(`(outline . ,o)
|
||||
(goto-char (overlay-end o))
|
||||
(re-search-forward regexp nil :move))
|
||||
(_ (end-of-line) nil)))
|
||||
(re-search-backward regexp nil :move)
|
||||
(cl-decf arg))
|
||||
(if (> arg 0) (goto-char (point-max)) (beginning-of-line))))
|
||||
|
||||
(defun my/org-babel-previous-visible-src-block (arg)
|
||||
"Move to the prevous visible source block.
|
||||
|
||||
With ARG, repeats or can move backward if negative."
|
||||
(interactive "p")
|
||||
(my/org-babel-next-visible-src-block (- arg)))
|
||||
|
||||
(with-eval-after-load 'org
|
||||
(general-define-key
|
||||
:keymaps 'org-mode-map
|
||||
:states '(normal emacs)
|
||||
"M-]" #'my/org-babel-next-visible-src-block
|
||||
"M-[" #'my/org-babel-previous-visible-src-block))
|
||||
#+end_src
|
||||
*** Open a file from =org-directory=
|
||||
A function to open a file from =org-directory=, excluding a few directories like =roam= and =journal=.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun my/org-file-open ()
|
||||
(interactive)
|
||||
(let* ((files
|
||||
(thread-last
|
||||
'("projects" "misc")
|
||||
(mapcar (lambda (f)
|
||||
(directory-files (concat org-directory "/" f) t (rx ".org" eos))))
|
||||
(apply #'append)
|
||||
(mapcar (lambda (file)
|
||||
(string-replace (concat org-directory "/") "" file)))
|
||||
(append
|
||||
'("inbox.org" "contacts.org")))))
|
||||
(find-file
|
||||
(concat org-directory "/"
|
||||
(completing-read "Org file: " files)))))
|
||||
|
||||
(my-leader-def
|
||||
"o o" 'my/org-file-open)
|
||||
#+end_src
|
||||
** System configuration
|
||||
Functions related to literate configuration.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue