#-*- mode: org -*-
#+TITLE: Emacs Setup for Programming Languages
#+AUTHOR: Volker Edelmann
#+EMAIL:  vedelmann@gmx.de
#+STARTUP: indent
#+OPTIONS:  toc:nil
#+OPTIONS:   ^:{}

#+begin_src emacs-lisp
  ;; -*- coding: utf-8; lexical-binding: t -*-
#+end_src

* Introduction
- REPL
- editing
  - editing paradigm
  - code completion
  - templates
- moving
- selecting
- code folding
- static syntax checking
- compilation
- debugging
- project-management


* Comments
Builtin!
#+begin_src emacs-lisp
  (require 'newcomment)
#+end_src

* Key bindings for all prog-modes
#+begin_src emacs-lisp
  (bind-keys :map prog-mode-map
  	     ("C-c /"   . indent-region)
  	     ("C-c C-/" . indent-buffer)
  	     ("C-c ;"   . comment-dwim)
  	     ("C-c i"   . consult-imenu)
  	     ("C-c c"   . consult-flymake)
  	     )
#+end_src

* UI
If there is need for a modal interface, a Major-Mode-Hydra can be defined.
Bind in mayor-mode keymap to:

#+begin_example
:bind ( :map KEYMAP
  ("M-SPC" . major-mode-hydra))
#+end_example

* Lisp-like (s-expression) languages 
   - emacs-lisp
   - clojure
   - [guile (scheme)]
   - [hy]

* Languages with Tree-sitter grammars
** Exploring
- combobulate
- Built-In commands: treesit-explore-mode

** Tree-sitter and combobulate
store grammars in a common location

#+begin_src emacs-lisp
      (setq treesit-extra-load-path (list (concat emacsen-dir "treesit/grammars/")                                           )
      )
#+end_src

#+begin_src emacs-lisp
    (use-package treesit
      :mode (("\\.tsx\\'" . tsx-ts-mode))
      :preface
      (defun mp-setup-install-grammars ()
        "Install Tree-sitter grammars if they are absent."
        (interactive)
        (dolist (grammar
                  '(
                    (tcl . "https://github.com/tree-sitter-grammars/tree-sitter-tcl")
  		  ))
          (add-to-list 'treesit-language-source-alist grammar)
      ;; Only install `grammar' if we don't already have it
      ;; installed. However, if you want to *update* a grammar then
      ;; this obviously prevents that from happening.
      (unless (treesit-language-available-p (car grammar))
        (treesit-install-language-grammar (car grammar)))))

      ;; Optional, but recommended. Tree-sitter enabled major modes are
      ;; distinct from their ordinary counterparts.
      ;;
      ;; You can remap major modes with `major-mode-remap-alist'. Note
      ;; that this does *not* extend to hooks! Make sure you migrate them
      ;; also
      (dolist (mapping
               '((python-mode . python-ts-mode)
                 (css-mode . css-ts-mode)
                 (typescript-mode . typescript-ts-mode)
                 (js2-mode . js-ts-mode)
                 (bash-mode . bash-ts-mode)
                 (css-mode . css-ts-mode)
                 (json-mode . json-ts-mode)
                 (js-json-mode . json-ts-mode)))
        (add-to-list 'major-mode-remap-alist mapping))
      :config
      (mp-setup-install-grammars)
      ;; Do not forget to customize Combobulate to your liking:
      ;;
      ;;  M-x customize-group RET combobulate RET
      ;;
      (use-package combobulate
        :preface
        ;; You can customize Combobulate's key prefix here.
        ;; Note that you may have to restart Emacs for this to take effect!
        (setq combobulate-key-prefix "C-c o")

        ;; Optional, but recommended.
        ;;
        ;; You can manually enable Combobulate with `M-x
        ;; combobulate-mode'.
        :hook
        ((python-ts-mode . combobulate-mode)
         (js-ts-mode . combobulate-mode)
         (html-ts-mode . combobulate-mode)
         (css-ts-mode . combobulate-mode)
         (yaml-ts-mode . combobulate-mode)
         (typescript-ts-mode . combobulate-mode)
         (json-ts-mode . combobulate-mode)
         (tsx-ts-mode . combobulate-mode))
        ;; Amend this to the directory where you keep Combobulate's source
        ;; code.
        :load-path ("ve/combobulate")))
#+end_src

*** Additional Grammars
Repos, where grammars are found:

#+begin_src emacs-lisp
  (setq treesit-language-source-alist
        '(
          (awk "https://github.com/Beaglefoot/tree-sitter-awk")
          (bash "https://github.com/tree-sitter/tree-sitter-bash")
          (c . ("https://github.com/tree-sitter/tree-sitter-c"))
          (cpp . ("https://github.com/tree-sitter/tree-sitter-cpp"))
          (clojure "https://github.com/sogaiu/tree-sitter-clojure")	
          (cmake "https://github.com/uyha/tree-sitter-cmake")
          (css "https://github.com/tree-sitter/tree-sitter-css")
  	(d . ("https://github.com/gdamore/tree-sitter-d"))
          (dockerfile . ("https://github.com/camdencheek/tree-sitter-dockerfile"))
  	(elisp "https://github.com/Wilfred/tree-sitter-elisp")
          (html "https://github.com/tree-sitter/tree-sitter-html")
  	(java "https://github.com/tree-sitter/tree-sitter-java")
  	(javascript "https://github.com/tree-sitter/tree-sitter-javascript" "master" "src")
          (json "https://github.com/tree-sitter/tree-sitter-json")             
          (julia "https://github.com/tree-sitter/tree-sitter-julia")
          (kotlin "https://github.com/fwcd/tree-sitter-kotlin")
          (lua . ("https://github.com/Azganoth/tree-sitter-lua"))
          (make "https://github.com/alemuller/tree-sitter-make")
          (markdown "https://github.com/ikatyang/tree-sitter-markdown")
          (perl "https://github.com/tree-sitter-perl/tree-sitter-perl")
          (php . ("https://github.com/tree-sitter/tree-sitter-php"))
          (python "https://github.com/tree-sitter/tree-sitter-python")
  	(rust "https://github.com/tree-sitter/tree-sitter-rust")
          (sql "https://github.com/m-novikov/tree-sitter-sql")
          (scala "https://github.com/tree-sitter/tree-sitter-scala")
          (toml "https://github.com/tree-sitter/tree-sitter-toml")
          (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
          (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
          (yaml "https://github.com/ikatyang/tree-sitter-yaml")          )
        )
#+end_src
 
Check if a grammar is available:

(treesit-language-available-p 'd)

*** Textobjects 

#+begin_src emacs-lisp
  (setq treesit-query-dir (concat emacsen-dir "treesit/queries/"))
  ;; (setq treesit-query-dir "~/emacsen/treesit/queries/")
#+end_src


*** TODO Movement
Using ts-movement turned out not to be useful.

*** Selections
very important when working with a REPL:

#+begin_src emacs-lisp
  (use-package expreg)
#+end_src

Bind keys (example):

(define-key map ("C-+") 'expreg-expand)

(define-key map ("C--") 'expreg-contract)


Easiest solution found: expreg.el. Just 2 commands.

*** Folding
Using tree-sitter-fold turned out not to be useful.


** Others
*** TCL/TK

* Containers
#+begin_src emacs-lisp
  (defcustom lemacs-docker-executable 'docker
    "The executable to be used with docker-mode."
    :type '(choice
  	  (const :tag "podman" podman))
    :group 'lemacs)

  (use-package docker
    :defer t
    :ensure t
    :bind ("C-c d" . docker)
    :config
    (pcase lemacs-docker-executable
      ('docker
       (setf docker-command "docker"
  	   docker-compose-command "docker-compose"
  	   docker-container-tramp-method "docker"))
      ('podman
       (setf docker-command "podman"
  	   docker-compose-command "podman-compose"
  	   docker-container-tramp-method "podman"))))

#+end_src

* Visuals
** Parentheses, brackets, ...
#+begin_src emacs-lisp
(require 'elec-pair)
#+end_src

Mark [, {, (:
#+begin_src emacs-lisp
  (use-package rainbow-delimiters)
;;  (add-hook 'prog-mode-hook 'rainbow-delimiters-mode)
  (set-face-attribute 'rainbow-delimiters-unmatched-face nil
                      :foreground "red"
  		    :height 1.5
  		    :weight 'ultra-bold
                      :inherit 'error)
   (set-face-attribute 'rainbow-delimiters-depth-1-face nil
  		     :foreground "black"
                       :weight 'extra-bold)
  (set-face-attribute 'rainbow-delimiters-depth-2-face nil
  		     :foreground "black"
                       :weight 'extra-bold)
  (set-face-attribute 'rainbow-delimiters-depth-3-face nil
  		     :foreground "black"
                       :weight 'extra-bold)
#+end_src

** Project Browser
*** DISCARDED dired-sidebar
cannot display fd-dired queries


* Editing
In Lisp-like modes, there are perhaps better alternatives:
- lispy
- paredit
- smartparens


* Source Code Version Control
** Fossil
#+begin_src emacs-lisp
  (use-package vc-fossil
    :defer t
    :init (add-to-list 'vc-handled-backends 'Fossil t)
    )
#+end_src

** TODO Pijul
TODO: The package vc-pijul does not yet exist.

#+begin_src emacs-lisp
#+end_src
(use-package vc-pijul
    :defer t
    :init (add-to-list 'vc-handled-backends 'Pijul t)
    )


* Project Management
#+begin_src emacs-lisp
    (put 'compile-command         'safe-local-variable t)
    (setq enable-local-variables :safe)

    (use-package project
      :diminish project-mode " Π"
  )
#+end_src


* Static Checkers/Linters
Although flymake is an Emacs built-in, flycheck has more checkers and
often works without further configuration when a
checker/linter is available via the OS path settings.

** My default
Flycheck is very well documented:

https://www.flycheck.org/en/latest/user/installation.html

https://www.flycheck.org/en/latest/user/quickstart.html

https://www.flycheck.org/en/latest/community/extensions.html#language-support

flycheck does not explicitly support windows, but there are rarely issues on windows.
Most checkers/linters are external programs, they have to be installed. The
configuration is found in the language specific section,
After installation, it is easy to check the setup. From the docs:

#+begin_verse
 Visit a Python or Javascript file and check whether your Flycheck setup is complete with C-c ! v.
#+end_verse

As it is turned on per default, it has to be explicitly turned off in modes,
where this is unwanted (e.g. if a flymake-checker is preferred). This is done
via Hooks.

Diagnostics: list-flycheck-errors

#+begin_src emacs-lisp
  (use-package flycheck
    :ensure t
  )
#+end_src


** Builtin: flymake

Diagnostics: flymake-show-buffer-diagnostics

#+begin_src emacs-lisp
  (require 'flymake)
  (use-package emacs
    :config
       (setq flymake-mode-line-lighter t)
  )
#+end_src


* Finding and referencing symbols
** Language Servers
https://langserver.org/

*** Built-In eglot 
#+begin_src emacs-lisp
  (use-package eglot
  :custom
    (eglot-autoshutdown t)
    (eglot-events-buffer-size 0)
    (eglot-extend-to-xref nil)
    (eglot-ignored-server-capabilities
     '(:hoverProvider
       :documentHighlightProvider
       :documentFormattingProvider
       :documentRangeFormattingProvider
       :documentOnTypeFormattingProvider
       :colorProvider
       :foldingRangeProvider))
    (eglot-stay-out-of '(yasnippet tempel))
  )
#+end_src

** Tags
*** Built-In semantic
#+begin_src emacs-lisp
  (require 'semantic)
#+end_src

*** Gnu Global
Global has 5 languages built-in and supports ctags with all its 150+ languages.

A reasonable solution using Gnu Global seems to be:

https://github.com/leoliu/ggtags

- completion
- reference

comes with integration for completion-at-point, eldoc- and xref-integration.

#+begin_src emacs-lisp
  (use-package ggtags
    :bind ( :map ggtags-navigation-map
            ("M-o" . nil)
           )
    )
#+end_src
  
**** Prerequisites
  Install
  - Gnu Global
  - Python: pygments


* References
#+begin_src emacs-lisp
  (use-package xref
    :pin gnu
    :custom
    (xref-auto-jump-to-first-xref t)
    (xref-file-name-display 'project-relative)
    (xref-search-program 'ripgrep)
    :config
    (setq xref-show-xrefs-function       #'consult-xref
          xref-show-definitions-function #'consult-xref) 
    )
#+end_src

* Documentation

#+begin_src emacs-lisp
  (use-package eldoc
    :pin gnu
    :diminish
    :bind ("s-d" . #'eldoc)
    :custom
    (eldoc-echo-area-prefer-doc-buffer t)
    (eldoc-echo-area-use-multiline-p t))
#+end_src


* Navigation

add this hook when no eglot or tags-backend is available:

(add-hook 'xref-backend-functions #'dumb-jump-xref-activate)

#+begin_src emacs-lisp
  (use-package dumb-jump)
#+end_src


** Hydra
An xref-Hydra may be more general and preferrable.

#+begin_src emacs-lisp
    (pretty-hydra-define hydra-dumb-jump (:color blue)
      ("Dumb Jump"
       (
        ("b" dumb-jump-back "Back")
        ("l" dumb-jump-quick-look "Quick look")
        ("j" dumb-jump-go "Go")
        ("i" dumb-jump-go-prompt "Prompt")
        ("o" dumb-jump-go-other-window "Other window")
        ("e" dumb-jump-go-prefer-external "Go external")
        ("x" dumb-jump-go-prefer-external-other-window "Go external other window")
        )
       )
      )
#+end_src


* Folding
Depending on the mode, hideshow may be a good solution.
hs-minor-mode creates a menu.

Two functions are defined for cycling visibility: 

#+begin_src emacs-lisp
      (use-package hideshow
        :hook
        (prog-mode . hs-minor-mode)
      :config

      (defun my/hs-cycle (&optional level)
        (interactive "p")
        (let (message-log-max
              (inhibit-message t))
          (if (= level 1)
              (pcase last-command
                ('hs-cycle
                 (hs-hide-level 1)
                 (setq this-command 'hs-cycle-children))
                ('hs-cycle-children
                 ;; TODO: Fix this case. `hs-show-block' needs to be
                 ;; called twice to open all folds of the parent
                 ;; block.
                 (save-excursion (hs-show-block))
                 (hs-show-block)
                 (setq this-command 'hs-cycle-subtree))
                ('hs-cycle-subtree
                 (hs-hide-block))
                (_
                 (if (not (hs-already-hidden-p))
                     (hs-hide-block)
                   (hs-hide-level 1)
                   (setq this-command 'hs-cycle-children))))
            (hs-hide-level level)
            (setq this-command 'hs-hide-level))))

      (defun my/hs-global-cycle ()
        (interactive)
        (pcase last-command
          ('hs-global-cycle
           (save-excursion (hs-show-all))
           (setq this-command 'hs-global-show))
          (_ (hs-hide-all))))
      )
 #+end_src

 #+begin_src emacs-lisp
   (defun wrycode/prog-cycle (&optional level)
     "Emulates org-mode's tab cycling using builtin hideshow. Tries to cycle
       visibility when it makes sense, and otherwise fallback to the default
       indent-for-tab-command.

       Basically, if your cursor is at the beginning of a line where there is
       an opening block, tab will cycle visibility just like org mode
       headings. It will also reset the global cycling state for
       `wrycode/prog-cycle-global' for the same intuitive feel as the org-mode
       cycling commands."
     (interactive "p")
     (let ((current-line (line-number-at-pos))
           (cycled nil))
       (if (bolp) ; Only attempt cycling if at the beginning of the line
           (save-excursion
             ;; Move to the end of line to get "inside" any block that might exist on the line
       	  (end-of-line)
             ;; Only proceed if hideshow finds a block that begins on this
             ;; line
             (when (and (hs-find-block-beginning)
                        (= current-line (line-number-at-pos (point))))
               (setq cycled t) ; Mark that we're cycling
       	    (setq wrycode/global-cycle-state 2) ; resets global cycling state
               (pcase last-command
                 ('prog-cycle-children
                  (save-excursion (hs-show-block))
                  (setq this-command '_))
                 (_
                  ;; Initial press.  If block is visible, hide it. If we
                  ;; already hid it, show level one
                  (if (not (hs-already-hidden-p))
                      (hs-hide-block)
                    (hs-hide-level 1)
                    (setq this-command 'prog-cycle-children)))))))
       (unless cycled ; fall back to default tab behavior
         (call-interactively 'indent-for-tab-command))))

   (defvar wrycode/global-cycle-state 0)

   (defun wrycode/prog-cycle-global ()
     "Org-shifttab, but in prog-mode"  
     (interactive)
     (setq wrycode/global-cycle-state (mod (1+ wrycode/global-cycle-state) 3))
     (pcase wrycode/global-cycle-state
       (0 (hs-hide-all))
       (1 (hs-hide-level 3))    
       (2 (hs-show-all))))

   (add-hook 'prog-mode-hook
   	  (lambda ()
   	    (local-set-key (kbd "TAB") 'wrycode/prog-cycle)
      	    (local-set-key (kbd "<backtab>") 'wrycode/prog-cycle-global)))

#+end_src
* 

* Outline
 Outline minor mode will work for text- and prog-modes, as long as there are
 suitable regexp for detecting the structure. See my TCL config for an example.

 Outline mode comes in handy,

 - when hideshow does not work properly
 - when you aim to shift whole code blocks
   
 #+begin_src emacs-lisp
         (setq my/is-outline-overview nil)

         (defun my/outline-overview ()
            "Show only outline headings."
            (interactive)
            (outline-show-all)
            (outline-hide-body)
            (setq my/is-outline-overview t)
            )

      (defun my/outline-reset ()
            "Show only outline headings."
            (interactive)
            (outline-show-all)
            (setq my/is-outline-overview nil)
            )
         
         (defun my/toggle-outline-overview ()   
           "toggle outline headings."
           (interactive)
           (if my/is-outline-overview
      	    (my/outline-reset)
               (my/outline-overview)
             )
           )
#+end_src

* Narrowing
#+begin_src emacs-lisp
  (defun ve/narrow-to-region-indirect (start end)
    "Restrict editing in this buffer to the current region, indirectly."
    (interactive "r")
    (deactivate-mark)
    (let ((buf (clone-indirect-buffer nil nil)))
      (with-current-buffer buf
        (narrow-to-region start end))
      (switch-to-buffer buf)))
#+end_src

* Accessing cheat-sheets

The DP-Proxy blocks the requests.

#+begin_src emacs-lisp
  (if sys/linuxp
  (use-package cheat-sh)
  )
#+end_src



* Bye

#+begin_src emacs-lisp
;;; programming-common-setup.el ends here
#+end_src