External

Interaction with the outside world.

Preamble

;;; external.el --- Cunene: My emacs configuration. -*- lexical-binding: t -*-
;; Author: Marco Craveiro <marco_craveiro@gmail.com> URL:
;; https://github.com/mcraveiro/prelude Version: 0.0.3 Keywords: convenience

;; This file is not part of GNU Emacs.

;;; Commentary:

;; General editor configuration

;;; License:

;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License
;; as published by the Free Software Foundation; either version 3
;; of the License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Code:

Shells

Bash

(defun cunene/create-named-bash-shell (name)
  "Starts a new named bash shell. Bash must be in the path.
NAME is the postfix for the buffer name."
  (interactive "sName: ")
  (let
      ((explicit-shell-file-name "bash")
       (explicit-bash-args '("--login" "-i"))
       (buffer-name (concat "*shell - " name "*")))
    (shell)
    (rename-buffer buffer-name)))

;; (global-set-key (kbd "C-x M") 'cunene/create-named-bash-shell)

Eshell

Links:

Todo:

(use-package eshell
  :config
  (add-hook 'eshell-mode-hook
            (lambda ()
              ;; Only set this once, it shouldn't need to be local
              (setq imenu-generic-expression
                    '(("Prompt" "^ \\$ \\(.*\\)" 1)))))  ; More specific regex
  (require 'em-hist)
  (require 'em-alias)
  (add-to-list
   'eshell-command-aliases-list (list "ll" "ls -l"))
  (defalias 'ff 'find-file)
  (use-package eshell-git-prompt
    :config
    (setq-default eshell-directory-name (cunene/cache-concat "eshell"))
    (eshell-git-prompt-use-theme 'powerline)
    (define-advice eshell-git-prompt-powerline-dir (:override () short)
      "Show only last directory."
      (file-name-nondirectory (directory-file-name default-directory))))
  :bind (:map eshell-hist-mode-map
              ("<down>" . 'next-line)
              ("<up>" . 'previous-line)
              ;; ([remap eshell-previous-matching-input-from-input] . helm-eshell-history)
              ;; ([remap eshell-list-history] . helm-eshell-history)
              ))


;; Start a new eshell even if one is active.
;; (global-set-key (kbd "C-x M") (lambda () (interactive) (eshell t)))

;; Start a regular shell if you prefer that.
;; (global-set-key (kbd "C-x M-m") 'shell)

(defun eshell/z (&optional regexp)
    "Navigate to a previously visited directory in eshell, or to
any directory proferred by `consult-dir'."
    (let ((eshell-dirs (delete-dups
                        (mapcar 'abbreviate-file-name
                                (ring-elements eshell-last-dir-ring)))))
      (cond
       ((and (not regexp) (featurep 'consult-dir))
        (let* ((consult-dir--source-eshell `(:name "Eshell"
                                             :narrow ?e
                                             :category file
                                             :face consult-file
                                             :items ,eshell-dirs))
               (consult-dir-sources (cons consult-dir--source-eshell
                                          consult-dir-sources)))
          (eshell/cd (substring-no-properties
                      (consult-dir--pick "Switch directory: ")))))
       (t (eshell/cd (if regexp (eshell-find-previous-directory regexp)
                            (completing-read "cd: " eshell-dirs)))))))

(defun cunene/create-named-eshell (name)
  "Create an eshell buffer named NAME."
  (interactive "sName: ")
  (let
      ((buffer-name (concat "*shell - " name "*")))
    (eshell)
    (rename-buffer buffer-name)))

;; (global-set-key (kbd "C-x m") 'cunene/create-named-eshell)

Powershell

powershell https://github.com/jschaf/powershell.el
(use-package powershell)

Terminals

(use-package eat
  :preface
  (defun cunene/eat-open-file (file)
    "Helper function to open files from eat terminal."
    (interactive)
    (condition-case err
        (if (file-exists-p file)
            (find-file-other-window file t)
          (user-error "File doesn't exist: %s" file))
      (error (message "Failed to open file: %s" (error-message-string err)))))  :init
  (add-to-list 'project-switch-commands '(eat-project "Eat terminal") t)
  (add-to-list 'project-switch-commands '(eat-project-other-window "Eat terminal other window") t)
  (add-to-list 'project-kill-buffer-conditions '(major-mode . eat-mode))
  :config
  (add-to-list 'eat-message-handler-alist (cons "open" 'cunene/eat-open-file))
  (setq process-adaptive-read-buffering nil) ; makes EAT a lot quicker!
  (setq eat-term-name "xterm-256color") ; https://codeberg.org/akib/emacs-eat/issues/119"
  (setq eat-kill-buffer-on-exit t)
  (setq eat-shell-prompt-annotation-failure-margin-indicator "")
  (setq eat-shell-prompt-annotation-running-margin-indicator "")
  (setq eat-shell-prompt-annotation-success-margin-indicator ""))

(with-eval-after-load 'eat
    (global-set-key (kbd "C-c o t") 'eat)
    (global-set-key (kbd "C-c o T") 'eat-other-window)
    (define-key project-prefix-map (kbd "t") 'eat-project)
    (define-key project-prefix-map (kbd "T") 'eat-project-other-window))

General

(use-package project-shells)

Grepping

deadgrep https://github.com/Wilfred/deadgrep
rg https://github.com/dajva/rg.el
;; (use-package deadgrep
;;   :ensure t)

(use-package rg
  :ensure t)

Web

;; hard-code location of Chrome and Edge on Windows.

(defvar cunene/browse-url-edge-program
  (if (eq system-type 'windows-nt)
      "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
    "/opt/microsoft/msedge/msedge")
"The name by which to invoke the Edge program.")

(if (eq system-type 'windows-nt)
    (setq browse-url-chrome-program "C:/Program Files/Google/Chrome/Application/chrome"))

(defvar cunene/browse-url-edge-arguments ""
  "Additional arguments for Edge.")

(defun cunene/browse-url-edge (url &optional _new-window)
  "Ask the Google Edge WWW browser to load URL.
Default to the URL around or before point.  The strings in
variable `cunene/browse-url-edge-arguments' are also passed to
Google Edge.
The optional argument NEW-WINDOW is not used."
  (interactive (browse-url-interactive-arg "URL: "))
  (setq url (browse-url-encode-url url))
  (let* ((process-environment (browse-url-process-environment)))
    (apply #'start-process
           (concat "google-edge " url) nil
           cunene/browse-url-edge-program
           (append
            cunene/browse-url-edge-arguments
            (list url)))))

(defvar cunene/browsers
      '(("Chrome" . browse-url-chrome)
        ("EWW" . eww-browse-url)
        ("Edge" . cunene/browse-url-edge)
        ("Firefox" . browse-url-firefox))
      "Available web browsers.")

(defvar cunene/last-browser nil
  "Last browser selected by the user.")

(defun cunene/browse-url (url &optional new-window)
  "Select browser from menu, remembering choice."
  (interactive (browse-url-interactive-arg "URL: "))
  (when (or (null cunene/last-browser)
            current-prefix-arg)  ; Use prefix arg to force choice
    (setq cunene/last-browser
          (completing-read "Browser: " cunene/browsers nil t)))
  (if-let ((browser-fn (cdr (assoc cunene/last-browser cunene/browsers))))
      (funcall browser-fn url new-window)
    (error "No browser selected")))

(setq browse-url-browser-function #'cunene/browse-url)

;; Sourced from here:
;; - https://emacs.stackexchange.com/questions/50433/add-browser-bookmark-to-bookmark-browser
;; FIXME this breaks bookmark display at present.
(require 'bookmark)
(defun cunene/bookmark-url (url &optional name)
  "Add URL to bookmarks with an optional NAME.
If NAME is not provided, prompt for it.
URL should be a valid web address."
  (interactive
   (let ((url (read-string "URL: " (or (thing-at-point-url-at-point) ""))))
     (list url (read-string "Bookmark name: " url))))

  ;; Validate URL format
  (unless (string-match-p "^\\(https?\\|ftp\\|file\\)://" url)
    (user-error "Invalid URL format: %s" url))

  (let ((bookmark-name (or name url)))
    ;; Check if bookmark already exists
    (when (assoc bookmark-name bookmark-alist)
      (if (y-or-n-p (format "Bookmark '%s' already exists. Replace? " bookmark-name))
          (bookmark-delete bookmark-name)
        (user-error "Bookmark creation canceled")))

    ;; Create the bookmark
    (bookmark-store bookmark-name nil
                    `((filename . ,url)
                      (handler . (lambda (bm)
                                   (browse-url (bookmark-get-filename bm))))))

    ;; Save to disk
    (bookmark-save)

    (message "Bookmarked: %s -> %s" bookmark-name url)))

;; Make addresses clickable
(add-hook 'prog-mode-hook #'goto-address-prog-mode)
(add-hook 'text-mode-hook #'goto-address-mode)
(add-hook 'org-mode-hook #'goto-address-mode)
(add-hook 'help-mode-hook #'goto-address-mode)

SSH

ssh https://github.com/ieure/ssh-el =
(use-package ssh
  :config  (add-hook 'ssh-mode-hook
                     (lambda ()
                       (setq ssh-directory-tracking-mode t)
                       (push '(ssh-mode comint-input-ring comint-input-ring-index comint-bol) consult-mode-histories)
                       (shell-dirtrack-mode t)
                       (setq dirtrackp nil))))

(require 'tramp)
(setq tramp-default-method "ssh")
(setq ssh-explicit-args nil)
(tramp-change-syntax 'default)

;; Putty is expected to be on the path.
(when (string-equal system-type "windows-nt")
  (setq ssh-program "putty")
  (add-to-list 'tramp-methods
               `("plinkw"
                 (tramp-login-program        "plink")
                 ;; ("%h") must be a single element, see `tramp-compute-multi-hops'.
                 (tramp-login-args           (("-l" "%u") ("-P" "%p") ("-t")
                                              ("%h") ("\"")
                                              (,(format
                                                 "env 'TERM=%s' 'PROMPT_COMMAND=' 'PS1=%s'"
                                                 tramp-terminal-type
                                                 "$"))
                                              ("/bin/sh") ("\"")))
                 (tramp-remote-shell         "/bin/sh")
                 (tramp-remote-shell-login   ("-l"))
                 (tramp-remote-shell-args    ("-c"))
                 (tramp-default-port         22))
               ))

Processes

prodigy https://github.com/rejeep/prodigy.el
(use-package prodigy
  :bind ("C-c 8" . #'prodigy)
  :config
;;  (load "~/.config/emacs/services.el" 'noerror)
)

Mongo

inf-mongo https://github.com/endofunky/inf-mongo
(use-package inf-mongo)

;; Examples:
;; (cunene/mongo-oid-from-date "2023-03-09")
;; (cunene/mongo-oid-from-date)
(defun cunene/mongo-oid-from-date (&optional date)
  "Return a valid MongoDB ObjectId for the specified DATE."
  (let ((timestamp
         (format "%x"
                 (time-to-seconds
                  (date-to-time (or date (current-time)))))) ;; FXIME: current time needs conversion
         (machine-id (s-pad-right 3 "\x00" (format "%x" (cl-random 16777216))))
         (pid (s-pad-right 2 "\x00" (format "%x" (emacs-pid))))
         (increment (s-pad-right 3 "\x00" (format "%x" (cl-random 16777216)))))
    (format "%s%s%s%s" timestamp machine-id pid increment))
  )

(defun cunene/object-id-to-timestamp (start end)
  "Given a region with a mongo ObjectId, return the timestamp.
START and END mark the region."
  (interactive "r")
  (let*
      ((object-id (buffer-substring-no-properties (mark) (point)))
       (timestamp-hex (substring-no-properties object-id 0 8))
       (timestamp-num (string-to-number timestamp-hex 16)))
    (message "Timestamp: %s"
             (format-time-string "%Y-%m-%d %a %H:%M:%S" timestamp-num)))
  )

(defun cunene/timet-to-timestamp (start end)
  "Given a region with a UNIX timestamp in time_t, return the human timestamp.
START and END mark the region."
  (interactive "r")
  (let*
      ((time-t-string (buffer-substring-no-properties (mark) (point)))
       (timestamp-num (string-to-number time-t-string))
       (timestamp-time (seconds-to-time timestamp-num)))
    (message "Timestamp: %s"
             (format-time-string "%Y-%m-%d %a %H:%M:%S" timestamp-time)))
  )

Logging

(use-package paimon)

Postamble

;;; external.el ends here