hvesalai / emacs-scala-mode

The definitive scala-mode for emacs
http://ensime.org
GNU General Public License v3.0
361 stars 68 forks source link

forward/backward word specialised for scala #75

Closed fommil closed 9 years ago

fommil commented 10 years ago

It would be great if alt-{left,right} had special behaviour in scala files, e.g. camel case words are respected, differentiating between variables and operators ("this+that" will be skipped entirely as a single word by default behaviour), etc

fommil commented 10 years ago

back should also go to the start of the block. default emacs "back word" will make

  test("do things") {
    ^doAllTheThings(things)
  }

go to

  test("do ^things") {
    doAllTheThings(things)
  }

instead of

  test("do things") ^{
    doAllTheThings(things)
  }

which is very important for greedy delete behaviour.

hvesalai commented 10 years ago

Hmm... I'm not a big fan of changing the default behavior of emacs movement, so I've tried to keep the word-movement as close as possible to what emacs does in lisp-mode.

I think what you really should ask for is a function that jumps to the beginning (outside) of the current code block. So that would jump from anywhere inside the block in you example to where the cursor is in your last example. Then you could use forward-sexp (M-C-f) to jump to the end of it if you want to, for example, mark it.

Also on the todo-list is working versions of beginning-of-defun function.

All pull requests on these issues are welcome.

hvesalai commented 10 years ago

I had the feeling there was already something for moving into/out from blocks. The command are down-listand up-list, normally bound to M-C-d and M-C-u.

fommil commented 10 years ago

@hvesalai it's not so much being able to do this as the ability to bind it to forward/backward-word. That way other emacs features behave as expected (especially delete word).

Sorry, my ENSIME backlog is so long at the moment I won't be able to submit PRs for a long time.

hvesalai commented 10 years ago

There is also forward-list and backward-list, bound normally to M-C-n and M-C-p

hvesalai commented 10 years ago

Btw... what do you mean by this+that? Do you mean that + should be a word? It is a sexp so backward-sexp and forward-sexp consider it as a separate token.

hvesalai commented 10 years ago

For the CamelCase thing to work, I think we should first try if any of the existing solutions work, such as http://www.emacswiki.org/emacs/CamelCase

fommil commented 10 years ago

@hvesalai yeah, I looked at that one but it seems unmaintained now as its not on melpa

hvesalai commented 10 years ago

As to your original question, would this be the "specification" of what you want (for the forward directin):

For backward direction:

Can you simulate if this behavior matches what you wanted.

fommil commented 10 years ago

I wouldn't want to skip over comments. I'd still expect next/prev word to work as usual.

I don't think it really matters what is currently at the point, it's more about what the next boundary before and after the point is. If I interpret "the thing at the cursor" to mean "the closest thing after/before the cursor" then this sounds sensible.

Default emacs "word" rules are very tailored to English (and elisp) that it doesn't cope well with special characters and often considers them part of the word.

hvesalai commented 10 years ago

scala-mode2 uses the default emacs word movements, but the syntax table decides what is a word char and what isn't. That's why in this+that + is not considered as a word.

fommil commented 10 years ago

yes exactly, so I'm basically asking for a scala-specific syntax table :-)

hvesalai commented 10 years ago

The syntax table IS scala-specific. Highly so. We even have a syntactify function that extends it.

fommil commented 10 years ago

hmm, maybe it's not working for me then. Should what I'm asking for be working already?

hvesalai commented 10 years ago

No, but that's how it should be from the syntax-table point of view. + is not a word and it should not be. It's a "symbol constituent"

hvesalai commented 10 years ago

Rather, this is what I've documented

    ;; by default all opchars are punctuation, but they will be
    ;; modified by syntax-propertize-function to be symbol
    ;; constituents when a part of varid or capitalid
hvesalai commented 10 years ago

So actually a + on it's own is just punctuation from emacs point of view. Only the + is foo_+ is considered as symbol constituent.

hvesalai commented 10 years ago

And the reason why + cannot be a symbol constituent is that then this+that would be considered as one token. With the current syntax-table and syntax-propertize-function that + is considered as punctuation, which separates the two words.

fommil commented 10 years ago

@hvesalai actually the + thing was the low priority thing here... I'm much more interested in braces and camelcase being considered word boundaries.

hvesalai commented 10 years ago

Please try the new scala-syntax:forward-token function. I'll write the respective backward-token later after I get feedback on this one.

Here's how I bind it in my scala-mode-hook

(add-hook 'scala-mode-hook '(lambda ()
  (local-set-key (kbd "RET") '(lambda ()
                                (interactive)
                                (newline-and-indent)
                                (scala-indent:insert-asterisk-on-multiline-comment)))
  (local-set-key (kbd "M-RET") 'scala-indent:join-line)
  (local-set-key (kbd "<backtab>") 'scala-indent:indent-with-reluctant-strategy)
  (local-set-key (kbd "M-.") 'sbt-find-definitions)
  (local-set-key (kbd "C-x '") 'sbt-run-previous-command)
  (local-set-key (kbd "<ESC> <right>") 'scala-syntax:forward-token)
  (local-set-key (kbd "<ESC> <up>") 'backward-up-list)
  (local-set-key (kbd "<ESC> <down>") 'down-list)

))
hvesalai commented 10 years ago

btw. It doesn't handle camelCase or under_score_separated words yet.

fommil commented 10 years ago

great! But by some bitter rebuke by the universe, I have to work on the only .java files in our codebase today... shudder

fommil commented 10 years ago

I tried the elpa scala-mode2-20140627.203 today and I can see some differences between the scala forward token and the usual emacs forward word... but I couldn't see if you had created a backwards equivalent yet.

I think it is potentially better, but in order to work well with my existing setup I'm really hoping that I can cook this into forward/backward-word (not just rebind (alt/ctrl)-(left/right) to use this different function) because I have some other scripts that depend on forward/backward-word behaving well for the mode (e.g. hungry delete).

hvesalai commented 10 years ago

you can easily re-define forward-word in the scala-mode-hook

(set (make-local-variable 'forward-word) 'scala-syntax:forward-token)

the respective backward-token has not yet been implemented.

hvesalai commented 10 years ago

p.s. may I ask how you use that hungry delete? I don't know that feature. Tried to google it, but didn't find a good explanation of what it does.

fommil commented 10 years ago

https://github.com/fommil/unix/blob/master/.emacs#L68

fommil commented 10 years ago

This is my config that gets CamelCase navigation, but not yet integrated with your forward-word. I think having this subword mode working with your boundary logic would be very good! (And I would rebound forward/backward-word in the scala buffers to be your implementation instead... I'm not sure how to do it for my contextual-backspace though)

(subword-mode 1)

(defun contextual-backspace ()
  "Hungry whitespace or delete word depending on context"
  (interactive)
  (if (looking-back "[\t\s\n\r]\\{2,\\}" (- (point) 3))
      (hungry-delete-backward 1)
    (if (subword-mode)
        (subword-backward-kill 1)
      (backward-kill-word 1))))

(global-set-key (kbd "C-<backspace>") 'contextual-backspace)
(global-set-key (kbd "C-<left>") 'subword-backward)
(global-set-key (kbd "C-<right>") 'subword-forward)

if you could provide a scala:backward-kill-word then I could do something with that (unless it uses backward-word under the hood, in which case it should just work if I remove the special logic for subword-mode).

fommil commented 9 years ago

I've done a lot of soul searching on this issue, and I am pretty convinced that "standard emacs navigation" is correct... but that regexes don't quite cut it. Therefore for block movement, we're looking at implementing https://github.com/ensime/ensime-server/issues/973 ... so scala-mode2 doesn't need to look at implementing any of the sexpression navigation. regex brace-only sexp navigation is ok but obviously depends on the style of scala being navigated.

For forward/backward word, default emacs works fine and to get subword navigation I use (global-subword-mode 1)