#lang racket/base (require racket/path racket/string "config.rkt") (provide find-editor find-editors editor-downloads configured-editor set-editor! set-editor-auto!) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Supporting functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Quote an executable path for use as a Git editor command. ; pre : p is a path to an executable. ; post : p has only been converted to a string. ; result : A quoted command path. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (quote-command-path p) (format "\"~a\"" (path->string p))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Build a path below an environment variable when it is defined. ; pre : variable is an environment variable name. ; post : The environment has only been inspected. ; result : The constructed path, or #f. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (environment-path variable . parts) (let ((base (getenv variable))) (if base (apply build-path base parts) #f))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Make an editor description from an executable path. ; pre : p is a path or #f; arguments contains any required wait arguments. ; post : The filesystem has only been inspected. ; result : (name description command), or #f when the executable does not exist. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (editor-at name description p arguments) (if (and p (file-exists? p)) (list name description (string-append (quote-command-path p) arguments)) #f)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Find an editor executable on PATH. ; pre : executable is a pathless executable name. ; post : PATH has only been inspected. ; result : (name description command), or #f. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (editor-on-path name description executable arguments) (let ((p (find-executable-path executable))) (editor-at name description p arguments))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Remove duplicate editor descriptions while preserving preference order. ; pre : editors contains editor descriptions or #f values. ; post : editors has only been inspected. ; result : One editor description per editor name. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (unique-editors editors) (let loop ((remaining editors) (names '()) (result '())) (cond ((null? remaining) (reverse result)) ((not (car remaining)) (loop (cdr remaining) names result)) (else (let* ((editor (car remaining)) (name (car editor))) (if (member name names) (loop (cdr remaining) names result) (loop (cdr remaining) (cons name names) (cons editor result)))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Return known GUI editors found on Windows. ; pre : The current platform is Windows. ; post : PATH and standard Windows installation locations were inspected. ; result : A list of editor descriptions. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (find-windows-editors) (unique-editors (list (editor-on-path "vscode" "Visual Studio Code" "code.cmd" " --wait") (editor-on-path "vscode" "Visual Studio Code" "code.exe" " --wait") (editor-at "vscode" "Visual Studio Code" (environment-path "LOCALAPPDATA" "Programs" "Microsoft VS Code" "bin" "code.cmd") " --wait") (editor-at "vscode" "Visual Studio Code" (environment-path "ProgramFiles" "Microsoft VS Code" "bin" "code.cmd") " --wait") (editor-at "vscode" "Visual Studio Code" (environment-path "ProgramFiles(x86)" "Microsoft VS Code" "bin" "code.cmd") " --wait") (editor-on-path "notepad++" "Notepad++" "notepad++.exe" " -multiInst -nosession") (editor-at "notepad++" "Notepad++" (environment-path "ProgramFiles" "Notepad++" "notepad++.exe") " -multiInst -nosession") (editor-at "notepad++" "Notepad++" (environment-path "ProgramFiles(x86)" "Notepad++" "notepad++.exe") " -multiInst -nosession") (editor-on-path "notepad" "Notepad" "notepad.exe" "") (editor-at "notepad" "Notepad" (environment-path "SystemRoot" "System32" "notepad.exe") "")))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Return known GUI editors found on macOS. ; pre : The current platform is macOS. ; post : PATH and standard application locations were inspected. ; result : A list of editor descriptions. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (find-macos-editors) (unique-editors (list (editor-on-path "vscode" "Visual Studio Code" "code" " --wait") (editor-at "vscode" "Visual Studio Code" (string->path "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code") " --wait") (let ((open (or (find-executable-path "open") (let ((p (string->path "/usr/bin/open"))) (if (file-exists? p) p #f))))) (if open (list "textedit" "TextEdit" (format "~a -W -a TextEdit" (quote-command-path open))) #f))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Return known GUI editors found on Unix/Linux. ; pre : The current platform is Unix. ; post : PATH and common Linux installation locations were inspected. ; result : A list of editor descriptions. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (find-unix-editors) (unique-editors (list (editor-on-path "vscode" "Visual Studio Code" "code" " --wait") (editor-on-path "kate" "Kate" "kate" " --block") (editor-on-path "gedit" "Gedit" "gedit" " --wait") (editor-on-path "xed" "Xed" "xed" " --wait") (editor-at "vscode" "Visual Studio Code" (string->path "/snap/bin/code") " --wait") (editor-at "vscode" "Visual Studio Code" (string->path "/usr/local/bin/code") " --wait") (editor-at "vscode" "Visual Studio Code" (string->path "/usr/bin/code") " --wait") (editor-at "kate" "Kate" (string->path "/usr/local/bin/kate") " --block") (editor-at "kate" "Kate" (string->path "/usr/bin/kate") " --block") (editor-at "gedit" "Gedit" (string->path "/usr/bin/gedit") " --wait") (editor-at "xed" "Xed" (string->path "/usr/bin/xed") " --wait")))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Set the editor environment variables for Git commands started by git-cli. ; pre : command is a valid Git editor command. ; post : GIT_EDITOR and GIT_SEQUENCE_EDITOR contain command. ; result : command. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (set-editor-environment! command) (putenv "GIT_EDITOR" command) (putenv "GIT_SEQUENCE_EDITOR" command) command) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Provided functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Return all well-known GUI editors found on the current platform. ; pre : The platform and filesystem are available. ; post : No editor has been started. ; result : A list of (name description command) items. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (find-editors) (case (system-type 'os) ((windows) (find-windows-editors)) ((macosx) (find-macos-editors)) ((unix) (find-unix-editors)) (else '()))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Return official download pointers for optional GUI editors. ; pre : The current platform is known. ; post : No network request has been made. ; result : A list of (name description url) items. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (editor-downloads) (case (system-type 'os) ((windows) '(("vscode" "Visual Studio Code" "https://code.visualstudio.com/download") ("notepad++" "Notepad++" "https://notepad-plus-plus.org/downloads/"))) ((macosx) '(("vscode" "Visual Studio Code" "https://code.visualstudio.com/download"))) ((unix) '(("vscode" "Visual Studio Code" "https://code.visualstudio.com/download"))) (else '()))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Read the editor explicitly configured for git-cli. ; pre : The git-cli configuration is readable. ; post : The configuration has not been changed. ; result : The configured editor command, or #f. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (configured-editor) (let ((editor (cfg-get 'git 'editor #f))) (if (and (string? editor) (not (string=? (string-trim editor) ""))) editor #f))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Store and activate the editor command used by git-cli. ; pre : command is a command string suitable for GIT_EDITOR. ; post : The command has been stored and applied to the Git editor environment. ; result : command. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (set-editor! command) (cfg-set! 'git 'editor command) (set-editor-environment! command)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Return git-cli to automatic editor detection and activate that editor. ; pre : A well-known GUI editor can be found on the current platform. ; post : The explicit editor setting is cleared and the detected editor is active. ; result : The automatically detected editor command. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (set-editor-auto!) (let ((editors (find-editors))) (if (null? editors) (error 'git-config "No well-known GUI editor found") (begin (cfg-set! 'git 'editor "") (set-editor-environment! (caddr (car editors))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Find the GUI editor that git-cli should offer to Git. ; pre : The platform and git-cli configuration are available. ; post : No editor has been started. ; result : A configured or well-known GUI editor command, or #f. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (find-editor) (or (configured-editor) (let ((editors (find-editors))) (if (null? editors) #f (caddr (car editors))))))