;; ess-julia.el --- ESS julia mode and inferior interaction -*- lexical-binding: t; -*-
;; Copyright (C) 2012-2022 Free Software Foundation, Inc.
;; Author: Vitalie Spinu
;; Maintainer: Vitalie Spinu
;; Created: 02-04-2012 (ESS 12.03)
;; Keywords: ESS, julia
;; This file is part of GNU Emacs.
;;; 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 this program. If not, see
;;
;;; Commentary:
;; Customize inferior-julia-program to point to your julia binary
;; and start the inferior with M-x julia.
;; As of Sept 2015, this file depends heavily on julia-mode.el from the Julia
;; sources. If you install ESS using `make', this will work fine, otherwise
;; ensure that julia-mode.el is on your path before loading this file.
;;; Code:
(require 'ess-help)
(require 'ess-inf)
(require 'ess-r-mode)
(require 'ess-utils)
;; Don't require `julia-mode' to compile this file.
(when t (require 'julia-mode))
(declare-function julia-mode "julia-mode" ())
(declare-function julia-mode-latexsub-completion-at-point-before "julia-mode" ())
(declare-function julia-mode-latexsub-completion-at-point-around "julia-mode" ())
(defvar julia-mode-syntax-table)
(defvar ac-prefix)
(declare-function company-in-string-or-comment "company")
(declare-function company-doc-buffer "company")
(defcustom inferior-julia-args ""
"String of arguments (see `julia --help') used when starting julia."
:group 'ess-julia
:type 'string)
(eval-when-compile
(require 'cl-lib))
(defun ess-julia-send-string-function (process string _visibly)
"Send the Julia STRING to the PROCESS.
VISIBLY is not currently used."
(let ((file (concat temporary-file-directory "julia_eval_region.jl")))
(with-temp-file file
(insert string))
(process-send-string process (format ess-load-command file))))
;;; HELP
(cl-defmethod ess-help-get-topics (proc &context (ess-dialect "julia"))
(append (with-current-buffer (ess--foreground-command "ESS.all_help_topics()\n")
(split-string (buffer-string) "\n"))
(ess-julia--get-objects proc)))
(defun ess-julia--retrieve-topics (url)
(with-current-buffer (url-retrieve-synchronously url)
(require 'url)
(goto-char (point-min))
(let (out)
(while (re-search-forward
"toctext[ \"]+href=\"\\([^>]+\\)\">\\([^<]+\\) ")
(inferior-ess-secondary-prompt . nil)
(inferior-ess-prompt . "\\w*> ")
(ess-local-customize-alist . 'ess-julia-customize-alist)
(inferior-ess-program . inferior-julia-program)
(ess-load-command . "include(expanduser(\"%s\"))\n")
(ess-funargs-command . "ESS.fun_args(\"%s\")\n")
(ess-dump-error-re . "in \\w* at \\(.*\\):[0-9]+")
(ess-error-regexp . "\\(^\\s-*at\\s-*\\(?3:.*\\):\\(?2:[0-9]+\\)\\)")
(ess-error-regexp-alist . ess-julia-error-regexp-alist)
(ess-mode-completion-syntax-table . ess-julia-completion-syntax-table)
;; (inferior-ess-objects-command . inferior-ess-r-objects-command)
;; (inferior-ess-search-list-command . "search()\n")
(inferior-ess-help-command . "ESS.help(\"%s\")\n")
;; (inferior-ess-help-command . "help(\"%s\")\n")
(ess-language . "julia")
(ess-dialect . "julia")
(ess-suffix . "jl")
(ess-dump-filename-template . (replace-regexp-in-string
"S$" ess-suffix ; in the one from custom:
ess-dump-filename-template-proto))
(ess-mode-editing-alist . nil)
(ess-change-sp-regexp . nil );ess-r-change-sp-regexp)
(ess-help-sec-regex . ess-help-r-sec-regex)
(ess-help-sec-keys-alist . ess-help-r-sec-keys-alist)
(ess-function-pattern . ess-r-function-pattern)
(ess-object-name-db-file . "ess-jl-namedb.el" )
(ess-smart-operators . ess-r-smart-operators)
(inferior-ess-exit-command . "exit()\n")
;;harmful for shell-mode's C-a: -- but "necessary" for ESS-help?
(inferior-ess-language-start . nil)
(ess-STERM . "iESS")
(ess-editor . ess-r-editor)
(ess-pager . ess-r-pager)
(ess-getwd-command . "pwd()\n")
(ess-setwd-command . "cd(expanduser(\"%s\"))\n"))
"Variables to customize for Julia.")
(cl-defmethod ess--help-web-search-override (cmd &context (ess-dialect "julia"))
"Offer to search the web for a Julia command."
(browse-url (format "https://docs.julialang.org/en/latest/search/?q=%s" cmd)))
(defvar ess-julia-completion-syntax-table
(let ((table (copy-syntax-table ess-r-mode-syntax-table)))
(modify-syntax-entry ?. "_" table)
;; (modify-syntax-entry ?: "_" table)
;; (modify-syntax-entry ?$ "_" table)
(modify-syntax-entry ?@ "_" table)
table)
"Syntax table used for completion and help symbol lookup.
It makes underscores and dots word constituent chars.")
(cl-defmethod ess-help-commands (&context (ess-dialect "julia"))
'((packages . "_ess_list_categories()\n")
(package-index . "_ess_print_index(\"%s\")\n")
(index-keyword-reg . "^\\(.*+\\):$*")
(index-start-reg . ":")))
(defvar ess-julia-mode-syntax-table (copy-syntax-table julia-mode-syntax-table))
(defvar ess-julia-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map ess-mode-map)
map)
"Keymap for `ess-julia-mode'.")
;;;###autoload
(define-derived-mode ess-julia-mode julia-mode "ESS[julia]"
"Major mode for julia files."
:group 'ess-Julia
(setq-local ess-local-customize-alist ess-julia-customize-alist)
(setq ess-dialect "julia")
(ess-setq-vars-local ess-julia-customize-alist)
;; eldoc
(ess--setup-eldoc #'ess-julia-eldoc-function)
;; auto-complete
(ess--setup-auto-complete '(ac-source-ess-julia-objects))
;; company
(ess--setup-company '(company-ess-julia-objects))
;; for emacs >= 24
(remove-hook 'completion-at-point-functions #'ess-filename-completion 'local) ;; should be first
(add-hook 'completion-at-point-functions #'ess-julia-object-completion nil 'local)
(add-hook 'completion-at-point-functions #'ess-filename-completion nil 'local)
(add-hook 'completion-at-point-functions #'julia-mode-latexsub-completion-at-point-before nil 'local)
(add-hook 'completion-at-point-functions #'julia-mode-latexsub-completion-at-point-around nil 'local)
(if (fboundp 'ess-add-toolbar) (ess-add-toolbar)))
;; Inferior mode
(defvar inferior-ess-julia-mode-syntax-table
(copy-syntax-table ess-julia-mode-syntax-table)
"Syntax table for `inferior-ess-julia-mode'.")
(define-derived-mode inferior-ess-julia-mode inferior-ess-mode "iESS[julia]"
"Major mode for inferior julia processes."
:group 'ess-Julia
(ess-setq-vars-local ess-julia-customize-alist)
(setq-local comint-use-prompt-regexp t)
(setq comint-prompt-regexp (concat "^" inferior-ess-prompt))
(setq ess-dialect "julia")
;; eldoc
(ess--setup-eldoc #'ess-julia-eldoc-function)
;; auto-complete
(ess--setup-auto-complete '(ac-source-ess-julia-objects) t)
;; company
(ess--setup-company '(company-ess-julia-objects) t)
(remove-hook 'completion-at-point-functions #'ess-filename-completion 'local) ;; should be first
(add-hook 'completion-at-point-functions #'ess-julia-object-completion nil 'local)
(add-hook 'completion-at-point-functions #'ess-filename-completion nil 'local)
(add-hook 'completion-at-point-functions #'julia-mode-latexsub-completion-at-point-before nil 'local)
(add-hook 'completion-at-point-functions #'julia-mode-latexsub-completion-at-point-around nil 'local)
(setq comint-input-sender #'ess-julia-input-sender))
(defvar ess-julia-mode-hook nil)
(defvar ess-julia-post-run-hook nil
"Functions run in process buffer after starting julia process.")
;;;###autoload
(defun run-ess-julia (&optional start-args)
"Start an inferior julia process.
Optional prefix START-ARGS (\\[universal-argument]) allows to set
command line arguments, such as --load=. This should be OS
agnostic. If you have certain command line arguments that should
always be passed to julia, put them in the variable
`inferior-julia-args'."
(interactive "P")
;; get settings, notably inferior-julia-program :
(if (null inferior-julia-program)
(error "'inferior-julia-program' does not point to 'julia' or 'julia-basic' executable")
(ess-write-to-dribble-buffer ;; for debugging only
(format
"\n(julia): ess-dialect=%s, buf=%s, start-arg=%s\n current-prefix-arg=%s\n"
ess-dialect (current-buffer) start-args current-prefix-arg))
(let* ((jl-start-args
(concat inferior-julia-args " " ; add space just in case
(if start-args
(read-string
(concat "Starting Args"
(if inferior-julia-args
(concat " [other than '" inferior-julia-args "']"))
" ? "))
nil))))
(let ((inf-buf (inferior-ess jl-start-args ess-julia-customize-alist)))
(with-current-buffer inf-buf
(ess--tb-start)
;; Remove ` from julia's logo
(goto-char (point-min))
(while (re-search-forward "`" nil t)
(replace-match "'"))
;; Remove an offending unmatched parenthesis
(goto-char (point-min))
(forward-line 4)
(when (re-search-forward "(" nil t)
(replace-match "|"))
(goto-char (point-max))
;; --> julia helpers from ../etc/ess-julia.jl :
(ess--inject-code-from-file (format "%sess-julia.jl" ess-etc-directory))
(run-mode-hooks 'ess-julia-post-run-hook))
inf-buf))))
;;;###autoload
(defalias 'julia #'run-ess-julia)
(cl-defmethod ess--help-major-mode (&context (ess-dialect "julia"))
(ess-julia-help-mode))
(define-derived-mode ess-julia-help-mode ess-help-mode "ESS[Julia] Help"
"Major mode for Julia documentation."
:group 'ess-help
(let ((inhibit-read-only t))
;; Julia help buffers can contain color if julia starts with
;; --color=yes
(ansi-color-apply-on-region (point-min) (point-max))))
(add-to-list 'auto-mode-alist '("\\.jl\\'" . ess-julia-mode))
(provide 'ess-julia)
;;; ess-julia.el ends here