Permalink
Switch branches/tags
Find file
1510 lines (1220 sloc) 47.3 KB
;;; hy-mode.el --- Major mode for Hylang -*- lexical-binding: t -*-
;; Copyright © 2013 Julien Danjou <julien@danjou.info>
;; © 2017 Eric Kaschalk <ekaschalk@gmail.com>
;;
;; Authors: Julien Danjou <julien@danjou.info>
;; Eric Kaschalk <ekaschalk@gmail.com>
;; URL: http://github.com/hylang/hy-mode
;; Version: 1.0
;; Keywords: languages, lisp, python
;; Package-Requires: ((dash "2.13.0") (dash-functional "1.2.0") (s "1.11.0") (emacs "24"))
;; hy-mode 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, or (at your option)
;; any later version.
;;
;; hy-mode 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 hy-mode. If not, see <http://www.gnu.org/licenses/>.
;; This file is not part of GNU Emacs.
;;; Commentary:
;; Provides font-lock, indentation, and navigation for the Hy
;; language. (http://hylang.org)
(require 'cl)
(require 'dash)
(require 'dash-functional)
(require 's)
(defgroup hy-mode nil
"A mode for Hy"
:prefix "hy-mode-"
:group 'applications)
;;; Configuration
;;;; Inferior shell
(defconst hy-shell-interpreter "hy"
"Default Hy interpreter name.")
(defvar hy-shell-interpreter-args "--spy"
"Default arguments for Hy interpreter.")
(defvar hy-shell-use-control-codes? nil
"Append `--control-codes' flag to `hy-shell-interpreter-args'?
Requires recent version of Hy and `hy-shell-interpreter-args' to contain `--spy'.
Keep nil unless using specific Hy branch.")
(defvar hy-shell-spy-delim ""
"If using `--spy' interpreter arg then delimit spy ouput by this string.")
;;;; Highlighting
(defvar hy-font-lock-highlight-percent-args? t
"Whether to highlight '%i' symbols in Hy's clojure-like syntax for lambdas.")
;;;; Indentation
(defvar hy-indent-special-forms
'(:exact
("when" "unless"
"for" "for*"
"while"
"except" "catch")
:fuzzy
("def"
"with"
"fn"
"lambda"))
"Special forms to always indent following line by +1.
Fuzzy forms require match at start of symbol (eg. with-something)
will indent special. Exact forms require the symbol and def exactly match.")
;;; Syntax Table
(defconst hy-mode-syntax-table
(-let [table
(copy-syntax-table lisp-mode-syntax-table)]
(modify-syntax-entry ?\{ "(}" table)
(modify-syntax-entry ?\} "){" table)
(modify-syntax-entry ?\[ "(]" table)
(modify-syntax-entry ?\] ")[" table)
;; Quote characters are prefixes
(modify-syntax-entry ?\~ "'" table)
(modify-syntax-entry ?\@ "'" table)
;; "," is a symbol in Hy, namely the tuple constructor
(modify-syntax-entry ?\, "_ p" table)
;; "|" is a symbol in hy, naming the or operator
(modify-syntax-entry ?\| "_ p" table)
;; "#" is a tag macro, we include # in the symbol
(modify-syntax-entry ?\# "_ p" table)
table)
"Hy mode's syntax table.")
(defconst inferior-hy-mode-syntax-table
(copy-syntax-table hy-mode-syntax-table)
"Inferior Hy mode's syntax tables inherits Hy mode's table.")
;;; Keywords
(defconst hy--kwds-anaphorics
'("ap-if" "ap-each" "ap-each-while" "ap-map" "ap-map-when" "ap-filter"
"ap-reject" "ap-dotimes" "ap-first" "ap-last" "ap-reduce" "ap-pipe"
"ap-compose" "xi")
"Hy anaphoric contrib keywords.")
(defconst hy--kwds-builtins
'("*map" "accumulate" "and" "assoc" "butlast" "calling-module-name" "car"
"cdr" "chain" "coll?" "combinations" "comp" "complement" "compress" "cons"
"cons?" "constantly" "count" "cut" "cycle" "dec" "def" "defmain" "del"
"dict-comp" "disassemble" "distinct" "doto" "drop" "drop-last" "drop-while"
"empty?" "even?" "every?" "filter" "first" "flatten" "float?" "fraction"
"genexpr" "gensym" "get" "group-by" "identity" "inc" "input"
"instance?" "integer" "integer-char?" "integer?" "interleave" "interpose"
"is" "is-not" "is_not" "islice" "iterable?" "iterate" "iterator?" "juxt"
"keyword" "keyword?" "last" "list*" "list-comp" "macroexpand"
"macroexpand-1" "map" "merge-with" "multicombinations" "name" "neg?" "none?"
"nth" "numeric?" "odd?" "or" "partition" "permutations"
"pos?" "product" "quasiquote" "quote" "range" "read" "read-str"
"reduce" "remove" "repeat" "repeatedly" "rest" "second" "setv" "set-comp"
"slice" "some" "string" "string?" "symbol?" "take" "take-nth" "take-while"
"tee" "unquote" "unquote-splice" "xor" "zero?" "zip" "zip-longest"
;; Pure python builtins
"abs" "all" "any" "bin" "bool" "callable" "chr"
"compile" "complex" "delattr" "dict" "dir" "divmod" "enumerate"
"eval" "float" "format" "frozenset" "getattr" "globals" "hasattr"
"hash" "help" "hex" "id" "isinstance" "issubclass" "iter" "len"
"list" "locals" "max" "memoryview" "min" "next" "object" "oct" "open"
"ord" "pow" "repr" "reversed" "round" "set" "setattr"
"sorted" "str" "sum" "super" "tuple" "type" "vars"
"ascii" "bytearray" "bytes" "exec"
"--package--" "__package__" "--import--" "__import__"
"--all--" "__all__" "--doc--" "__doc__" "--name--" "__name__")
"Hy builtin keywords.")
(defconst hy--kwds-constants
'("True" "False" "None"
"Ellipsis"
"NotImplemented"
"nil" ; For those that alias None as nil
)
"Hy constant keywords.")
(defconst hy--kwds-exceptions
'("ArithmeticError" "AssertionError" "AttributeError" "BaseException"
"DeprecationWarning" "EOFError" "EnvironmentError" "Exception"
"FloatingPointError" "FutureWarning" "GeneratorExit" "IOError"
"ImportError" "ImportWarning" "IndexError" "KeyError"
"KeyboardInterrupt" "LookupError" "MemoryError" "NameError"
"NotImplementedError" "OSError" "OverflowError"
"PendingDeprecationWarning" "ReferenceError" "RuntimeError"
"RuntimeWarning" "StopIteration" "SyntaxError" "SyntaxWarning"
"SystemError" "SystemExit" "TypeError" "UnboundLocalError"
"UnicodeDecodeError" "UnicodeEncodeError" "UnicodeError"
"UnicodeTranslateError" "UnicodeWarning" "UserWarning" "VMSError"
"ValueError" "Warning" "WindowsError" "ZeroDivisionError"
"BufferError" "BytesWarning" "IndentationError" "ResourceWarning" "TabError")
"Hy exception keywords.")
(defconst hy--kwds-defs
'("defn" "defun"
"defmacro" "defmacro/g!" "defmacro!"
"defreader" "defsharp" "deftag")
"Hy definition keywords.")
(defconst hy--kwds-operators
'("!=" "%" "%=" "&" "&=" "*" "**" "**=" "*=" "+" "+=" "," "-"
"-=" "/" "//" "//=" "/=" "<" "<<" "<<=" "<=" "=" ">" ">=" ">>" ">>="
"^" "^=" "|" "|=" "~")
"Hy operator keywords.")
(defconst hy--kwds-special-forms
'(;; Looping
"loop" "recur"
"for" "for*"
;; Threading
"->" "->>" "as->"
;; Flow control
"return"
"if" "if*" "if-not" "lif" "lif-not"
"else" "unless" "when"
"break" "continue"
"while" "cond"
"do" "progn"
;; Functional
"lambda" "fn"
"yield" "yield-from"
"with" "with*"
"with-gensyms"
;; Error Handling
"except" "try" "throw" "raise" "catch" "finally" "assert"
;; Special
"print"
"not"
"in" "not-in"
;; Misc
"global" "nonlocal"
"eval" "eval-and-compile" "eval-when-compile"
;; Discontinued in Master
"apply" "kwapply")
"Hy special forms keywords.")
;;; Font Locks
;;;; Definitions
(defconst hy--font-lock-kwds-builtins
(list
(rx-to-string
`(: (not (any "#"))
symbol-start
(or ,@hy--kwds-operators
,@hy--kwds-builtins
,@hy--kwds-anaphorics)
symbol-end))
'(0 font-lock-builtin-face))
"Hy builtin keywords.")
(defconst hy--font-lock-kwds-constants
(list
(rx-to-string
`(: (or ,@hy--kwds-constants)))
'(0 font-lock-constant-face))
"Hy constant keywords.")
(defconst hy--font-lock-kwds-defs
(list
(rx-to-string
`(: (group-n 1 (or ,@hy--kwds-defs))
(1+ space)
(group-n 2 (1+ word))))
'(1 font-lock-keyword-face)
'(2 font-lock-function-name-face nil t))
"Hy definition keywords.")
(defconst hy--font-lock-kwds-exceptions
(list
(rx-to-string
`(: symbol-start
(or ,@hy--kwds-exceptions)
symbol-end))
'(0 font-lock-type-face))
"Hy exception keywords.")
(defconst hy--font-lock-kwds-special-forms
(list
(rx-to-string
`(: symbol-start
(or ,@hy--kwds-special-forms)
symbol-end))
'(0 font-lock-keyword-face))
"Hy special forms keywords.")
;;;; Static
(defconst hy--font-lock-kwds-aliases
(list
(rx (group-n 1 (or "defmacro-alias" "defn-alias" "defun-alias"))
(1+ space)
"["
(group-n 2 (1+ anything))
"]")
'(1 font-lock-keyword-face)
'(2 font-lock-function-name-face nil t))
"Hy aliasing keywords.")
(defconst hy--font-lock-kwds-class
(list
(rx (group-n 1 "defclass")
(1+ space)
(group-n 2 (1+ word)))
'(1 font-lock-keyword-face)
'(2 font-lock-type-face))
"Hy class keywords.")
(defconst hy--font-lock-kwds-decorators
(list
(rx
(or (: "#@"
(syntax open-parenthesis))
(: symbol-start
"with-decorator"
symbol-end
(1+ space)))
(1+ word))
'(0 font-lock-type-face))
"Hylight the symbol after `#@' or `with-decorator' macros.")
(defconst hy--font-lock-kwds-imports
(list
(rx (or "import" "require" ":as")
(or (1+ space) eol))
'(0 font-lock-keyword-face))
"Hy import keywords.")
(defconst hy--font-lock-kwds-self
(list
(rx symbol-start
(group "self")
(or "." symbol-end))
'(1 font-lock-keyword-face))
"Hy self keyword.")
(defconst hy--font-lock-kwds-tag-macros
(list
(rx "#"
(not (any "*" "@" "[")) ; #* is unpacking, #@ decorator, #[ bracket str
(0+ word))
'(0 font-lock-function-name-face))
"Hylight tag macros, ie. `#tag-macro', so they stand out.")
(defconst hy--font-lock-kwds-variables
(list
(rx symbol-start
(or "setv" "def")
symbol-end
(1+ space)
(group (1+ word)))
'(1 font-lock-variable-name-face))
"Hylight variable names in setv/def, only first name.")
;;;; Misc
(defconst hy--font-lock-kwds-anonymous-funcs
(list
(rx symbol-start "%" (1+ digit))
'(0 font-lock-variable-name-face))
"Hy '#%(print %1 %2)' styling anonymous variables.")
(defconst hy--font-lock-kwds-func-modifiers
(list
(rx symbol-start "&" (1+ word))
'(0 font-lock-type-face))
"Hy '&rest/&kwonly/...' styling.")
(defconst hy--font-lock-kwds-kwargs
(list
(rx symbol-start ":" (1+ word))
'(0 font-lock-constant-face))
"Hy ':kwarg' styling.")
(defconst hy--font-lock-kwds-shebang
(list
(rx buffer-start "#!" (0+ not-newline) eol)
'(0 font-lock-comment-face))
"Hy shebang line.")
(defconst hy--font-lock-kwds-unpacking
(list
(rx (or "#*" "#**")
symbol-end)
'(0 font-lock-keyword-face))
"Hy #* arg and #** kwarg unpacking keywords.")
;;;; Grouped
(defconst hy-font-lock-kwds
(list hy--font-lock-kwds-aliases
hy--font-lock-kwds-builtins
hy--font-lock-kwds-class
hy--font-lock-kwds-constants
hy--font-lock-kwds-defs
hy--font-lock-kwds-decorators
hy--font-lock-kwds-exceptions
hy--font-lock-kwds-func-modifiers
hy--font-lock-kwds-imports
hy--font-lock-kwds-kwargs
hy--font-lock-kwds-self
hy--font-lock-kwds-shebang
hy--font-lock-kwds-special-forms
hy--font-lock-kwds-tag-macros
hy--font-lock-kwds-unpacking
hy--font-lock-kwds-variables
(when hy-font-lock-highlight-percent-args?
hy--font-lock-kwds-anonymous-funcs)
)
"All Hy font lock keywords.")
;;; Utilities
;;;; Sexp Navigation
;; Aliases for `parse-partial-sexp' value
(defun hy--sexp-inermost-char (state)
(nth 1 state))
(defun hy--start-of-last-sexp (state)
(nth 2 state))
(defun hy--in-string? (state)
(nth 3 state))
(defun hy--start-of-string (state)
(nth 8 state))
(defun hy--prior-sexp? (state)
(number-or-marker-p (hy--start-of-last-sexp state)))
;;;; General purpose
(defun hy--str-or-nil (text)
"If TEXT is non-blank, return TEXT else nil."
(and (not (s-blank? text)) text))
(defun hy--str-or-empty (text)
"Return TEXT or the empty string it TEXT is nil."
(if text text ""))
(defun hy--current-form-string ()
"Get form containing current point as string."
(save-excursion
(-when-let* ((state (syntax-ppss))
(start-pos (hy--sexp-inermost-char state)))
(goto-char start-pos)
(while (ignore-errors (forward-sexp)))
(concat (buffer-substring-no-properties start-pos (point)) "\n"))))
;;; Indentation
;;;; Utilities
(defun hy--anything-before? (pos)
"Determine if chars before POS in current line."
(s-matches? (rx (not blank))
(buffer-substring (line-beginning-position) pos)))
(defun hy--anything-after? (pos)
"Determine if POS is before line-end-position."
(when pos
(< pos (line-end-position))))
(defun hy--check-non-symbol-sexp (pos)
"Check for a non-symbol yet symbol-like (tuple constructor comma) at POS."
(member (char-after pos) '(?\, ?\|)))
;;;; Normal Indent
(defun hy--normal-indent (last-sexp)
"Determine normal indentation column of LAST-SEXP.
Example:
(a (b c d
e
f))
1. Indent e => start at d -> c -> b.
Then backwards-sexp will throw error trying to jump to a.
Observe 'a' need not be on the same line as the ( will cause a match.
Then we determine indentation based on whether there is an arg or not.
2. Indenting f will go to e.
Now since there is a prior sexp d but we have no sexps-before on same line,
the loop will terminate without error and the prior lines indentation is it."
(goto-char last-sexp)
(-let [last-sexp-start nil]
(if (ignore-errors
(while (hy--anything-before? (point))
(setq last-sexp-start (prog1
;; Indentation should ignore quote chars
(if (-contains? '(?\' ?\` ?\~ ?\#)
(char-before))
(1- (point))
(point))
(backward-sexp))))
t)
(current-column)
(if (not (hy--anything-after? last-sexp-start))
(1+ (current-column))
(goto-char last-sexp-start) ; Align with function argument
(current-column)))))
;;;; Function or form
(defun hy--not-function-form-p ()
"Non-nil if form at point doesn't represent a function call."
(or (-contains? '(?\[ ?\{) (char-after))
(not (looking-at (rx anything ; Skips form opener
(or "(" (syntax symbol) (syntax word)))))))
;;;; Hy find indent spec
(defun hy--find-indent-spec (state)
"Return integer for special indentation of form or nil to use normal indent.
Note that `hy--not-function-form-p' filters out forms that are lists and dicts.
Point is always at the start of a function."
(-when-let
(function (and (hy--prior-sexp? state)
(thing-at-point 'symbol)))
(or (-contains? (plist-get hy-indent-special-forms :exact)
function)
(-some (-cut s-matches? <> function)
(plist-get hy-indent-special-forms :fuzzy)))))
;;;; Hy indent function
(defun hy-indent-function (indent-point state)
"Indent at INDENT-POINT where STATE is `parse-partial-sexp' for INDENT-POINT."
(goto-char (hy--sexp-inermost-char state))
(if (hy--not-function-form-p)
(1+ (current-column)) ; Indent after [, {, ... is always 1
(forward-char 1) ; Move to start of sexp
(cond ((hy--check-non-symbol-sexp (point)) ; Comma tuple constructor
(+ 2 (current-column)))
((hy--find-indent-spec state) ; Special form uses fixed indendation
(1+ (current-column)))
(t
(hy--normal-indent calculate-lisp-indent-last-sexp)))))
;;; Bracket String Literals
(defun hy--match-bracket-string (limit)
"Search forward for a bracket string literal."
(re-search-forward
(rx "#["
(0+ not-newline)
"["
(group (1+ (not (any "]"))))
"]"
(0+ not-newline)
"]")
limit
t))
(defun hy-syntax-propertize-function (start end)
"Implements context sensitive syntax highlighting beyond `font-lock-keywords'.
In particular this implements bracket string literals.
START and END are the limits with which to search for bracket strings passed
and determined by `font-lock-mode' internals when making an edit to a buffer."
(save-excursion
(goto-char start)
;; Start goes to current line, need to go to start of #[[ block
(when (nth 1 (syntax-ppss)) ; when inermost-char go to [ => [ => #
(goto-char (- (hy--sexp-inermost-char (syntax-ppss)) 2)))
(while (hy--match-bracket-string end)
(put-text-property (1- (match-beginning 1)) (match-beginning 1)
'syntax-table (string-to-syntax "|"))
(put-text-property (match-end 1) (1+ (match-end 1))
'syntax-table (string-to-syntax "|")))))
;;; Font Lock Syntactics
(defun hy--string-in-doc-position? (state)
"Is STATE within a docstring?"
(if (= 1 (hy--start-of-string state)) ; Identify module docstring
t
(-when-let* ((first-sexp (hy--sexp-inermost-char state))
(function (save-excursion
(goto-char (1+ first-sexp))
(thing-at-point 'symbol))))
(s-matches? (rx "def" (not blank)) function)))) ; "def"=="setv"
(defun hy-font-lock-syntactic-face-function (state)
"Return syntactic face function for the position represented by STATE.
STATE is a `parse-partial-sexp' state, and the returned function is the
Lisp font lock syntactic face function. String is shorthand for either
a string or comment."
(if (hy--in-string? state)
(if (hy--string-in-doc-position? state)
font-lock-doc-face
font-lock-string-face)
font-lock-comment-face))
;;; Shell Integration
;;;; Configuration
(defconst hy-shell-buffer-name "Hy"
"Default buffer name for Hy interpreter.")
(defconst hy-shell-internal-buffer-name "Hy Internal"
"Default buffer name for the internal Hy process.")
(defvar hy-shell-buffer nil
"The current shell buffer for Hy.")
(defvar hy--shell-output-filter-in-progress nil
"Whether we are waiting for output in `hy-shell-send-string-no-output'.")
(defvar hy--shell-font-lock-enable t
"Whether the shell should font-lock the current line.")
(defconst hy--shell-spy-delim-uuid "#cbb4fcbe-b6ba-4812-afa3-4a5ac7b20501"
"UUID denoting end of python block in `--spy --control-categories' output")
;;;; Shell buffer utilities
(defun hy--shell-format-process-name (proc-name)
"Format a PROC-NAME with closing astericks."
(->> proc-name (s-prepend "*") (s-append "*")))
(defun hy-shell-get-process (&optional internal)
"Get process corr. to `hy-shell-buffer-name'/`hy-shell-internal-buffer-name'."
(-> (if internal hy-shell-internal-buffer-name hy-shell-buffer-name)
hy--shell-format-process-name
get-buffer-process))
(defun hy--shell-current-buffer-process ()
"Get process associated with current buffer."
(get-buffer-process (current-buffer)))
(defun hy--shell-current-buffer-a-process? ()
"Is `current-buffer' a live process?"
(process-live-p (hy--shell-current-buffer-process)))
(defun hy--shell-get-or-create-buffer ()
"Get or create `hy-shell-buffer' buffer for current hy shell process."
(if hy-shell-buffer
hy-shell-buffer
(hy--shell-with-shell-buffer
(-let [proc-name
(process-name (hy--shell-current-buffer-process))]
(generate-new-buffer proc-name)))))
(defun hy--shell-buffer? ()
"Is `hy-shell-buffer' set and does it exist?"
(and hy-shell-buffer
(buffer-live-p hy-shell-buffer)))
(defun hy--shell-kill-buffer ()
"Kill `hy-shell-buffer'."
(when (hy--shell-buffer?)
(kill-buffer hy-shell-buffer)
(when (derived-mode-p 'inferior-hy-mode)
(setq hy-shell-buffer nil))))
;;;; Shell macros
(defmacro hy--shell-with-shell-buffer (&rest forms)
"Execute FORMS in the shell buffer."
(-let [shell-process
(gensym)]
`(-let [,shell-process
(hy-shell-get-process)]
(with-current-buffer (process-buffer ,shell-process)
,@forms))))
(defmacro hy--shell-with-font-locked-shell-buffer (&rest forms)
"Execute FORMS in the shell buffer with font-lock turned on."
`(hy--shell-with-shell-buffer
(save-current-buffer
(unless (hy--shell-buffer?)
(setq hy-shell-buffer (hy--shell-get-or-create-buffer)))
(set-buffer hy-shell-buffer)
(unless (font-lock-mode) (font-lock-mode 1))
(unless (derived-mode-p 'hy-mode) (hy-mode))
,@forms)))
;;;; Font locking
(defun hy--shell-faces-to-font-lock-faces (text &optional start-pos)
"Set all 'face in TEXT to 'font-lock-face optionally starting at START-POS."
(let ((pos 0)
(start-pos (or start-pos 0)))
(while (and (/= pos (length text))
(setq next (next-single-property-change pos 'face text)))
(let* ((plist (text-properties-at pos text))
(plist (-if-let (face (plist-get plist 'face))
(progn (plist-put plist 'face nil) ; Swap face
(plist-put plist 'font-lock-face face))
plist)))
(set-text-properties (+ start-pos pos) (+ start-pos next) plist)
(setq pos next)))))
(defun hy--shell-fontify-prompt-post-command-hook ()
"Fontify just the current line in `hy-shell-buffer' for `post-command-hook'.
Constantly extracts current prompt text and executes and manages applying
`hy--shell-faces-to-font-lock-faces' to the text."
(-when-let* (((_ . prompt-end) comint-last-prompt)
(_ (and prompt-end
(> (point) prompt-end) ; new command is being entered
(hy--shell-current-buffer-a-process?)))) ; process alive?
(let* ((input (buffer-substring-no-properties prompt-end (point-max)))
(deactivate-mark nil)
(buffer-undo-list t)
(font-lock-buffer-pos nil)
(text (hy--shell-with-font-locked-shell-buffer
(delete-region (line-beginning-position) (point-max))
(setq font-lock-buffer-pos (point))
(insert input)
(font-lock-ensure)
(buffer-substring font-lock-buffer-pos (point-max)))))
(hy--shell-faces-to-font-lock-faces text prompt-end))))
(defun hy--shell-font-lock-turn-on ()
"Turn on fontification of current line for hy shell."
(hy--shell-with-shell-buffer
(hy--shell-kill-buffer)
;; Required - I don't understand why killing doesn't capture the end nil setq
(setq-local hy-shell-buffer nil)
(add-hook 'post-command-hook
'hy--shell-fontify-prompt-post-command-hook nil 'local)
(add-hook 'kill-buffer-hook
'hy--shell-kill-buffer nil 'local)))
(defun hy--shell-font-lock-spy-output (string)
"Applies font-locking to hy outputted python blocks when `--spy' is enabled."
(with-temp-buffer
(if (s-contains? hy--shell-spy-delim-uuid string)
(-let ((python-indent-guess-indent-offset
nil)
((python-block hy-output)
(s-split hy--shell-spy-delim-uuid string)))
(python-mode)
(insert python-block)
(font-lock-default-fontify-buffer)
(-> (buffer-string)
hy--shell-faces-to-font-lock-faces
s-chomp
(s-concat hy-shell-spy-delim hy-output)))
string)))
;;;; Send strings
(defun hy--shell-end-of-output? (string)
"Return non-nil if STRING ends with the prompt."
(s-matches? comint-prompt-regexp string))
(defun hy--shell-output-filter (string)
"If STRING ends with input prompt then set filter in progress done."
(when (hy--shell-end-of-output? string)
(setq hy--shell-output-filter-in-progress nil))
"\n=> ")
(defun hy--shell-send-string (string &optional process internal)
"Internal implementation of shell send string functionality."
(let ((process (or process (hy-shell-get-process internal)))
(hy--shell-output-filter-in-progress t))
(comint-send-string process string)
(while hy--shell-output-filter-in-progress
(accept-process-output process))))
(defun hy-shell-send-string-no-output (string &optional process internal)
"Send STRING to hy PROCESS and inhibit printing output."
(-let [comint-preoutput-filter-functions
'(hy--shell-output-filter)]
(hy--shell-send-string string process internal)))
(defun hy-shell-send-string-internal (string)
"Send STRING to internal hy shell process."
(hy-shell-send-string-no-output string nil 'internal))
(defun hy--shell-send-internal-setup-code ()
"Send setup code for autocompletion and eldoc to hy internal process."
(hy-shell-send-string-internal (concat hy-eldoc-setup-code
hy-company-setup-code)))
(defun hy-shell-send-string (string &optional process)
"Send STRING to hy PROCESS."
(-let [comint-output-filter-functions
'(hy--shell-output-filter)]
(hy--shell-send-string string process)))
(defun hy--shell-send-async (string)
"Send STRING to internal hy process asynchronously."
(let ((output-buffer " *Comint Hy Redirect Work Buffer*")
(proc (hy-shell-get-process 'internal)))
(with-current-buffer (get-buffer-create output-buffer)
(erase-buffer)
(comint-redirect-send-command-to-process string output-buffer proc nil t)
(set-buffer (process-buffer proc))
(while (and (null comint-redirect-completed)
(accept-process-output proc nil 100 t)))
(set-buffer output-buffer)
(buffer-string))))
;;;; Update internal process
(defun hy--shell-update-imports ()
"Send imports/requires to the current internal process.
This is currently done manually as I'm not sure of the consequences of doing
so automatically through eg. regular intervals. Sending the imports allows
Eldoc to function (and will allow autocompletion once modules are completed).
Right now the keybinding is not publically exposed."
(interactive)
(save-excursion
(goto-char (point-min))
(while (re-search-forward (rx "(" (0+ space) (or "import" "require")) nil t)
(hy-shell-send-string-internal (hy--current-form-string)))))
;;;; Shell creation
(defun hy--shell-calculate-interpreter-args ()
"Calculate `hy-shell-interpreter-args' based on `--spy' flag."
(if (and hy-shell-use-control-codes?
(s-contains? "--spy" hy-shell-interpreter-args))
(s-concat hy-shell-interpreter-args " --control-codes")
hy-shell-interpreter-args))
(defun hy--shell-calculate-command (&optional internal)
"Calculate the string used to execute the inferior Hy process."
(format "%s %s"
(shell-quote-argument hy-shell-interpreter)
(if internal
""
(hy--shell-calculate-interpreter-args))))
(defun hy--shell-make-comint (cmd proc-name &optional show internal)
"Create and return comint process PROC-NAME with CMD, opt. INTERNAL and SHOW."
(-when-let* ((proc-buffer-name
(hy--shell-format-process-name proc-name))
(_
(not (comint-check-proc proc-buffer-name)))
(cmdlist
(split-string-and-unquote cmd))
(buffer
(apply 'make-comint-in-buffer proc-name proc-buffer-name
(car cmdlist) nil (cdr cmdlist)))
(process
(get-buffer-process buffer)))
(with-current-buffer buffer
(inferior-hy-mode))
(when show
(display-buffer buffer))
(if internal
(set-process-query-on-exit-flag process nil)
(setq hy-shell-buffer buffer))
proc-buffer-name))
;;;; Run Shell
(defun run-hy-internal ()
"Start an inferior hy process in the background for autocompletion."
(interactive)
(unless (executable-find "hy")
(message "Hy not found, activate a virtual environment containing Hy to use
Eldoc, Anaconda, and other hy-mode features."))
(when (and (not (hy-shell-get-process 'internal))
(executable-find "hy"))
(-let [hy--shell-font-lock-enable
nil]
(prog1
(-> (hy--shell-calculate-command 'internal)
(hy--shell-make-comint hy-shell-internal-buffer-name nil 'internal)
get-buffer-process)
(hy--shell-send-internal-setup-code)
(message "Hy internal process successfully started")))))
(defun run-hy (&optional cmd)
"Run an inferior Hy process.
CMD defaults to the result of `hy--shell-calculate-command'."
(interactive)
(unless (executable-find "hy")
(message "Hy not found, activate a virtual environment with Hy."))
(-> (or cmd (hy--shell-calculate-command))
(hy--shell-make-comint hy-shell-buffer-name 'show)
get-buffer-process))
;;; Eldoc
;;;; Setup Code
(defconst hy-eldoc-setup-code
"(import builtins)
(import inspect)
(import [hy.macros [-hy-macros]])
(defn --HYDOC-format-argspec [argspec]
\"Lispy version of format argspec covering all defun kwords.\"
(setv docs \"\")
(defmacro add-docs [&rest forms]
`(do (when docs (setv docs (+ docs \" \")))
(setv docs (+ docs ~@forms))))
(defn format-args [&rest args]
(->> args
(map (fn [x] (-> x str (.replace \"_\" \"-\"))))
(.join \" \")))
(setv args argspec.args)
(setv defaults argspec.defaults)
(setv varargs argspec.varargs)
(setv varkw argspec.varkw)
(setv kwonlyargs argspec.kwonlyargs)
(setv kwonlydefaults argspec.kwonlydefaults)
(when (and args defaults)
(setv args (-> argspec.defaults
len
(drop-last argspec.args)
list))
(setv defaults (-> args
len
(drop argspec.args)
list)))
(when (and kwonlyargs kwonlydefaults)
(setv kwonlyargs (->> kwonlyargs
(remove (fn [x] (in x (.keys kwonlydefaults))))
list))
(setv kwonlydefaults (->> kwonlydefaults
(.items)
(*map (fn [k v] (.format \"[{} {}]\" k v)))
list)))
(when args
(add-docs (format-args #* args)))
(when defaults
(add-docs \"&optional \"
(format-args #* defaults)))
(when varargs
(add-docs \"&rest \"
(format-args varargs)))
(when varkw
(add-docs \"&kwargs \"
(format-args varkw)))
(when kwonlyargs
(add-docs \"&kwonly \"
(format-args #* kwonlyargs)))
(when kwonlydefaults
(add-docs (if-not kwonlyargs \"&kwonly \" \"\")
(format-args #* kwonlydefaults)))
docs)
(defn --HYDOC-format-eldoc-string [obj-name f &optional full]
\"Format an obj name for callable f.\"
(if f.--doc--
(.format \"{obj}: ({args}){docs}\"
:obj (.replace obj-name \"_\" \"-\")
:args (--HYDOC-format-argspec (inspect.getfullargspec f))
:docs (if full
(->> f.--doc-- (.splitlines) (.join \"\n\") (+ \"\n\"))
(+ \" - \" (->> f.--doc-- (.splitlines) first))))
(.format \"{obj}: ({args})\"
:obj (.replace obj-name \"_\" \"-\")
:args (--HYDOC-format-argspec (inspect.getfullargspec f)))))
(defn --HYDOC-python-eldoc [obj &optional full]
\"Build eldoc string for python obj or string.
Not all defuns can be argspeced - eg. C defuns.\"
(try
(do (when (isinstance obj str)
(setv obj (.eval builtins obj (globals))))
(setv full-doc (.getdoc inspect obj))
(setv doc full-doc)
(try
(setv doc (--HYDOC-format-eldoc-string obj.--name-- obj
:full full))
(except [e TypeError]
(setv doc (->> doc (.splitlines) first (+ \"builtin: \")))
(when full
(setv doc (+ doc \"\n\"
(->> full-doc (.splitlines) rest (.join \"\"))))))))
(except [e Exception]
(setv doc \"\")))
doc)
(defn --HYDOC-macro-eldoc [obj &optional full]
\"Get eldoc string for a macro.\"
(try
(do (setv obj (.replace obj \"-\" \"_\"))
(setv macros (get -hy-macros None))
(when (in obj macros)
(--HYDOC-format-eldoc-string obj (get macros obj) :full full)))
(except [e Exception] \"\")))
(defn --HYDOC [obj &optional full]
\"Get eldoc string for any obj.\"
(setv doc (--HYDOC-python-eldoc obj :full full))
(unless doc (setv doc (--HYDOC-macro-eldoc obj :full full)))
doc)"
"Symbol introspection code to send to the internal process for eldoc.")
;;;; Utilities
(defun hy--eldoc-chomp-output (text)
"Chomp prefixes and suffixes from eldoc process output."
(->> text
(s-chop-suffixes '("\n=> " "=> " "\n"))
(s-chop-prefixes '("\"" "'" "\"'" "'\""))
(s-chop-suffixes '("\"" "'" "\"'" "'\""))))
(defun hy--eldoc-remove-syntax-errors (text)
"Quick fix to address parsing an incomplete dot-dsl."
(if (< 1 (-> text s-lines length))
""
text))
(defun hy--eldoc-send (string)
"Send STRING for eldoc to internal process returning output."
(-> string
hy--shell-send-async
hy--eldoc-chomp-output
hy--eldoc-remove-syntax-errors
hy--str-or-nil))
(defun hy--eldoc-format-command (symbol &optional full raw)
"Inspect SYMBOL with hydoc, optionally include FULL docs for a buffer."
(format "(try (--HYDOC %s :full %s) (except [e Exception] (str)))"
(if raw symbol (s-concat "\"" symbol "\""))
(if full "True" "False")))
(defun hy--eldoc-get-inner-symbol ()
"Traverse and inspect innermost sexp and return formatted string for eldoc."
(save-excursion
(-when-let* ((_ (hy-shell-get-process 'internal))
(state (syntax-ppss))
(start-pos (hy--sexp-inermost-char state))
(_ (progn (goto-char start-pos)
(not (hy--not-function-form-p))))
(function (progn (forward-char)
(thing-at-point 'symbol))))
;; Attribute method call (eg. ".format str") needs following sexp
(if (s-starts-with? "." function)
(when (ignore-errors (forward-sexp) (forward-char) t)
(pcase (char-after)
;; Can't send just .method to eldoc
(?\) (setq function nil))
(?\s (setq function nil))
(?\C-j (setq function nil)) ; newline
;; Dot dsl doesn't work on literals
(?\[ (concat "list" function))
(?\{ (concat "dict" function))
(?\" (concat "str" function))
;; Otherwise complete the dot dsl
(_ (progn
(forward-char)
(concat (thing-at-point 'symbol) function)))))
function))))
(defun hy--fontify-text (text regexp &rest faces)
"Fontify portions of TEXT matching REGEXP with FACES."
(when text
(-each
(s-matched-positions-all regexp text)
(-lambda ((beg . end))
(--each faces
(add-face-text-property beg end it nil text))))))
(defun hy--eldoc-fontify-text (text)
"Fontify eldoc TEXT."
(let ((kwd-rx
(rx string-start (1+ (not (any space ":"))) ":"))
(kwargs-rx
(rx symbol-start "&" (1+ word)))
(quoted-args-rx
(rx "`" (1+ (not space)) "`")))
(hy--fontify-text
text kwd-rx 'font-lock-keyword-face)
(hy--fontify-text
text kwargs-rx 'font-lock-type-face)
(hy--fontify-text
text quoted-args-rx 'font-lock-constant-face 'bold-italic))
text)
;;;; Documentation Functions
(defun hy--eldoc-get-docs (obj &optional full)
"Get eldoc or optionally buffer-formatted docs for `obj'."
(when obj
(hy--eldoc-fontify-text
(or (-> obj (hy--eldoc-format-command full) hy--eldoc-send)
(-> obj (hy--eldoc-format-command full t) hy--eldoc-send)))))
(defun hy-eldoc-documentation-function ()
"Drives `eldoc-mode', retrieves eldoc msg string for inner-most symbol."
(-> (hy--eldoc-get-inner-symbol)
hy--eldoc-get-docs))
;;; Describe thing at point
(defun hy--docs-for-thing-at-point ()
"Mirrors `hy-eldoc-documentation-function' formatted for a buffer, not a msg."
(-> (thing-at-point 'symbol)
(hy--eldoc-get-docs t)
hy--format-docs-for-buffer))
(defun hy--format-docs-for-buffer (text)
"Format raw hydoc TEXT for inserting into hyconda buffer."
(when text
(-let [kwarg-newline-regexp
(rx ","
(1+ (not (any "," ")")))
(group-n 1 "\\\n")
(1+ (not (any "," ")"))))]
(--> text
(s-replace "\\n" "\n" it)
(replace-regexp-in-string kwarg-newline-regexp "newline" it nil t 1)))))
(defun hy-describe-thing-at-point ()
"Implement shift-k docs lookup for `spacemacs/evil-smart-doc-lookup'."
(interactive)
(-when-let* ((text (hy--docs-for-thing-at-point))
(doc-buffer "*Hyconda*"))
(with-current-buffer (get-buffer-create doc-buffer)
(erase-buffer)
(switch-to-buffer-other-window doc-buffer)
(insert text)
(goto-char (point-min))
(forward-line)
(insert "------\n")
(fill-region (point) (point-max))
;; Eventually make hyconda-view-minor-mode, atm this is sufficient
(local-set-key "q" 'quit-window)
(when (fboundp 'evil-local-set-key)
(evil-local-set-key 'normal "q" 'quit-window)))))
;;; Autocompletion
(defconst hy-company-setup-code
"(import builtins)
(import hy)
(import [hy.lex.parser [hy-symbol-unmangle hy-symbol-mangle]])
(import [hy.macros [-hy-macros]])
(import [hy.compiler [-compile-table]])
(import [hy.core.shadow [*]])
(import [hy.core.language [*]])
(defn --HYCOMPANY-get-obj [text]
(when (in \".\" text)
(.join \".\" (-> text (.split \".\") butlast))))
(defn --HYCOMPANY-get-attr [text]
(if (in \".\" text)
(-> text (.split \".\") last)
text))
(defn --HYCOMPANY-get-obj-candidates [obj]
(try
(->> obj builtins.eval dir (map hy-symbol-unmangle) list)
(except [e Exception]
[])))
(defn --HYCOMPANY-get-macros []
\"Extract macro names from all namespaces and compile-table symbols.\"
(->> -hy-macros
(.values)
(map dict.keys)
(chain -compile-table)
flatten
(map --HYCOMPANY-get-name)
(map hy-symbol-unmangle)
distinct
list))
(defn --HYCOMPANY-get-global-candidates []
(->> (globals)
(.keys)
(map hy-symbol-unmangle)
(chain (--HYCOMPANY-get-macros))
flatten
distinct
list))
(defn --HYCOMPANY-get-name [x]
\"Return the candidate name for x.\"
(if (isinstance x str)
x
x.--name--))
(defn --HYCOMPANY-trim-candidates [candidates attr]
\"Limit list of candidates to those starting with attr.\"
(list (filter (fn [cand] (.startswith cand attr)) candidates)))
(defn --HYCOMPANY [text]
(setv obj (--HYCOMPANY-get-obj text))
(setv attr (--HYCOMPANY-get-attr text))
(if obj
(setv candidates (--HYCOMPANY-get-obj-candidates obj))
(setv candidates (--HYCOMPANY-get-global-candidates)))
(setv choices (--HYCOMPANY-trim-candidates candidates attr))
(if obj
(list (map (fn [x] (+ obj \".\" x))
choices))
choices))
(defn --HYANNOTATE-search-builtins [text]
(setv text (hy-symbol-mangle text))
(try
(do (setv obj (builtins.eval text))
(setv obj-name obj.--class--.--name--)
(cond [(in obj-name [\"function\" \"builtin_function_or_method\"])
\"def\"]
[(= obj-name \"type\")
\"class\"]
[(= obj-name \"module\")
\"module\"]
[True \"instance\"]))
(except [e Exception]
None)))
(defn --HYANNOTATE-search-compiler [text]
(in text -compile-table))
(defn --HYANNOTATE-search-shadows [text]
(->> hy.core.shadow
dir
(map hy-symbol-unmangle)
(in text)))
(defn --HYANNOTATE-search-macros [text]
(setv text (hy-symbol-mangle text))
(for [macro-dict (.values -hy-macros)]
(when (in text macro-dict)
(return (get macro-dict text))))
None)
(defn --HYANNOTATE [x]
;; only builtins format on case basis
(setv annotation (--HYANNOTATE-search-builtins x))
(when (and (not annotation) (--HYANNOTATE-search-shadows x))
(setv annotation \"shadowed\"))
(when (and (not annotation) (--HYANNOTATE-search-compiler x))
(setv annotation \"compiler\"))
(when (and (not annotation) (--HYANNOTATE-search-macros x))
(setv annotation \"macro\"))
(if annotation
(.format \"<{} {}>\" annotation x)
\"\"))"
"Autocompletion setup code to send to the internal process.")
(defconst hy--company-regexp
(rx "'"
(group (1+ (not (any ",]"))))
"'"
(any "," "]"))
"Regex to extra candidates from --HYCOMPANY.")
(defun hy--company-format-str (string)
"Format STRING to send to hy for completion candidates."
(when string
(format "(--HYCOMPANY \"%s\")" string)))
(defun hy--company-format-annotate-str (string)
"Format STRING to send to hy for its annotation."
(when string
(format "(--HYANNOTATE \"%s\")" string)))
(defun hy--company-annotate (candidate)
"Get company annotation for CANDIDATE string."
(-when-let* ((command (hy--company-format-annotate-str candidate))
(annotation (hy--shell-send-async command)))
(->> annotation
s-chomp
(s-chop-prefix "'")
(s-chop-suffix "'"))))
(defun hy--company-candidates (string)
"Get candidates for completion of STRING."
(-when-let* ((command (hy--company-format-str string))
(_ (not (s-starts-with? "." string))) ; dot dsl not impl yet
(candidates (hy--shell-send-async command))
(matches (s-match-strings-all hy--company-regexp candidates)))
(-select-column 1 matches))) ; Get match-data-1 for each match
(defun company-hy (command &optional arg &rest ignored)
(interactive (list 'interactive))
(cl-case command
(prefix (company-grab-symbol))
(candidates (hy--company-candidates arg))
(annotation (-> arg hy--company-annotate))
(meta (-> arg hy--eldoc-get-docs hy--str-or-empty))))
;;; Keybindings
;;;###autoload
(defun hy-insert-pdb ()
"Import and set pdb trace at point."
(interactive)
(insert "(do (import pdb) (pdb.set-trace))"))
;;;###autoload
(defun hy-insert-pdb-threaded ()
"Import and set pdb trace at point for a threading macro."
(interactive)
(insert "((fn [x] (import pdb) (pdb.set-trace) x))"))
;;;###autoload
(defun hy-shell-start-or-switch-to-shell ()
(interactive)
(if (hy--shell-buffer?)
(switch-to-buffer-other-window
(hy--shell-get-or-create-buffer))
(run-hy)))
;;;###autoload
(defun hy-shell-eval-buffer ()
"Send the buffer to the shell, inhibiting output."
(interactive)
(-let [text
(buffer-string)]
(unless (hy--shell-buffer?)
(hy-shell-start-or-switch-to-shell))
(hy--shell-with-shell-buffer
(hy-shell-send-string-no-output text))))
;;;###autoload
(defun hy-shell-eval-region ()
"Send highlighted region to shell, inhibiting output."
(interactive)
(when (and (region-active-p)
(not (region-noncontiguous-p)))
(-let [text
(buffer-substring (region-beginning) (region-end))]
(unless (hy--shell-buffer?)
(hy-shell-start-or-switch-to-shell))
(hy--shell-with-shell-buffer
(hy-shell-send-string-no-output text)))))
;;;###autoload
(defun hy-shell-eval-current-form ()
"Send form containing current point to shell."
(interactive)
(-when-let (text (hy--current-form-string))
(unless (hy--shell-buffer?)
(hy-shell-start-or-switch-to-shell))
(hy--shell-with-shell-buffer
(hy-shell-send-string text))))
;;; hy-mode and inferior-hy-mode
;;;; Hy-mode setup
(defun hy--mode-setup-eldoc ()
(make-local-variable 'eldoc-documentation-function)
(setq-local eldoc-documentation-function 'hy-eldoc-documentation-function)
(eldoc-mode +1))
(defun hy--mode-setup-font-lock ()
(setq-local font-lock-multiline t)
(setq font-lock-defaults
'(hy-font-lock-kwds
nil nil
(("+-*/.<>=!?$%_&~^:@" . "w")) ; syntax alist
nil
(font-lock-mark-block-function . mark-defun)
(font-lock-syntactic-face-function ; Differentiates (doc)strings
. hy-font-lock-syntactic-face-function))))
(defun hy--mode-setup-inferior ()
;; (add-to-list 'company-backends 'company-hy)
(setenv "PYTHONIOENCODING" "UTF-8")
(run-hy-internal)
(add-hook 'pyvenv-post-activate-hooks 'run-hy-internal nil t))
(defun hy--mode-setup-syntax ()
;; Bracket string literals require context sensitive highlighting
(setq-local syntax-propertize-function 'hy-syntax-propertize-function)
;; AutoHighlightSymbol needs adjustment for symbol recognition
(setq-local ahs-include "^[0-9A-Za-z/_.,:;*+=&%|$#@!^?-~\-]+$")
;; Lispy comment syntax
(setq-local comment-start ";")
(setq-local comment-start-skip
"\\(\\(^\\|[^\\\\\n]\\)\\(\\\\\\\\\\)*\\)\\(;+\\|#|\\) *")
(setq-local comment-add 1)
;; Lispy indent with hy-specialized indentation
(setq-local indent-tabs-mode nil)
(setq-local indent-line-function 'lisp-indent-line)
(setq-local lisp-indent-function 'hy-indent-function))
(defun hy--mode-setup-smartparens ()
"Setup smartparens, if active, pairs for Hy."
(when (fboundp 'sp-local-pair)
(sp-local-pair '(hy-mode) "`" "`" :actions nil)
(sp-local-pair '(hy-mode) "'" "'" :actions nil)
(sp-local-pair '(inferior-hy-mode) "`" "" :actions nil)
(sp-local-pair '(inferior-hy-mode) "'" "" :actions nil)))
;;;; Inferior-hy-mode setup
(defun hy--inferior-mode-setup ()
;; Comint config
(setq mode-line-process '(":%s"))
(setq-local indent-tabs-mode nil)
(setq-local comint-prompt-read-only t)
(setq-local comint-prompt-regexp (rx bol "=>" space))
;; Highlight errors according to colorama python package
(ansi-color-for-comint-mode-on)
(setq-local comint-output-filter-functions '(ansi-color-process-output))
;; Don't startup font lock for internal processes
(when hy--shell-font-lock-enable
(if (fboundp 'xterm-color-filter)
(setq-local comint-preoutput-filter-functions
`(xterm-color-filter hy--shell-font-lock-spy-output))
(setq-local comint-preoutput-filter-functions
`(hy--shell-font-lock-spy-output)))
(hy--shell-font-lock-turn-on))
;; Fixes issue with "=>", no side effects from this advice
(advice-add 'comint-previous-input :before
(lambda (&rest args) (setq-local comint-stored-incomplete-input ""))))
;;; Core
(add-to-list 'auto-mode-alist '("\\.hy\\'" . hy-mode))
(add-to-list 'interpreter-mode-alist '("hy" . hy-mode))
;;;###autoload
(define-derived-mode inferior-hy-mode comint-mode "Inferior Hy"
"Major mode for Hy inferior process."
(hy--inferior-mode-setup))
;;;###autoload
(define-derived-mode hy-mode prog-mode "Hy"
"Major mode for editing Hy files."
(hy--mode-setup-eldoc)
(hy--mode-setup-font-lock)
(hy--mode-setup-inferior)
(hy--mode-setup-smartparens)
(hy--mode-setup-syntax))
;; Spacemacs users please see spacemacs-hy, all bindings defined there
(set-keymap-parent hy-mode-map lisp-mode-shared-map)
(define-key hy-mode-map (kbd "C-c C-e") 'hy-shell-start-or-switch-to-shell)
(define-key hy-mode-map (kbd "C-c C-b") 'hy-shell-eval-buffer)
(define-key hy-mode-map (kbd "C-c C-t") 'hy-insert-pdb)
(define-key hy-mode-map (kbd "C-c C-S-t") 'hy-insert-pdb-threaded)
(provide 'hy-mode)
;;; hy-mode.el ends here