noctuid / lispyville

lispy + evil = lispyville

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support for lispy-tab

Ambrevar opened this issue · comments

I find lispy-tab super useful, but it's mapping to Evil is not obvious since it serves both for folding (usually TAB) and for indenting (usually =).

The indentation part is done by lispy--normalize. This function is so good it'd be fantastic to be able to run it over the whole buffer. Don't know If gg=G can do that... :)

I personally just use aggressive-indent-mode and never manually indent. I know that lispy-tab does extra prettification as well, but I don't often use it. Could you give an example where it does something that evil-indent/= doesn't do?

lispy--normalize-1 works on a single sexp as opposed to a region, but an new equivalent of = could potentially loop through each sexp in the region. What would the expected behavior be? Act on top-level forms only or forms at the earliest level in the region only?

I'm not sure I understand. lispy--normalize is recursive, if that was a misunderstanding.

Right, so it would be redundant to use it on a sexp within an already normalized sexp (which is why I mentioned same level options). I ask this because I'm not sure what behavior would be most preferable and some behaviors would be easier to implement.

Consider this example:

(foo
 (bar
  ~(baz)
   (qux)))
|

Basic summary of some possiblities:

  1. behavior: always act only on top-level sexps (easy to implement)
    result: (foo) sexp is normalized
  2. behavior: act on sexps that start within region (easy)
    result: (baz) and (quix) sexps are normalized
  3. behavior: act on the current sexp and all following sexps that start within the region (easy)
    result: (bar) sexp is normalized
  4. behavior: act on all sexps where at least one side is in region (harder because can't just check beginning of region)
    result: (foo) sexp is normalized
  5. behavior: to 4 what 3 is to 2; include sexps that contain either the region beginning or end

1 is less precise, and since you could just use a text object for the top level form if you wanted, it's probably preferable to be more precise. I'm leaning towards 3 or 5. What would your preference be?

What are ~ and |? Mark and point?

Yes. I went ahead and added an experimental lispyville-prettify operator for testing that has the third behavior.

Looks great so far!

@noctuid After calling lispyville-prettify with gg, I can't go back to my position using C-o.

I still haven't exactly decided how point restoration should work. Would you care to post some before/after examples with | as the point to show where the point starts and where you think it should end up? I can have lispyville-prettify add to the jumplist if necessary, but I'd prefer to just have the point end up in a sane location afterwards if possible.

@noctuid Sure!

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
             ;; conditional is if or unless
             ;; conditional-test is (= (mod x 2) 0) in our examples
             ,conditional ,condi|tional-test
             ;; and this is the action from the earlier lisp example
             ;; result = result + [x] in python
             do (setq ,result (append ,result (list ,expression))))
       ;; return the result
       ,result)))
;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
    (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))
(lcomp x for x in (range 50) if (= (mod x 2) 0))

It jumps to the beginning of the buffer after prettifying. I want it to stay where it was.

I've made some minor changes and added a key theme that remaps evil-indent. The main issue is whether the point should be preserved by buffer position or a marker. For example, by position:

(foo
 |a
 )
;; with == becomes 
(foo
 |a)

This seems like the expected behavior for this case as using a marker will move the point to the previous line. However, when characters before the buffer position are deleted, this doesn't work the same:

(foo

 |)

(bar)
;; with == becomes
(foo)

|(bar)
;; or using a marker becomes
|(foo)

(bar)

Neither stays with the closing paren for this case, but the marker behavior makes a little more sense.

This issue could be avoided by using the 2nd behavior and only normalizing lists that start within the region. This would prevent characters from before the point from being deleted, but it seems a little less convenient (e.g. for the above examples you would need to use =a( instead of ==).

The choice doesn't seem obvious to me, so any feedback would be appreciated.

It looks like the reason markers don't work as expected is because lispy actually deletes the entire sexp and then re-inserts it, so the marker just ends up before the newly inserted sexp. I can't think of any sane workaround at the moment.

lispy-tab now aligns to the margin:
abo-abo/lispy#422 (comment)

@noctuid: Could you update lispyville-prettify to support that too?