Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
560 lines (446 sloc) 5.93 KB

Smart Mode

Leading Close-Parens

Leading close-parens can cause many problems that can be fixed by paren mode, so we exit to paren mode when they are detected.

For example, it is convenient to keep trailing parens in front of the cursor after pressing enter or after deleting everything behind them:

(let [a 1
      |])
(let [a 1
      |])

Moving the cursor away:

(let [a 1
      ]); <-- spaces
(let [a 1])
      ; <-- spaces

But we also need safety from inadvertent AST breakage. For example, Indent Mode should allow this intermediate state:

(let [a 1
      |] (+ a 2))
(let [a 1
      |] (+ a 2))

Moving the cursor away will cause Indent Mode to still detect the leading close-paren, exit to Paren Mode, then fix the spacing to prevent inadvertent breakage.

(let [a 1
      ] (+ a 2))
(let [a 1]
     (+ a 2))

To prevent weird things, indentation needs to be locked to respect the leading close-paren. Exiting to Paren Mode allows this and prevents further AST breakage.

(let [a 1
  |] (+ a 2))
(let [a 1
      |] (+ a 2))

Moving cursor to the right progressively moves leading close-parens behind it to their normal positions:

(let [a 1
      ]|)
(let [a 1]
     |)

When in Paren Mode we must abide by its rules to stay balanced.

As a courtesy, unmatched close-parens in a paren trail at the beginning of a line are auto-removed (only when paren mode is triggered from smart mode).

(|)
-
|
(foo
  (bar|))
  ----
(foo
  |)
(foo
  }|)
(foo
  |)

Likewise:

(foo
  ) foo} bar|
(foo
  ) foo} bar|
       ^ error: unmatched-close-paren
(foo
  ) (bar|
(foo
  ) (bar|
    ^ error: unclosed-paren

Changes

Indent a single-line expression to enter a sibling:

(foo (bar)
      baz)
     +
(foo (bar
      baz))

Dedent multi-line expression to leave its parent:

(foo
  {:a 1
--
   :b 2})
(foo)
{:a 1
 :b 2}

Indent multi-line expression to enter new parent:

(foo)
  {:a 1
++
 :b 2}
(foo
  {:a 1
   :b 2})

Dedenting an inner line makes it leave parent:

(foo
  {:a 1
   :b 2})
---
(foo
  {:a 1})
:b 2

Dedenting a collection will adopt a former sibling line below it:

(defn foo
  [a b]
--
  bar)
(defn foo)
[a b
  bar]

But dedenting a top-level form should not cause a child to adopt a sibling:

  (defn foo
--
    [a b]
    bar)
(defn foo
  [a b]
  bar)

Indented comments move with expressions:

  (defn foo
--
    [a b]
    ; comment 1
    bar)
    ; comment 2
(defn foo
  [a b]
  ; comment 1
  bar)
  ; comment 2

Cursor temporarily preventing sibling adoption

To prevent undesirable sibling adoption when dedenting, we temporarily keep a close-paren from moving when the cursor is to the left of its open-paren.

(defn foo
  |[a b
--
   c d]
  bar
  baz)
(defn foo)
|[a b
 c d]
  bar
  baz
(defn foo)
|[a b
 c d]
  bar
  baz
(defn foo)
|[a b
 c d]
  bar
  baz

Multiple Changes

(my-fnfoo (if some-condition
 -----+++
         println) my-funfoo {:foo 1
                  ------+++
                          :bar 2})
(foo (if some-condition
       println) foo {:foo 1
                     :bar 2})

Resolving Precarious Paren After Dedent

Suppose we deleted foo in the example below. We expect 4 to not be adopted by any collection inside (((1 2 3))).

(foo |(((1
 ----
        2
        3)))
    4)
(|(((1
    2
    3)))
    4)

When cursor is removed, the precarious parens are resolved by preserving structure and correcting indentation.

((((1
 ^ prevCursor
    2
    3)))
    4)
((((1
    2
    3)))
 4)
((|((1
 ^ prevCursor
    2
    3)))
    4)
((|((1
    2
    3)))
 4)

Indenting Selected Lines

Indent only the first line:

  (foo
++
  (bar
    baz))
  (foo
    (bar
      baz))

Indent first two lines:

  (foo
++
    (bar
++
    baz))
  (foo
    (bar
      baz))

Indent last two lines:

  (foo
      (bar
++
        baz))
++
  (foo
      (bar
        baz))

Indent only the first line:

  (foo
++
  bar
  baz)
  (foo
    bar
    baz)

Indent first two lines:

  (foo
++
    bar
++
  baz)
  (foo
    bar
    baz)

Indent last two lines:

(foo
    bar
++
    baz)
++
(foo
    bar
    baz)

Multi-change Bug

Issue #173

((reduce-kv (fn [m k v]
+
            {}
           +
            {}))
           +
((reduce-kv (fn [m k v]
            {}
            {})))
                +
((reduce-kv (fn [m k v])
            {}
            {}))

Issue #176

(let [a 1]
  (
  +
    (foo))
  ++
(let [a 1]
  (
    (foo)))
         +
(let [a 1]
  (
    (foo)))

Issue #177

(let [a 1]

  (foo))
(let [a 1]
  (let [a 1]
  +++++++++++
  (foo))
++++++++
  (foo))
(let [a 1]
  (let [a 1]
    (foo))
  ++
  (foo))
(let [a 1]
  (let [a 1]
    (foo))
  (foo))

Issue #179

{:a                 {:b              (Integer/valueOf (-> ""
    ----------------
                                                          (.length)))}}
{:a {:b              (Integer/valueOf (-> ""
        -------------
                                                          (.length)))}}
                             -----------------------------
{:a {:b (Integer/valueOf (-> ""
                             (.length)))}}
You can’t perform that action at this time.