From c284efe30e87399e3573e250ed5ae02a3f899f7f Mon Sep 17 00:00:00 2001 From: justbur Date: Fri, 19 Feb 2016 15:20:27 -0500 Subject: [PATCH 1/2] julia-mode: Fix indent for hanging ops and module Introduce julia-indent-hanging to calculate indentation following any hanging operators as defined in julia-hanging-operator-regexp on the previous line. If a line follows a hanging operator increase the indentation by one increment, unless it is preceded by two hanging operators. julia-hanging-operator-regexp is taken from julia-parser.scm. Added two tests Fixes #15118 Fix indentation after module kw Register "module" as a keyword that starts a block, and add to new var julia-block-start-keywords-no-indent for block keywords that should not increase the indentation level. Fixes #11559 --- contrib/julia-mode-tests.el | 44 ++++++++++++ contrib/julia-mode.el | 138 ++++++++++++++++++++++++++++++++---- 2 files changed, 169 insertions(+), 13 deletions(-) diff --git a/contrib/julia-mode-tests.el b/contrib/julia-mode-tests.el index 1ad866f3757c5..de68089ba0c84 100644 --- a/contrib/julia-mode-tests.el +++ b/contrib/julia-mode-tests.el @@ -109,6 +109,35 @@ if foo end end")) +(ert-deftest julia--test-indent-module-keyword () + "Module should not increase indentation at any level." + (julia--should-indent + " +module +begin + a = 1 +end +end" + " +module +begin + a = 1 +end +end") + (julia--should-indent + " +begin +module +foo +end +end" + " +begin + module + foo + end +end")) + (ert-deftest julia--test-indent-function () "We should indent function bodies." (julia--should-indent @@ -164,6 +193,21 @@ bar" foo() = bar")) +(ert-deftest julia--test-indent-operator () + "We should increase indent after the first trailing operator +but not again after that." + (julia--should-indent + " +foo() |> +bar |> +baz +qux" + " +foo() |> + bar |> + baz +qux")) + (ert-deftest julia--test-indent-ignores-blank-lines () "Blank lines should not affect indentation of non-blank lines." (julia--should-indent diff --git a/contrib/julia-mode.el b/contrib/julia-mode.el index 58aafa3eb7682..fb17d4a4585b2 100644 --- a/contrib/julia-mode.el +++ b/contrib/julia-mode.el @@ -131,6 +131,71 @@ This function provides equivalent functionality, but makes no efforts to optimis "\\\\")) (group "'")))) +(defconst julia-hanging-operator-regexp + ;; taken from julia-parser.scm + (concat "^[^#\n]+ " + (regexp-opt + '( ;; conditional + "?" + ;; assignment + "=" ":=" "+=" "-=" "*=" "/=" "//=" ".//=" ".*=" "./=" "\\=" ".\\=" + "^=" ".^=" "÷=" ".÷=" "%=" ".%=" "|=" "&=" "$=" "=>" "<<=" ">>=" + ">>>=" "~" ".+=" ".-=" + ;; arrow + "--" "-->" "←" "→" "↔" "↚" "↛" "↠" "↣" "↦" "↮" "⇎" "⇏" "⇒" "⇔" "⇴" + "⇶" "⇷" "⇸" "⇹" "⇺" "⇻" "⇼" "⇽" "⇾" "⇿" "⟵" "⟶" "⟷" "⟷" "⟹" + "⟺" "⟻" "⟼" "⟽" "⟾" "⟿" "⤀" "⤁" "⤂" "⤃" "⤄" "⤅" "⤆" "⤇" "⤌" + "⤍" "⤎" "⤏" "⤐" "⤑" "⤔" "⤕" "⤖" "⤗" "⤘" "⤝" "⤞" "⤟" "⤠" "⥄" "⥅" + "⥆" "⥇" "⥈" "⥊" "⥋" "⥎" "⥐" "⥒" "⥓" "⥖" "⥗" "⥚" "⥛" "⥞" "⥟" "⥢" + "⥤" "⥦" "⥧" "⥨" "⥩" "⥪" "⥫" "⥬" "⥭" "⥰" "⧴" "⬱" "⬰" "⬲" "⬳" "⬴" + "⬵" "⬶" "⬷" "⬸" "⬹" "⬺" "⬻" "⬼" "⬽" "⬾" "⬿" "⭀" "⭁" "⭂" "⭃" "⭄" + "⭇" "⭈" "⭉" "⭊" "⭋" "⭌" "←" "→" + ;; or and and + "&&" "||" + ;; comparison + ">" "<" ">=" "≥" "<=" "≤" "==" "===" "≡" "!=" "≠" "!==" "≢" ".>" + ".<" ".>=" ".≥" ".<=" ".≤" ".==" ".!=" ".≠" ".=" ".!" "<:" ">:" "∈" + "∉" "∋" "∌" "⊆" "⊈" "⊂" "⊄" "⊊" "∝" "∊" "∍" "∥" "∦" "∷" "∺" "∻" "∽" + "∾" "≁" "≃" "≄" "≅" "≆" "≇" "≈" "≉" "≊" "≋" "≌" "≍" "≎" "≐" "≑" "≒" + "≓" "≔" "≕" "≖" "≗" "≘" "≙" "≚" "≛" "≜" "≝" "≞" "≟" "≣" "≦" "≧" "≨" + "≩" "≪" "≫" "≬" "≭" "≮" "≯" "≰" "≱" "≲" "≳" "≴" "≵" "≶" "≷" "≸" "≹" + "≺" "≻" "≼" "≽" "≾" "≿" "⊀" "⊁" "⊃" "⊅" "⊇" "⊉" "⊋" "⊏" "⊐" "⊑" "⊒" + "⊜" "⊩" "⊬" "⊮" "⊰" "⊱" "⊲" "⊳" "⊴" "⊵" "⊶" "⊷" "⋍" "⋐" "⋑" "⋕" "⋖" + "⋗" "⋘" "⋙" "⋚" "⋛" "⋜" "⋝" "⋞" "⋟" "⋠" "⋡" "⋢" "⋣" "⋤" "⋥" "⋦" "⋧" + "⋨" "⋩" "⋪" "⋫" "⋬" "⋭" "⋲" "⋳" "⋴" "⋵" "⋶" "⋷" "⋸" "⋹" "⋺" "⋻" "⋼" + "⋽" "⋾" "⋿" "⟈" "⟉" "⟒" "⦷" "⧀" "⧁" "⧡" "⧣" "⧤" "⧥" "⩦" "⩧" "⩪" "⩫" + "⩬" "⩭" "⩮" "⩯" "⩰" "⩱" "⩲" "⩳" "⩴" "⩵" "⩶" "⩷" "⩸" "⩹" "⩺" "⩻" "⩼" + "⩽" "⩾" "⩿" "⪀" "⪁" "⪂" "⪃" "⪄" "⪅" "⪆" "⪇" "⪈" "⪉" "⪊" "⪋" "⪌" "⪍" + "⪎" "⪏" "⪐" "⪑" "⪒" "⪓" "⪔" "⪕" "⪖" "⪗" "⪘" "⪙" "⪚" "⪛" "⪜" "⪝" "⪞" + "⪟" "⪠" "⪡" "⪢" "⪣" "⪤" "⪥" "⪦" "⪧" "⪨" "⪩" "⪪" "⪫" "⪬" "⪭" "⪮" "⪯" + "⪰" "⪱" "⪲" "⪳" "⪴" "⪵" "⪶" "⪷" "⪸" "⪹" "⪺" "⪻" "⪼" "⪽" "⪾" "⪿" "⫀" + "⫁" "⫂" "⫃" "⫄" "⫅" "⫆" "⫇" "⫈" "⫉" "⫊" "⫋" "⫌" "⫍" "⫎" "⫏" "⫐" "⫑" + "⫒" "⫓" "⫔" "⫕" "⫖" "⫗" "⫘" "⫙" "⫷" "⫸" "⫹" "⫺" "⊢" "⊣" + ;; pipe, colon + "|>" "<|" ":" ".." + ;; plus + "+" "-" "⊕" "⊖" "⊞" "⊟" ".+" ".-" "++" "|" "∪" "∨" "$" "⊔" "±" "∓" + "∔" "∸" "≂" "≏" "⊎" "⊻" "⊽" "⋎" "⋓" "⧺" "⧻" "⨈" "⨢" "⨣" "⨤" "⨥" "⨦" + "⨧" "⨨" "⨩" "⨪" "⨫" "⨬" "⨭" "⨮" "⨹" "⨺" "⩁" "⩂" "⩅" "⩊" "⩌" "⩏" "⩐" + "⩒" "⩔" "⩖" "⩗" "⩛" "⩝" "⩡" "⩢" "⩣" + ;; bitshift + "<<" ">>" ">>>" ".<<" ".>>" ".>>>" + ;; times + "*" "/" "./" "÷" ".÷" "%" "⋅" "∘" "×" ".%" ".*" "\\" + ".\\" "&" "∩" "∧" "⊗" "⊘" "⊙" "⊚" "⊛" "⊠" "⊡" "⊓" "∗" "∙" "∤" "⅋" + "≀" "⊼" "⋄" "⋆" "⋇" "⋉" "⋊" "⋋" "⋌" "⋏" "⋒" "⟑" "⦸" "⦼" "⦾" "⦿" "⧶" + "⧷" "⨇" "⨰" "⨱" "⨲" "⨳" "⨴" "⨵" "⨶" "⨷" "⨸" "⨻" "⨼" "⨽" "⩀" "⩃" "⩄" + "⩋" "⩍" "⩎" "⩑" "⩓" "⩕" "⩘" "⩚" "⩜" "⩞" "⩟" "⩠" "⫛" "⊍" "▷" "⨝" "⟕" + "⟖" "⟗" + ;; rational + "//" ".//" + ;; power + "^" ".^" "↑" "↓" "⇵" "⟰" "⟱" "⤈" "⤉" "⤊" "⤋" "⤒" "⤓" "⥉" "⥌" "⥍" + "⥏" "⥑" "⥔" "⥕" "⥘" "⥙" "⥜" "⥝" "⥠" "⥡" "⥣" "⥥" "⥮" "⥯" "↑" "↓" + ;; decl, dot + "::" ".")) + (regexp-opt '(" #" " \n" "#" "\n")))) + (defconst julia-triple-quoted-string-regex ;; We deliberately put a group on the first and last delimiter, so ;; we can mark these as string delimiters for font-lock. @@ -258,7 +323,11 @@ This function provides equivalent functionality, but makes no efforts to optimis (defconst julia-block-start-keywords (list "if" "while" "for" "begin" "try" "function" "type" "let" "macro" - "quote" "do" "immutable")) + "quote" "do" "immutable" "module")) + +;; For keywords that begin a block without additional indentation +(defconst julia-block-start-keywords-no-indent + (list "module")) (defconst julia-block-end-keywords (list "end" "else" "elseif" "catch" "finally")) @@ -426,6 +495,56 @@ with it. Returns nil if we're not within nested parens." (current-column)) nil)))) +(defun julia-prev-line-skip-blank-or-comment () + "Move point to beginning of previous line skipping blank lines +and lines including only comments. Returns number of lines moved. +A return of -1 signals that we moved to the first line of +the (possibly narrowed) buffer, so there is nowhere else to go." + (catch 'result + (let ((moved 0) this-move) + (while t + (setq this-move (forward-line -1)) + (cond + ;; moved into comment or blank + ((and (= 0 this-move) + (or (looking-at-p "^\\s-*\\(?:#.*\\)*$") + (julia-in-comment))) + (incf moved)) + ;; success + ((= 0 this-move) + (throw 'result (1+ moved))) + ;; on first line and in comment + ((and (bobp) + (or (looking-at-p "^\\s-*\\(?:#.*\\)*$") + (julia-in-comment))) + (throw 'result -1)) + ((bobp) + (throw 'result moved)) + (t + (throw 'result 0))))))) + +(defun julia-indent-hanging () + "Calculate indentation for lines that follow \"hanging\" +operators (operators that end the previous line) as defined in +`julia-hanging-operator-regexp'. An assignment operator ending +the previous line increases the indent as do the other operators +unless another operator is found two lines up. Previous line +means previous line after skipping blank lines and lines with +only comments." + (let (prev-indent) + (save-excursion + (when (> (julia-prev-line-skip-blank-or-comment) 0) + (setq prev-indent (current-indentation)) + (when (looking-at-p julia-hanging-operator-regexp) + (if (and (> (julia-prev-line-skip-blank-or-comment) 0) + (looking-at-p julia-hanging-operator-regexp)) + ;; two preceding hanging operators => indent same as line + ;; above + prev-indent + ;; one preceding hanging operator => increase indent from line + ;; above + (+ julia-indent-offset prev-indent))))))) + (defun julia-indent-line () "Indent current line of julia code." (interactive) @@ -437,17 +556,8 @@ with it. Returns nil if we're not within nested parens." (save-excursion (beginning-of-line) (ignore-errors (julia-paren-indent))) - ;; If the previous line ends in =, increase the indent. - (ignore-errors ; if previous line is (point-min) - (save-excursion - (if (and (not (equal (point-min) (line-beginning-position))) - (progn - (forward-line -1) - (end-of-line) (backward-char 1) - (and (equal (char-after (point)) ?=) - (not (julia-in-comment))))) - (+ julia-indent-offset (current-indentation)) - nil))) + ;; indent due to hanging operators or a line ending in = + (julia-indent-hanging) ;; Indent according to how many nested blocks we are in. (save-excursion (beginning-of-line) @@ -455,7 +565,9 @@ with it. Returns nil if we're not within nested parens." (let ((endtok (julia-at-keyword julia-block-end-keywords)) (last-open-block (julia-last-open-block (- (point) julia-max-block-lookback)))) (max 0 (+ (or last-open-block 0) - (if endtok (- julia-indent-offset) 0))))))) + (if (or endtok + (julia-at-keyword julia-block-start-keywords-no-indent)) + (- julia-indent-offset) 0))))))) ;; Point is now at the beginning of indentation, restore it ;; to its original position (relative to indentation). (when (>= point-offset 0) From 0d4de5a4b3ca84cf38732f4083fa75d68444971a Mon Sep 17 00:00:00 2001 From: justbur Date: Sun, 21 Feb 2016 13:37:17 -0500 Subject: [PATCH 2/2] julia-mode: Improve paren indent perf Replace julia-paren-indent with a version that uses the higher level function backward-up-list to find any previous opening parentheses. This function automatically skips balanced expressions like strings, as well as comments. On some test files the time to indent falls by more than 75% with this change, even after increasing the threshold for paren lookback to 10k. Note the threshold is removed in this version. --- contrib/julia-mode.el | 45 +++++++++++-------------------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/contrib/julia-mode.el b/contrib/julia-mode.el index fb17d4a4585b2..2f53f293404e4 100644 --- a/contrib/julia-mode.el +++ b/contrib/julia-mode.el @@ -452,13 +452,6 @@ beginning of the buffer." (unless (eq (point) (point-min)) (backward-char))) -(defvar julia-max-paren-lookback 400 - "When indenting, don't look back more than this -many characters to see if there are unclosed parens. - -This variable has a major effect on indent performance if set too -high.") - (defvar julia-max-block-lookback 5000 "When indenting, don't look back more than this many characters to see if there are unclosed blocks. @@ -471,29 +464,16 @@ high.") containing paren before point, so we can align succeeding code with it. Returns nil if we're not within nested parens." (save-excursion - ;; Back up to previous line (beginning-of-line was already called) - (backward-char) - (let ((min-pos (max (- (point) julia-max-paren-lookback) - (point-min))) - (open-count 0)) - (while (and (> (point) min-pos) - (not (plusp open-count))) - - (when (looking-at (rx (any "[" "]" "(" ")"))) - (unless (or (julia-in-string) (julia-in-comment)) - (cond ((looking-at (rx (any "[" "("))) - (incf open-count)) - ((looking-at (rx (any "]" ")"))) - (decf open-count))))) - - (julia--safe-backward-char)) - - (if (plusp open-count) - (progn (forward-char 2) - (while (looking-at (rx blank)) - (forward-char)) - (current-column)) - nil)))) + (beginning-of-line) + (let ((parser-state (syntax-ppss))) + (cond ((nth 3 parser-state) nil) ;; strings + ((= (nth 0 parser-state) 0) nil) ;; top level + (t + (ignore-errors ;; return nil if any of these movements fail + (backward-up-list) + (forward-char) + (skip-syntax-forward " ") + (current-column))))))) (defun julia-prev-line-skip-blank-or-comment () "Move point to beginning of previous line skipping blank lines @@ -549,13 +529,10 @@ only comments." "Indent current line of julia code." (interactive) (let* ((point-offset (- (current-column) (current-indentation)))) - (end-of-line) (indent-line-to (or ;; If we're inside an open paren, indent to line up arguments. - (save-excursion - (beginning-of-line) - (ignore-errors (julia-paren-indent))) + (julia-paren-indent) ;; indent due to hanging operators or a line ending in = (julia-indent-hanging) ;; Indent according to how many nested blocks we are in.