unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Generalizing find-definition
@ 2014-11-02 14:15 Jorgen Schaefer
  2014-11-02 15:34 ` Stefan Monnier
  2014-11-02 22:26 ` Stephen Leake
  0 siblings, 2 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-02 14:15 UTC (permalink / raw)
  To: emacs-devel

Hello!
Emacs by default uses etags.el to find definitions. This is great for C
and similar more static languages, but many more dynamic languages have
other ways of finding definitions. This has lead to major/minor modes
redefining M-. to a specialized "go to definition" function (e.g.
SLIME for CL, slime-nav for Emacs Lisp, Geiser, Cider, Elpy etc.). I
think it would be good if Emacs provided a generalized functionality
for M-. and related commands instead of having all these modes
re-implementing it.

If I get positive feedback on the following ideas, I'm happy to do the
necessary coding (once the git transition is done).


M-. is bound to a new command `find-definition', which primarily calls
the value of a new variable `find-definition-function' (by default a
wrapper around `find-tag' to keep the current functionality intact).
`find-definition' also keeps track of the tag ring, so this would move
`find-tag-marker-ring' and related functionality out of etags.el, too.

M-* is the standard opposite command for this, so that would be
extracted as well. SLIME and a few other modes re-define M-, to be the
opposite for M-. instead for easier navigation. How do you feel about
swapping the definition of M-, and M-* in etags.el?

C-M-. is currently bound to find-tag-regexp. There is currently no
standard functionality in Emacs to find the callers of a symbol at
point, which might be nice to put on C-M-. if it is defined at some
point for symmetry reasons. If so, find-tag-regexp needs a new key
binding. I'm unsure how useful find-tag-regexp is to begin with as I do
not use tags tables, so I kept it out of the proposal below, even
though it is certainly related.

Other commands affected by this would be C-x 4 . and C-x 5 .


So the new module find-definition.el would define:


Variable `find-definition-function'

A function to be called to find definitions for the symbol at point. It
is called with no arguments, and should return a marker for the
location of the definition.


M-. find-definition

Call `find-definition-function' and go to the marker returned, if any.
Store the old location in `find-definition-marker-ring'.


M-, or M-* find-definition-pop-mark

Go to the last location in `find-definition-marker-ring'


C-x 4 . and C-x 5 .

Same as `find-definition', except the marker returned by
`find-definition-function' is displayed in another window or frame
instead.


Changes to etags.el:

Remove the aforementioned key bindings.

Provide a new function, `etags-find-definition', which calls
`find-tag-noselect'. This will also check for the current prefix
argument to mimic the old behavior for NEXT-P.


Changes to elisp-mode.el:

Provide a value for `find-definition-function' which finds the
definition of the symbol at point.


Comments?

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 14:15 Generalizing find-definition Jorgen Schaefer
@ 2014-11-02 15:34 ` Stefan Monnier
  2014-11-02 16:29   ` Jorgen Schaefer
  2014-11-17 20:10   ` Jorgen Schaefer
  2014-11-02 22:26 ` Stephen Leake
  1 sibling, 2 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-02 15:34 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: emacs-devel

> M-. is bound to a new command `find-definition', which primarily calls
> the value of a new variable `find-definition-function' (by default a
> wrapper around `find-tag' to keep the current functionality intact).
> `find-definition' also keeps track of the tag ring, so this would move
> `find-tag-marker-ring' and related functionality out of etags.el, too.

Yes, this sounds great.

> M-* is the standard opposite command for this, so that would be
> extracted as well.  SLIME and a few other modes re-define M-, to be the
> opposite for M-. instead for easier navigation.  How do you feel about
> swapping the definition of M-, and M-* in etags.el?

That's incompatible with the current M-, binding.
What would then be the equivalent of the current M-, ?

> C-M-. is currently bound to find-tag-regexp.  There is currently no
> standard functionality in Emacs to find the callers of a symbol at
> point, which might be nice to put on C-M-. if it is defined at some
> point for symmetry reasons.

M-. RET  does "find the callers of a symbol at point", AFAICT.

> Comments?

I'm all for it,


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 15:34 ` Stefan Monnier
@ 2014-11-02 16:29   ` Jorgen Schaefer
  2014-11-02 18:14     ` Helmut Eller
  2014-11-03  2:22     ` Stefan Monnier
  2014-11-17 20:10   ` Jorgen Schaefer
  1 sibling, 2 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-02 16:29 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

On Sun, 02 Nov 2014 10:34:28 -0500
Stefan Monnier <monnier@iro.umontreal.ca> wrote:

> > M-* is the standard opposite command for this, so that would be
> > extracted as well.  SLIME and a few other modes re-define M-, to be
> > the opposite for M-. instead for easier navigation.  How do you
> > feel about swapping the definition of M-, and M-* in etags.el?
> 
> That's incompatible with the current M-, binding.
> What would then be the equivalent of the current M-, ?

The idea would be to simply swap M-, and M-*, so M-* would then be
`tags-loop-continue'. As I do not use tags, I do not know how often
that command is used and whether M-* is too inconvenient for this,
though.

> > C-M-. is currently bound to find-tag-regexp.  There is currently no
> > standard functionality in Emacs to find the callers of a symbol at
> > point, which might be nice to put on C-M-. if it is defined at some
> > point for symmetry reasons.
> 
> M-. RET  does "find the callers of a symbol at point", AFAICT.

I must be missing something - how does this work? M-. should jump
to the definition of the symbol at point, then RET should just enter a
newline? And if M-. prompts for a tag, RET will just accept the default?

Apparently, SLIME uses M-_ and M-? for edit-uses (to accomodate
differing keyboard layouts), so that might be a better choice anyhow.

> > Comments?
> 
> I'm all for it,

Thank you. I'll wait for more feedback and will work on this after the
official git transition.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 16:29   ` Jorgen Schaefer
@ 2014-11-02 18:14     ` Helmut Eller
  2014-11-02 18:35       ` Jorgen Schaefer
  2014-11-03  2:22     ` Stefan Monnier
  1 sibling, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-11-02 18:14 UTC (permalink / raw)
  To: emacs-devel

On Sun, Nov 02 2014, Jorgen Schaefer wrote:

> On Sun, 02 Nov 2014 10:34:28 -0500
> Stefan Monnier <monnier@iro.umontreal.ca> wrote:
>
>> > M-* is the standard opposite command for this, so that would be
>> > extracted as well.  SLIME and a few other modes re-define M-, to be
>> > the opposite for M-. instead for easier navigation.  How do you
>> > feel about swapping the definition of M-, and M-* in etags.el?
>> 
>> That's incompatible with the current M-, binding.
>> What would then be the equivalent of the current M-, ?
>
> The idea would be to simply swap M-, and M-*, so M-* would then be
> `tags-loop-continue'. As I do not use tags, I do not know how often
> that command is used and whether M-* is too inconvenient for this,
> though.

In SLIME, M-, and M-* do the same thing.

Using M-, for pop-tag-mark is IMO very desirable, because on most
keyboards . and , are on adjacent keys and it's easy, almost pleasant,
to jump to a definition and back -- at least in the case when there's a
unique match.

We keep the binding for M-* around mostly because some users have stored
it in their muscle memory.  But it seems to me that M-* is quite hard to
press (on US keyboards) and if it weren't for tradition, we wouldn't
bind it at all.  SLIME will definitely keep the M-./M-, pair, well
knowing that it's incompatible with the binding for tags-loop-continue.

In my experience, tags-loop-continue is rather hard to use and I have
long argued to get rid of it and replace it with a better UI.
E.g. tags-loop-continue must be pressed multiple times just to find out
at the end that none of the offered candidates was relevant.  SLIME does
it differently: the list of all candidates is displayed in a separate
buffer with one candidate per line; a bit like the results of a search
engine like Google.  The user must then move the cursor to the
interesting line and press RET to actually jump to the definition.  If
there's only a single candidate, then there's no need to display the
list and we can jump to the candidate right away.  SLIME has no analog
to tags-loop-continue (and no key binding for it) because it's not
needed; at least nobody ever asked for such a command.

For SLIME, we would happily use the "standard" find-definition
infrastructure if it comes with a good UI.  The tags-loop-continue based
design is not acceptable for us.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 18:14     ` Helmut Eller
@ 2014-11-02 18:35       ` Jorgen Schaefer
  2014-11-02 19:51         ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-02 18:35 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On Sun, 02 Nov 2014 19:14:55 +0100
Helmut Eller <eller.helmut@gmail.com> wrote:

> For SLIME, we would happily use the "standard" find-definition
> infrastructure if it comes with a good UI.

Thank you for the feedback.

Could you elaborate a bit on what you'd like to see for "a good UI"? My
idea so far was to simply have modes provide a function that returns a
marker or nil, while the function can check for interactive calls if it
wants to, and use that in the usual go to location in same
window/other window/other frame and go back. But I'm very open to ideas
to make this better.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 18:35       ` Jorgen Schaefer
@ 2014-11-02 19:51         ` Helmut Eller
  2014-11-02 20:17           ` Jorgen Schaefer
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-11-02 19:51 UTC (permalink / raw)
  To: emacs-devel

On Sun, Nov 02 2014, Jorgen Schaefer wrote:

> Could you elaborate a bit on what you'd like to see for "a good UI"? My
> idea so far was to simply have modes provide a function that returns a
> marker or nil, while the function can check for interactive calls if it
> wants to, and use that in the usual go to location in same
> window/other window/other frame and go back. But I'm very open to ideas
> to make this better.

Let me define a bit of terminology that I use in SLIME: M-. parses the
"symbol" at point and then asks the external process to search a list of
"candidates" that match the symbol.  A candidate is a "location" with a
short description.  A location is a file:line:column triple (in the
simplest case).

The primary complication for a "good UI" is the case when there is more
than one candidate.

etags.el solves this complication by introducing the tags-loop-continue
command to cycle through the list of candidates (I think etags.el does
not compute an explicit list of candidates, but instead keeps a marker
in the TAGS buffer and tags-loop-continue will quite literally continue
the search starting from that marker).  I don't like this kind of UI.
It would be better to display the list of candidates instead of keeping
it hidden.

Displaying the list of candidates in an auxiliary buffer, make it easy to
select an interesting candidate and finally close the buffer is the job
of the UI.  Doing this well is not so easy.  I spent quite some time to
make it good in SLIME.  It's still not perfect, but IMO way better than
what etags.el offers.

Parsing the "symbol" at point for M-. will be language dependent and
some language may need to determine a lot of context (current
module/class/function etc.).  In SLIME, it's relatively easy as we only
need to determine the current package and then the symbol like in Elisp.
I think a "good UI" should, by default, determine the symbol around
point without asking each time (as find-tag does).

BTW: M-x rgrep needs to solve similar UI issues as it also needs to deal
with a "list of candidates".  Maybe there's an opportunity to
share/unify some things.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 19:51         ` Helmut Eller
@ 2014-11-02 20:17           ` Jorgen Schaefer
  0 siblings, 0 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-02 20:17 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On Sun, 02 Nov 2014 20:51:40 +0100
Helmut Eller <eller.helmut@gmail.com> wrote:

> On Sun, Nov 02 2014, Jorgen Schaefer wrote:
> 
> > Could you elaborate a bit on what you'd like to see for "a good
> > UI"? My idea so far was to simply have modes provide a function
> > that returns a marker or nil, while the function can check for
> > interactive calls if it wants to, and use that in the usual go to
> > location in same window/other window/other frame and go back. But
> > I'm very open to ideas to make this better.
> 
> Let me define a bit of terminology that I use in SLIME:
> [...]

Thank you for the elaborate explanation!

> The primary complication for a "good UI" is the case when there is
> more than one candidate.

Yes; I deliberately omitted this for my initial proposal, as this is
definitely not simple, and I suspect no matter how it is done, it will
leave *someone* unhappy.

> BTW: M-x rgrep needs to solve similar UI issues as it also needs to
> deal with a "list of candidates".  Maybe there's an opportunity to
> share/unify some things.

I think it would be possible to optionally return a list of markers
from `find-definition-function', which would then be displayed in a
buffer similar to rgrep/compile.

Thank you for the idea. I like that.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 14:15 Generalizing find-definition Jorgen Schaefer
  2014-11-02 15:34 ` Stefan Monnier
@ 2014-11-02 22:26 ` Stephen Leake
  2014-11-03  7:31   ` Jorgen Schaefer
  1 sibling, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-11-02 22:26 UTC (permalink / raw)
  To: emacs-devel

Jorgen Schaefer <forcer@forcix.cx> writes:

> So the new module find-definition.el would define:

looks good; ada-mode could use this

ada-mode also has "show all references of name at point", which
currently pops up a buffer in compilation-mode, showing
compilation-style locations of all uses. That's similar functionality to
find-tag/tags-loop-continue, and similar UI to SLIME.

> Changes to elisp-mode.el:
>
> Provide a value for `find-definition-function' which finds the
> definition of the symbol at point.

+1

I have my own hack for this; It requires me to distinguish between
variable and defun names and invoke separate keys; I hope you can remove
that annoyance.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 16:29   ` Jorgen Schaefer
  2014-11-02 18:14     ` Helmut Eller
@ 2014-11-03  2:22     ` Stefan Monnier
  2014-11-03  7:03       ` Helmut Eller
                         ` (2 more replies)
  1 sibling, 3 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-03  2:22 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: emacs-devel

> The idea would be to simply swap M-, and M-*, so M-* would then be
> `tags-loop-continue'.

I'm not sure what would be the benefit of such a swap.

This said, one of the benefits of the generic functionality you're
suggesting is that users can easily rebind those commands and know that
this will affect consistently all packages providing the functionality.

>> M-. RET  does "find the callers of a symbol at point", AFAICT.

Duh, sorry for being dense, of course it doesn't.  etags does not offer
the needed info, so the etags.el UI doesn't have such a thing.

Helmut Eller <eller.helmut@gmail.com> writes:
> bind it at all.  SLIME will definitely keep the M-./M-, pair, well
> knowing that it's incompatible with the binding for tags-loop-continue.

When SLIME uses the new functionality it should mess with its key
bindings at all, so the user can more easily make global changes to
those key bindings.

> In my experience, tags-loop-continue is rather hard to use and I have
> long argued to get rid of it and replace it with a better UI.
> E.g. tags-loop-continue must be pressed multiple times just to find out
> at the end that none of the offered candidates was relevant.  SLIME does
> it differently: the list of all candidates is displayed in a separate
> buffer with one candidate per line; a bit like the results of a search
> engine like Google.  The user must then move the cursor to the
> interesting line and press RET to actually jump to the definition.  If
> there's only a single candidate, then there's no need to display the
> list and we can jump to the candidate right away.  SLIME has no analog
> to tags-loop-continue (and no key binding for it) because it's not
> needed; at least nobody ever asked for such a command.

I agree that tags-loop-continue is not super convenient.
What key-binding does SLIME use to get this list buffer (which would
most naturally be implemented as a kind of grep/compilation-mode buffer)?
Would C-u M-. be usable for that?

> ada-mode also has "show all references of name at point", which
> currently pops up a buffer in compilation-mode, showing
> compilation-style locations of all uses.  That's similar functionality to
> find-tag/tags-loop-continue, and similar UI to SLIME.

Sounds like we agree.

So on the UI side we mostly have:
- "jump to *the* definition of thing at point".
- "list definitions of thing at point".
- "list uses of thing at point".
- "return to buffer/position before the last jump".

And on the backend side we have:
- identifier-at-point-function
- find-definition-function (with an argument to decide whether we want
  a whole list or just the best/first candidate).
- find-uses-function

Etags.el currently offers some additional functionality:
- jump to definition of any identifier, with TAB-completion.
- jump to file (among those listed in the TAGS file), tho currently
  without TAB-completion.
AFAICT, the first could be implemented just in the generic code, except
for TAB-completion.
And the second one is also provided by things like projectile (among
many other thingies), tho it seems that none of the bundled Emacs
packages provide a good solution for that common need.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03  2:22     ` Stefan Monnier
@ 2014-11-03  7:03       ` Helmut Eller
  2014-11-03  7:44       ` Jorgen Schaefer
  2014-11-03 14:46       ` Generalizing find-definition Stephen Leake
  2 siblings, 0 replies; 172+ messages in thread
From: Helmut Eller @ 2014-11-03  7:03 UTC (permalink / raw)
  To: emacs-devel

On Sun, Nov 02 2014, Stefan Monnier wrote:

>> In my experience, tags-loop-continue is rather hard to use and I have
>> long argued to get rid of it and replace it with a better UI.
>> E.g. tags-loop-continue must be pressed multiple times just to find out
>> at the end that none of the offered candidates was relevant.  SLIME does
>> it differently: the list of all candidates is displayed in a separate
>> buffer with one candidate per line; a bit like the results of a search
>> engine like Google.  The user must then move the cursor to the
>> interesting line and press RET to actually jump to the definition.  If
>> there's only a single candidate, then there's no need to display the
>> list and we can jump to the candidate right away.  SLIME has no analog
>> to tags-loop-continue (and no key binding for it) because it's not
>> needed; at least nobody ever asked for such a command.
>
> I agree that tags-loop-continue is not super convenient.
> What key-binding does SLIME use to get this list buffer (which would
> most naturally be implemented as a kind of grep/compilation-mode buffer)?
> Would C-u M-. be usable for that?

M-. brings up the list (if there are multiple candidates; if there's a
single candidate then no list is displayed).  We use C-u M-. to read the
symbol from the minibuffer (as opposed to parsing it from context around
point).

We also have a group of bindings for similar but not so important
commands:

 C-c C-w c  -- who calls (lists callers)
 C-c C-w w  -- calls who (lists functions called by the current function)
 C-c C-w r  -- who references (for global variables)
 C-c C-w s  -- who sets (for global variables)
 C-c C-w b  -- who binds (for dynamic variables)
 C-c C-w s  -- who specializes (lists methods specialized for a specific class)
 C-c <      -- list callers
 C-c >      -- list callees

The C-c < and C-c C-w c do conceptually the same but one is implemented
with an index (like tags) and the other by scanning and inspecting
function objects on the heap.  Different commands for historical
reasons.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 22:26 ` Stephen Leake
@ 2014-11-03  7:31   ` Jorgen Schaefer
  2014-11-03  8:13     ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-03  7:31 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

On Sun, 02 Nov 2014 17:26:08 -0500
Stephen Leake <stephen_leake@stephe-leake.org> wrote:

> Jorgen Schaefer <forcer@forcix.cx> writes:
> 
> > Changes to elisp-mode.el:
> >
> > Provide a value for `find-definition-function' which finds the
> > definition of the symbol at point.
> 
> +1
> 
> I have my own hack for this; It requires me to distinguish between
> variable and defun names and invoke separate keys; I hope you can
> remove that annoyance.

I'm not sure how I can remove that annoyance - is that not
language-dependent? What kind of functionality would you require there?

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03  2:22     ` Stefan Monnier
  2014-11-03  7:03       ` Helmut Eller
@ 2014-11-03  7:44       ` Jorgen Schaefer
  2014-11-03 14:17         ` Stephen Leake
  2014-11-03 14:30         ` Stefan Monnier
  2014-11-03 14:46       ` Generalizing find-definition Stephen Leake
  2 siblings, 2 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-03  7:44 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

On Sun, 02 Nov 2014 21:22:03 -0500
Stefan Monnier <monnier@iro.umontreal.ca> wrote:

> > The idea would be to simply swap M-, and M-*, so M-* would then be
> > `tags-loop-continue'.
> 
> I'm not sure what would be the benefit of such a swap.

The only benefit would be that M-, is now "go back after M-.", which is
what a lot of the packages that redefine M-. do to the key. Hence the
suggestion to swap the two keys, to unify that apparently rather
widespread use of M-,. If Emacs would prefer to keep M-, as is, that is
fine with me, too.

> What key-binding does SLIME use to get this list buffer (which would
> most naturally be implemented as a kind of grep/compilation-mode
> buffer)? Would C-u M-. be usable for that?

All of SLIME, slime-nav, Geiser and Cider use C-u M-. to prompt the
user for a symbol name to go to instead of using the symbol at point,
so that might be a useful generalized function if we do not need the
prefix for the "next" feature of etags.

> > ada-mode also has "show all references of name at point", which
> > currently pops up a buffer in compilation-mode, showing
> > compilation-style locations of all uses.  That's similar
> > functionality to find-tag/tags-loop-continue, and similar UI to
> > SLIME.
> 
> Sounds like we agree.
> 
> So on the UI side we mostly have:
> - "jump to *the* definition of thing at point".
> - "list definitions of thing at point".

Agreed. So far, I would say this is the same key binding (M-.)
which then either jumps to the definition if there is one, or lists them
if there is more than one.

> - "list uses of thing at point".

What key binding would you suggest for that? SLIME apparently uses M-_
and M-? (i.e. the key right of . in different keyboard layouts).

> - "return to buffer/position before the last jump".

Agreed.

> And on the backend side we have:
> - identifier-at-point-function

Why do we need this? It does not make much sense for this functionality
in languages with combined identifiers. For example, in Python,
"foo.bar()" can be pretty much anything, it depends heavily on the
context. Libraries for introspection expect a position in a file/buffer,
not an identifier, to find the definitions.

> - find-definition-function (with an argument to decide whether we want
>   a whole list or just the best/first candidate).

I am not sure if it is generally easily possible to sort the list of
candidates like that. I guess tags often has the problem of knowing
more than one possible candidate, so it would be preferable for tags to
default to a single destination?

> Etags.el currently offers some additional functionality:
> - jump to definition of any identifier, with TAB-completion.

C-u M-. could call a separate function when set, which would be
provided by the mode author to prompt for an identifier (with tab
completion if possible) and goes to the definition of that symbol.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03  7:31   ` Jorgen Schaefer
@ 2014-11-03  8:13     ` Helmut Eller
  2014-11-03 13:49       ` Stephen Leake
  2014-11-03 17:58       ` Jorgen Schaefer
  0 siblings, 2 replies; 172+ messages in thread
From: Helmut Eller @ 2014-11-03  8:13 UTC (permalink / raw)
  To: emacs-devel

On Mon, Nov 03 2014, Jorgen Schaefer wrote:

>> I have my own hack for this; It requires me to distinguish between
>> variable and defun names and invoke separate keys; I hope you can
>> remove that annoyance.
>
> I'm not sure how I can remove that annoyance - is that not
> language-dependent?  What kind of functionality would you require there? 

One way to solve this is to merge the list of candidates for functions
and variables.  At least that's what we do in SLIME.  In elisp-mode
M-. could combine the candidates of find-function and find-variable.
Most of the time the merged list would only contain a single candidate.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03  8:13     ` Helmut Eller
@ 2014-11-03 13:49       ` Stephen Leake
  2014-11-03 17:58       ` Jorgen Schaefer
  1 sibling, 0 replies; 172+ messages in thread
From: Stephen Leake @ 2014-11-03 13:49 UTC (permalink / raw)
  To: emacs-devel

Helmut Eller <eller.helmut@gmail.com> writes:

> On Mon, Nov 03 2014, Jorgen Schaefer wrote:
>
>>> I have my own hack for this; It requires me to distinguish between
>>> variable and defun names and invoke separate keys; I hope you can
>>> remove that annoyance.
>>
>> I'm not sure how I can remove that annoyance - is that not
>> language-dependent?  What kind of functionality would you require there? 
>
> One way to solve this is to merge the list of candidates for functions
> and variables.  At least that's what we do in SLIME.  In elisp-mode
> M-. could combine the candidates of find-function and find-variable.
> Most of the time the merged list would only contain a single candidate.

+1

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03  7:44       ` Jorgen Schaefer
@ 2014-11-03 14:17         ` Stephen Leake
  2014-11-03 14:30         ` Stefan Monnier
  1 sibling, 0 replies; 172+ messages in thread
From: Stephen Leake @ 2014-11-03 14:17 UTC (permalink / raw)
  To: emacs-devel

Jorgen Schaefer <forcer@forcix.cx> writes:

> Stefan Monnier <monnier@iro.umontreal.ca> wrote:
>
>> And on the backend side we have:
>> - identifier-at-point-function
>
> Why do we need this? It does not make much sense for this functionality
> in languages with combined identifiers. For example, in Python,
> "foo.bar()" can be pretty much anything, it depends heavily on the
> context. Libraries for introspection expect a position in a file/buffer,
> not an identifier, to find the definitions.

ada-mode has ada-identifier-at-point; it returns a string suitable for
the external cross-reference engine.

On the other hand, 'ada-goto-declaration' (which would be used for
'find-definition-function') calls ada-identifier-at-point (and similarly
for other cases), so I don't think this needs to be split out as an API
function.

>> - find-definition-function (with an argument to decide whether we want
>>   a whole list or just the best/first candidate).
>
> I am not sure if it is generally easily possible to sort the list of
> candidates like that. I guess tags often has the problem of knowing
> more than one possible candidate, so it would be preferable for tags to
> default to a single destination?

I don't ever want the back-end to guess for me without giving me the
option to override; if there's a list, I want to see it, either as an
icomplete style completion list, or in a buffer.

If the backend can put some order on the list (last used is usually
good), that is fine.

>> Etags.el currently offers some additional functionality:
>> - jump to definition of any identifier, with TAB-completion.
>
> C-u M-. could call a separate function when set, which would be
> provided by the mode author to prompt for an identifier (with tab
> completion if possible) and goes to the definition of that symbol.

It might also be useful to have find-definition-function take an
optional 'identifier' argument. Then other code that finds identifiers
could use it.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03  7:44       ` Jorgen Schaefer
  2014-11-03 14:17         ` Stephen Leake
@ 2014-11-03 14:30         ` Stefan Monnier
  2014-11-03 18:28           ` Jorgen Schaefer
  2014-11-06 15:22           ` Dmitry Gutov
  1 sibling, 2 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-03 14:30 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: emacs-devel

> The only benefit would be that M-, is now "go back after M-.", which is
> what a lot of the packages that redefine M-. do to the key.  Hence the
> suggestion to swap the two keys, to unify that apparently rather
> widespread use of M-,.  If Emacs would prefer to keep M-, as is, that is
> fine with me, too.

I see, so it's compatibility with other packages vs. compatibility with
etags.el.  Luckily, I think there is no hurry to make this choice.
So we could poll the users.

>> So on the UI side we mostly have:
>> - "jump to *the* definition of thing at point".
>> - "list definitions of thing at point".
> Agreed. So far, I would say this is the same key binding (M-.)
> which then either jumps to the definition if there is one, or lists them
> if there is more than one.

IIUC the first is only used when there's only one definition.  I guess
it's OK, and it reduces the pressure on key-bindings.

>> - "list uses of thing at point".
> What key binding would you suggest for that? SLIME apparently uses M-_
> and M-? (i.e. the key right of . in different keyboard layouts).

AFAICT there're both unbound currently, so either is fine.  Of course,
we could also re-use M-. for that (see below).

>> And on the backend side we have:
>> - identifier-at-point-function
> Why do we need this?

I thought we already agreed that we need a language-aware way to
determine what is the identifier at point (taking into account the
current namespace/package/etc..).

> It does not make much sense for this functionality in languages with
> combined identifiers.  For example, in Python, "foo.bar()" can be
> pretty much anything, it depends heavily on the context.  Libraries for
> introspection expect a position in a file/buffer, not an identifier,
> to find the definitions.

But if we also want the user to type the identifier via `C-u M-.', then
we need find-definition-function to be able to deal with a string rather
than a buffer position.  So let's simply say that
find-definition-function will be called either with a string or with
a buffer position.  If the function prefers only working from strings,
it can call identifier-at-point-function to turn the buffer position
into a string.

>> - find-definition-function (with an argument to decide whether we want
>> a whole list or just the best/first candidate).
> I am not sure if it is generally easily possible to sort the list of
> candidates like that.  I guess tags often has the problem of knowing
> more than one possible candidate, so it would be preferable for tags to
> default to a single destination?

In etags.el, it doesn't take noticeably more time to get the full list
rather than just the first match, so I guess it's OK if we just always
return the full list.

>> Etags.el currently offers some additional functionality:
>> - jump to definition of any identifier, with TAB-completion.
> C-u M-. could call a separate function when set, which would be
> provided by the mode author to prompt for an identifier (with tab
> completion if possible) and goes to the definition of that symbol.

Right.  I guess the prompting can be part of the generic code, and the
completion-table can be provided by an appropriate foo-function set by
the backend providing find-definition-function.

[ Side note: I expect that identifier-at-point-function would pretty
  much always be provided by the major mode, but I expect that
  find-definition-function (as well as the TAB-completion function) will
  sometimes be provided by the major mode, and sometimes by
  a mode-agnostic package (like etags.el).  ]

Helmut Eller <eller.helmut@gmail.com> writes:
> We also have a group of bindings for similar but not so important
> commands:
>
>  C-c C-w c  -- who calls (lists callers)
>  C-c C-w w  -- calls who (lists functions called by the current function)
>  C-c C-w r  -- who references (for global variables)
>  C-c C-w s  -- who sets (for global variables)
>  C-c C-w b  -- who binds (for dynamic variables)
>  C-c C-w s  -- who specializes (lists methods specialized for a specific class)
>  C-c <      -- list callers
>  C-c >      -- list callees

Right, this brings up two issues:
- distinguish different kinds of uses: definitions, accesses,
  assignments, refinements (via advice-add, defmethod, ...), let-binds, ...
- distinguish different categories of "identifiers" (e.g. classes,
  variables, functions, types, methods, ...).

So far the proposal is to forget about the second distinction, tho if we
pass buffer-positions to find-definition-function,
find-definition-function can make those distinctions on its own.
We could additionally have some kind of convention for disambiguation
when using identifier strings, e.g. type "foobar variable" in the
minibuffer prompt to state explicitly that you're interested in the
foobar variable rather than the foobar type.

For the first distinction, at the UI level we could do:
- Specify the kind of use by the key-binding (M-. to find
  definitions, M-? to find uses).  This doesn't work too well if there
  are many more refinements.
- Specify the kind of use via a minibuffer prompt.
  E.g. C-u M-. would first prompt for an identifier (defaulting to the
  one at point), and then prompt for the kind of use to look for.
- Specify the use by filtering in the "found matches" buffer.
  For "definition", this kind of sucks since in many cases there'd be
  only one definition, but you'd first have to go through the complete
  list displayed in a buffer, and then use some key-binding to filter
  the sole definition out of that.
- A mix of the above.  E.g. M-. searches only for one particular subset
  of uses (e.g. definitions and refinements), while M-? search for the
  complementary subset (or for all possible uses, including the ones
  already provided under M-.), and after M-? you can filter the results
  in the buffer.

At the backend level, in order to accommodate those different possible
UIs, the find-definition-function would seem to need to receive 2 args:
one specifying the identifier, and the other specifying the kind of uses
and the elements of the returned list should not just be positions but
pairs of "position and use-kind".


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03  2:22     ` Stefan Monnier
  2014-11-03  7:03       ` Helmut Eller
  2014-11-03  7:44       ` Jorgen Schaefer
@ 2014-11-03 14:46       ` Stephen Leake
  2014-11-03 16:42         ` Stefan Monnier
  2 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-11-03 14:46 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>>> M-. RET  does "find the callers of a symbol at point", AFAICT.
>
> Duh, sorry for being dense, of course it doesn't.  etags does not offer
> the needed info, so the etags.el UI doesn't have such a thing.

In emacs -Q, M-. is 'find-tag', which prompts for a tag with the default
being the identifer at point, and goes to the first occurrence of that
identifier in the tags table. Then tags-loop-continue goes to the next
occurrence.

The occurrences will include the calling locations; perhaps that is what
was meant here.

In other situations, "find the callers" would mean go to the start or
declaration of the calling functions, not the point of the call.

ada-mode provides "show all references", which is the same as "show all
calls" for functions. It does not currently have "show all callers".

> So on the UI side we mostly have:
> - "jump to *the* definition of thing at point".
> - "list definitions of thing at point".

    These two should be combined; only show the list if it has more than
    one item. The point is that in most cases there is only one, so
    showing the list is just an annoyance.
    
> - "list uses of thing at point".

    This should show the list if there is only one; that's not typical,
    and is important information.
    
> - "return to buffer/position before the last jump".

I'm not clear this should be separate from the mark ring (push-mark,
pop-mark).

> And on the backend side we have:
> - identifier-at-point-function

    This probably does not need to be separate; the other functions will
    call the equivalent backend function.

> - find-definition-function (with an argument to decide whether we want
>   a whole list or just the best/first candidate).

    I don't think we need that argument. Instead, it could take an
    optional argument 'identifier', which would be used instead of
    indentifier-at-point.
    
> - find-uses-function

> Etags.el currently offers some additional functionality:
> - jump to definition of any identifier, with TAB-completion.

    This implies that the backend has to provide the list of identifiers
    for completion; that's a new backend function 'complete-identifier'.

    ada-mode doesn't have that now, but it could be useful.

    It could also get very slow; the list of identifiers can be very
    large. I guess that's no different from the current tags usage.
    
> - jump to file (among those listed in the TAGS file), tho currently
>   without TAB-completion.

    Another backend function; 'complete-file-name'.

    Or it could be 'project-directories' and 'project-extensions', to
    be used with ff-get-file.

    ada-mode uses compilation-search-path for 'project-directories', and
    ada-body-suffixes, ada-spec-suffixes for 'project-extensions.

    ada-mode does not currently provide jump to file with prompt, only
    jump to file derived from identifier at point. Prompt would be
    useful.
    
> And the second one is also provided by things like projectile (among
> many other thingies), tho it seems that none of the bundled Emacs
> packages provide a good solution for that common need.

I think ff-get-file is good. But maybe others want a more restricted list?

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 14:46       ` Generalizing find-definition Stephen Leake
@ 2014-11-03 16:42         ` Stefan Monnier
  2014-11-04 15:39           ` Stephen Leake
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-03 16:42 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

>> And the second one is also provided by things like projectile (among
>> many other thingies), tho it seems that none of the bundled Emacs
>> packages provide a good solution for that common need.
> I think ff-get-file is good. But maybe others want a more restricted list?

Not only ff-get-file is a function, not a command, but I don't think it
provides the same functionality.  E.g. it doesn't look recursively
inside subdirs.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03  8:13     ` Helmut Eller
  2014-11-03 13:49       ` Stephen Leake
@ 2014-11-03 17:58       ` Jorgen Schaefer
  2014-11-04 15:54         ` Stephen Leake
  1 sibling, 1 reply; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-03 17:58 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On Mon, 03 Nov 2014 09:13:45 +0100
Helmut Eller <eller.helmut@gmail.com> wrote:

> On Mon, Nov 03 2014, Jorgen Schaefer wrote:
> 
> >> I have my own hack for this; It requires me to distinguish between
> >> variable and defun names and invoke separate keys; I hope you can
> >> remove that annoyance.
> >
> > I'm not sure how I can remove that annoyance - is that not
> > language-dependent?  What kind of functionality would you require
> > there? 
> 
> One way to solve this is to merge the list of candidates for functions
> and variables.  At least that's what we do in SLIME.  In elisp-mode
> M-. could combine the candidates of find-function and find-variable.
> Most of the time the merged list would only contain a single
> candidate.

Why would the proposed find-definition interface make a distinction
between variables and functions at all? So far, there has been none
proposed?

Does that mean that this is not an issue? That'd be great :-)

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 14:30         ` Stefan Monnier
@ 2014-11-03 18:28           ` Jorgen Schaefer
  2014-11-03 20:09             ` Stefan Monnier
  2014-11-06 15:22           ` Dmitry Gutov
  1 sibling, 1 reply; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-03 18:28 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

On Mon, 03 Nov 2014 09:30:56 -0500
Stefan Monnier <monnier@iro.umontreal.ca> wrote:

> >> And on the backend side we have:
> >> - identifier-at-point-function
> > Why do we need this?
> 
> I thought we already agreed that we need a language-aware way to
> determine what is the identifier at point (taking into account the
> current namespace/package/etc..).

For Python, there is nothing suitable for "identifier at point" that I
can return that would help find the definition, except "the whole
contents of the buffer, the file name, and the position of point within
the buffer".

To get the definition of something, I pass that - the full text, the
file name, and the position - to a Python library and get back a list of
locations.

There is no intermediate "get the identifier at point" step, because
that concept does not make sense here.

> >> Etags.el currently offers some additional functionality:
> >> - jump to definition of any identifier, with TAB-completion.
> > C-u M-. could call a separate function when set, which would be
> > provided by the mode author to prompt for an identifier (with tab
> > completion if possible) and goes to the definition of that symbol.
> 
> Right.  I guess the prompting can be part of the generic code, and the
> completion-table can be provided by an appropriate foo-function set by
> the backend providing find-definition-function.

For Python, I do provide a tab-completable list of identifiers
(currently only for pydoc, but the code should be reusable for this),
but it is not a simple list of identifiers, but a tree. The tab
completion starts with top-level modules (like "json"), but when you
add a ".", it will continue to complete attributes of the module or
class etc.. Providing the full list of possible completions right
away would take a very long time. This kind of completion is tricky to
generalize, so if we want the user to be able to type a symbol, we
should allow the backend to override this. We can provide a simple
default for the trivial case, though.

So as I see it, `find-definition' would usually call the value of
`find-definition-function' with no arguments. If that function returns
an empty list, or if `find-definition' was called with a prefix
argument, it would use a function from the backend that prompts for
a symbol and pass the result of that to `find-definition-function'.

Alternatively, the backend simply provides one function that prompts
the user for an identifier and returns the list of uses instead of the
dance above.

> [ Side note: I expect that identifier-at-point-function would pretty
>   much always be provided by the major mode, but I expect that
>   find-definition-function (as well as the TAB-completion function)
>   will sometimes be provided by the major mode, and sometimes by
>   a mode-agnostic package (like etags.el).  ]

Or even a minor mode that provide extra functionality for a major mode -
Elpy is a minor mode which is active in Python mode buffers.

> For the first distinction, at the UI level we could do:
> - Specify the kind of use by the key-binding (M-. to find
>   definitions, M-? to find uses).  This doesn't work too well if there
>   are many more refinements.
> - Specify the kind of use via a minibuffer prompt.
>   E.g. C-u M-. would first prompt for an identifier (defaulting to the
>   one at point), and then prompt for the kind of use to look for.
> - Specify the use by filtering in the "found matches" buffer.
>   For "definition", this kind of sucks since in many cases there'd be
>   only one definition, but you'd first have to go through the complete
>   list displayed in a buffer, and then use some key-binding to filter
>   the sole definition out of that.
> - A mix of the above.  E.g. M-. searches only for one particular
> subset of uses (e.g. definitions and refinements), while M-? search
> for the complementary subset (or for all possible uses, including the
> ones already provided under M-.), and after M-? you can filter the
> results in the buffer.

This is getting extremely complex and I do not see how this would
benefit the user much. There are definitions of an identifier, and uses
of an identifier.

Typical IDEs make a distinction between these, too. For example,
Eclipse provides "go to definition or declaration" on F3 or <C-left> on
a symbol.

The *uses* of an identifier are quite tricky. IDEs provide various
tools for that, including various searches, call graphs, highlighting
occurences, etc.

There are usually very few definitions, and M-. should just go there.
The "find uses of the identifier at point" functionality seems quite
distinct from a user's point of view, so should be distinct in the user
interface, too.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 18:28           ` Jorgen Schaefer
@ 2014-11-03 20:09             ` Stefan Monnier
  2014-11-03 20:55               ` Jorgen Schaefer
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-03 20:09 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: emacs-devel

> For Python, there is nothing suitable for "identifier at point" that I

Yet, you say that you offer a tab-completable selection of identifiers.
So obviously, there is a suitable "identifier at point".

Do you mean by the above not that the concept is meaningless but that
it's difficult to write a function that reliably returns the appropriate
identifier at point?

If so, I can definitely agree that for some modes it can be difficult,
and it should not be necessary to write such a function if the backend
can use a buffer-position instead (and delegate the hard work to some
external tool).

But having such an identifier-at-point-function can be useful for all
kinds of things (typically default for minibuffer inputs of various
commands), so it makes a lot of sense to standardize it.

> For Python, I do provide a tab-completable list of identifiers
> (currently only for pydoc, but the code should be reusable for this),
> but it is not a simple list of identifiers, but a tree. The tab
> completion starts with top-level modules (like "json"), but when you
> add a ".", it will continue to complete attributes of the module or
> class etc.. Providing the full list of possible completions right
> away would take a very long time.

Of course.  But such completion is nothing new.  File names are
completed this way.  Bzr revision names are completed this way as well.
No need for a special completion command, you can do that just fine with
just a completion-table (and then partial-completion style will let you
complete "js.fo" to anything that matches "js*.fo*").

Note that such completion tables are clearly not lists of strings, but
they're functions (actually, they're objects represented as functions,
for lack of an object system).

> This is getting extremely complex and I do not see how this would
> benefit the user much.

As long as the "set of use-kinds" and the "set of identifier categories"
is determined by the backend itself, I don't think it increases
complexity of the backends or the backend API very much.

And there are clearly example of existing systems which do provide such
refinements (e.g. find-variable vs find-function on Elisp, and the C-c
C-w s versus C-c C-w b versus C-c C-w r in SLIME), so it makes sense to
include it in the design of the backend API.

> There are definitions of an identifier, and uses of an identifier.

Yes, clearly this is the main distinction and we should focus on this
functionality w.r.t designing the UI.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 20:09             ` Stefan Monnier
@ 2014-11-03 20:55               ` Jorgen Schaefer
  2014-11-03 22:38                 ` Stefan Monnier
                                   ` (3 more replies)
  0 siblings, 4 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-03 20:55 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

On Mon, 03 Nov 2014 15:09:40 -0500
Stefan Monnier <monnier@iro.umontreal.ca> wrote:

> > For Python, there is nothing suitable for "identifier at point"
> > that I
> 
> Yet, you say that you offer a tab-completable selection of
> identifiers. So obviously, there is a suitable "identifier at point".

The tab completion I talked about is for global modules and their
contents and has no access to locally-available identifiers. It is a
feature completely unrelated to finding the definition of the
identifier at point.

> Do you mean by the above not that the concept is meaningless but that
> it's difficult to write a function that reliably returns the
> appropriate identifier at point?

I suspect we mean the same thing, but just to clarify.

Consider a piece of code like this:

    from foo import Foo

    bar = Foo()
    bar.baz_|_

M-. should go to the definition of the attribute baz of the class Foo.
But the obvious "identifier at point" is "bar.baz", which by itself
does not have any relationship to said method without the assignment
"bar = Foo()", which by itself is also not meaningful without the
import statement. The only single string that reliably would allow to
find the correct definition would be "foo.Foo.baz", but I do not think
that anyone would consider that to be "the identifier at point" here.

So what I am saying is that the only representation for "the identifier
at point" that allows finding the definition of said identifier is the
triple (file name, full buffer contents, position within the buffer
contents). There is no simpler "identifier at point".

> If so, I can definitely agree that for some modes it can be difficult,
> and it should not be necessary to write such a function if the backend
> can use a buffer-position instead (and delegate the hard work to some
> external tool).
>
> But having such an identifier-at-point-function can be useful for all
> kinds of things (typically default for minibuffer inputs of various
> commands), so it makes a lot of sense to standardize it.

Emacs can and probably should standardize it, but unless it is useful
for the interface of finding the definition of the thing at point, it
would seem like a good idea not to do it in the library being discussed
here.

> Note that such completion tables are clearly not lists of strings, but
> they're functions (actually, they're objects represented as functions,
> for lack of an object system).

What is the advantage of having the backend define a function that
returns a completion table as opposed to a function that prompts the
user for a symbol? The saved code seems minimal, and it means the
backend can not set the prompt, for example.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 20:55               ` Jorgen Schaefer
@ 2014-11-03 22:38                 ` Stefan Monnier
  2014-11-04 14:52                   ` Stephen Leake
  2014-11-06 15:33                   ` Dmitry Gutov
  2014-11-03 22:39                 ` Stefan Monnier
                                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-03 22:38 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: emacs-devel

> The only single string that reliably would allow to find the correct
> definition would be "foo.Foo.baz", but I do not think that anyone
> would consider that to be "the identifier at point" here.

I would.

>> Note that such completion tables are clearly not lists of strings, but
>> they're functions (actually, they're objects represented as functions,
>> for lack of an object system).
> What is the advantage of having the backend define a function that
> returns a completion table as opposed to a function that prompts the
> user for a symbol? The saved code seems minimal, and it means the
> backend can not set the prompt, for example.

It means the UI can use it for various kinds of completion (e.g. for
completion-at-point-function).


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 20:55               ` Jorgen Schaefer
  2014-11-03 22:38                 ` Stefan Monnier
@ 2014-11-03 22:39                 ` Stefan Monnier
  2014-11-04 14:58                   ` Stephen Leake
  2014-11-03 23:46                 ` Stephen J. Turnbull
  2014-11-04  2:52                 ` Yuri Khan
  3 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-03 22:39 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: emacs-devel

> What is the advantage of having the backend define a function that
> returns a completion table as opposed to a function that prompts the
> user for a symbol? The saved code seems minimal, and it means the
> backend can not set the prompt, for example.

Another reason is the general rule that the backend should not do
UI tasks and prompting is definitely a UI operation.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 20:55               ` Jorgen Schaefer
  2014-11-03 22:38                 ` Stefan Monnier
  2014-11-03 22:39                 ` Stefan Monnier
@ 2014-11-03 23:46                 ` Stephen J. Turnbull
  2014-11-04  7:58                   ` Jorgen Schaefer
  2014-11-04  2:52                 ` Yuri Khan
  3 siblings, 1 reply; 172+ messages in thread
From: Stephen J. Turnbull @ 2014-11-03 23:46 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: Stefan Monnier, emacs-devel

Jorgen Schaefer writes:

 > The only single string that reliably would allow to find the
 > correct definition would be "foo.Foo.baz", but I do not think that
 > anyone would consider that to be "the identifier at point" here.

Why not?  Emacs's practical definition of "identifier at point"
doesn't need to be the same as that of the programming language's
grammar.  I have no trouble taking both points of view, although
usually at different times.

 > So what I am saying is that the only representation for "the identifier
 > at point" that allows finding the definition of said identifier is the
 > triple (file name, full buffer contents, position within the buffer
 > contents). There is no simpler "identifier at point".

In some sense, you always need full buffer contents except for the
very simplest of programs.




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 20:55               ` Jorgen Schaefer
                                   ` (2 preceding siblings ...)
  2014-11-03 23:46                 ` Stephen J. Turnbull
@ 2014-11-04  2:52                 ` Yuri Khan
  2014-11-04  7:41                   ` Jorgen Schaefer
  3 siblings, 1 reply; 172+ messages in thread
From: Yuri Khan @ 2014-11-04  2:52 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: Stefan Monnier, Emacs developers

On Tue, Nov 4, 2014 at 2:55 AM, Jorgen Schaefer <forcer@forcix.cx> wrote:

> Consider a piece of code like this:
>
>     from foo import Foo
>
>     bar = Foo()
>     bar.baz_|_
>
> M-. should go to the definition of the attribute baz of the class Foo.
> But the obvious "identifier at point" is "bar.baz", which by itself
> does not have any relationship to said method without the assignment
> "bar = Foo()", which by itself is also not meaningful without the
> import statement. The only single string that reliably would allow to
> find the correct definition would be "foo.Foo.baz", but I do not think
> that anyone would consider that to be "the identifier at point" here.s

How are you solving (or going to solve) the problem of dynamic/duck typing?

Consider:

```python
def get_content(file_like):
    return file_like.read_|_()
```

Here, in a function that accepts any file-like object, “Find
definition” for read() might mean might mean file.read, or
StringIO.read, or zipfile.ZipExtFile.read, or a read() method of any
of a multitude classes defined in various libraries.

Showing all definitions of read() method of all classes defined in the
transitive closure of modules imported by the main program is
moderately easy but not helpful to the user (e.g. because some classes
are never passed in and their read() implements a different semantic).

Determining which exact subset of read() methods can be available at
the given location in code, I suspect, amounts to solving the Halting
Problem.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-04  2:52                 ` Yuri Khan
@ 2014-11-04  7:41                   ` Jorgen Schaefer
  0 siblings, 0 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-04  7:41 UTC (permalink / raw)
  To: Yuri Khan; +Cc: Emacs developers

On Tue, 4 Nov 2014 09:52:28 +0700
Yuri Khan <yuri.v.khan@gmail.com> wrote:

> On Tue, Nov 4, 2014 at 2:55 AM, Jorgen Schaefer <forcer@forcix.cx>
> wrote:
> 
> > Consider a piece of code like this:
> >
> >     from foo import Foo
> >
> >     bar = Foo()
> >     bar.baz_|_
> >
> > [...]
>
> How are you solving (or going to solve) the problem of dynamic/duck
> typing?

Using the Jedi or Rope libraries. They try to guess the identifier at
point (in the example above they can, in your example they can't).

You can try this already today using e.g. Elpy:

https://github.com/jorgenschaefer/elpy

In Elpy, M-. already works as described here.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 23:46                 ` Stephen J. Turnbull
@ 2014-11-04  7:58                   ` Jorgen Schaefer
  0 siblings, 0 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-04  7:58 UTC (permalink / raw)
  To: Stephen J. Turnbull; +Cc: emacs-devel

On Tue, 04 Nov 2014 08:46:33 +0900
"Stephen J. Turnbull" <stephen@xemacs.org> wrote:

> Jorgen Schaefer writes:
> 
>  > The only single string that reliably would allow to find the
>  > correct definition would be "foo.Foo.baz", but I do not think that
>  > anyone would consider that to be "the identifier at point" here.
> 
> Why not?

The "foo.Foo.baz" in this example would be an error at that point in
the program, and not all things that are defined have such a fully
qualified name in the first place. This all seems a bit
counter-intuitive for something called "identifier at point".

> Emacs's practical definition of "identifier at point"
> doesn't need to be the same as that of the programming language's
> grammar.  I have no trouble taking both points of view, although
> usually at different times.

I have no problem with that, either - we can call it Frobble, Blubber
or Moof, too, if we like. We use words so others understand us, and
redefining them more or less randomly is not helping with communication.

We have gone full circle and instead of asking "can an 'identifier at
point' be used to find the definition of the thing at point", we have
defined "identifier at point" to mean "that string (or object?) which
can be used to find the definition of the thing at point". Now that is
fine, mind you, we can use language however we like, but we will cause
quite some confusion with that.

For example, some people might think "oh hey, I have a function that
returns an identifier at point, and I am supposed to be able to pass
that string to the other function which returns the definitions of that
identifier. Let's write a function which receives an identifier and
returns where it is used."

But the way we defined "identifier at point", that does not necessarily
allow that.

Confusion all around just because we went for re-defining a term that
is usually used in other ways.

> In some sense, you always need full buffer contents except for the
> very simplest of programs.

Exactly. Which is why I'm saying that I do not see the need or use for
an indirection via a "get identifier at point" function for the
proposed library. Which is pretty much all I was trying to say up to the
semantics discussion on the number of ways we can define what
"identifier at point" could possibly mean.

So let's drop that idea and the semantics discussion.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 22:38                 ` Stefan Monnier
@ 2014-11-04 14:52                   ` Stephen Leake
  2014-11-04 18:12                     ` Stefan Monnier
  2014-11-06 15:33                   ` Dmitry Gutov
  1 sibling, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-11-04 14:52 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> > from foo import Foo

> > bar = Foo()
> > bar.baz_|_

>> The only single string that reliably would allow to find the correct
>> definition would be "foo.Foo.baz", but I do not think that anyone
>> would consider that to be "the identifier at point" here.
>
> I would.

I would also, but I would not expect a generic Emacs function to return
that. It certainly requires a backend function.

I'm still not clear what 'identifier-at-point' is good for outside "goto
definition", "show uses", and completion. Since the meaning is highly
backend specific, I don't see what other parts of Emacs might use it.

You say there are other uses - can you list some?

In ada-mode, 'ada-identifier-at-point' is used in (ada-mode doesn't
provide completion yet):

ada-goto-declaration

    aka find-definition-function
    
ada-show-references

    aka find-uses-function
    
ada-show-declaration-parents

    If identifier-at-point is a type, show the definition of the types
    it inherits from.
    
ada-show-overriding

    If identifier-at-point is a function, show definitions
    of functions that override it.
    
ada-show-overridden

    If identifier-at-point is a function, show definitions
    of the function that it overrides.

The last three are additional candidates for the API.

>>> Note that such completion tables are clearly not lists of strings, but
>>> they're functions (actually, they're objects represented as functions,
>>> for lack of an object system).
>> What is the advantage of having the backend define a function that
>> returns a completion table as opposed to a function that prompts the
>> user for a symbol? The saved code seems minimal, and it means the
>> backend can not set the prompt, for example.
>
> It means the UI can use it for various kinds of completion (e.g. for
> completion-at-point-function).

+1

ada-mode would provide a tree as well.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 22:39                 ` Stefan Monnier
@ 2014-11-04 14:58                   ` Stephen Leake
  0 siblings, 0 replies; 172+ messages in thread
From: Stephen Leake @ 2014-11-04 14:58 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> What is the advantage of having the backend define a function that
>> returns a completion table as opposed to a function that prompts the
>> user for a symbol? The saved code seems minimal, and it means the
>> backend can not set the prompt, for example.
>
> Another reason is the general rule that the backend should not do
> UI tasks and prompting is definitely a UI operation.

+1

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 16:42         ` Stefan Monnier
@ 2014-11-04 15:39           ` Stephen Leake
  2014-11-04 18:14             ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-11-04 15:39 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>>> And the second one is also provided by things like projectile (among
>>> many other thingies), tho it seems that none of the bundled Emacs
>>> packages provide a good solution for that common need.
>> I think ff-get-file is good. But maybe others want a more restricted list?
>
> Not only ff-get-file is a function, not a command, 

It could be wrapped; I was referring to the functionality.

> but I don't think it
> provides the same functionality.  E.g. it doesn't look recursively
> inside subdirs.

the function we are talking about is:

> - jump to file (among those listed in the TAGS file)

So looking recursively inside subdirs is wrong.

The TAGS file contains a list of file:line:column (among other things).
In this context, we only care about the file names.

That list of file names is the source code for the current project (for
some loose definition of 'project').

So the function we are defining is 'goto-file-in-project', with the user
prompted for the file name.

In ada-mode, the definition of 'file in project' is the combination of
compilation-search-path with ada-spec-suffixes, ada-body-suffixes. I
believe EDE projects are similar.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 17:58       ` Jorgen Schaefer
@ 2014-11-04 15:54         ` Stephen Leake
  0 siblings, 0 replies; 172+ messages in thread
From: Stephen Leake @ 2014-11-04 15:54 UTC (permalink / raw)
  To: emacs-devel

Jorgen Schaefer <forcer@forcix.cx> writes:

> On Mon, 03 Nov 2014 09:13:45 +0100
> Helmut Eller <eller.helmut@gmail.com> wrote:
>
>> On Mon, Nov 03 2014, Jorgen Schaefer wrote:
>> 
>> >> I have my own hack for this; It requires me to distinguish between
>> >> variable and defun names and invoke separate keys; I hope you can
>> >> remove that annoyance.
>> >
>> > I'm not sure how I can remove that annoyance - is that not
>> > language-dependent?  What kind of functionality would you require
>> > there? 
>> 
>> One way to solve this is to merge the list of candidates for functions
>> and variables.  At least that's what we do in SLIME.  In elisp-mode
>> M-. could combine the candidates of find-function and find-variable.
>> Most of the time the merged list would only contain a single
>> candidate.
>
> Why would the proposed find-definition interface make a distinction
> between variables and functions at all? So far, there has been none
> proposed?

I agree, it should not.

> Does that mean that this is not an issue? 

No, the problem is the backend functions currently provided by elisp;
function-called-at-point vs variable-at-point. There is no current
elisp function that provides 'identifier-at-point'.

In addition, symbol-file requires 'defun or 'defvar to return the
appropriate file name.

The proposed solution for the elisp backend for
find-definition-function is to call both function-called-at-point and
variable-at-point; if only one gives a result, go there. Otherwise, show
the list of two locations.

That's still not quite right; if point is on an argument to a function,
function-called-at-point will return the function symbol, not the
argument. So we really need just identifier-at-point, which is the first
portion of variable-at-point and function-called-at-point.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-04 14:52                   ` Stephen Leake
@ 2014-11-04 18:12                     ` Stefan Monnier
  2014-11-04 23:13                       ` Stephen Leake
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-04 18:12 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

> I would also, but I would not expect a generic Emacs function to return
> that.  It certainly requires a backend function.

Agreed.

> I'm still not clear what 'identifier-at-point' is good for outside "goto
> definition", "show uses", and completion. Since the meaning is highly
> backend specific, I don't see what other parts of Emacs might use it.

I just know that it's used commonly.  Maybe it's always used by
backend-specific code, so it could stay completely in each backend, but
I don't see any reason why we couldn't standardize the name of that
backend function, in case some non-backend specific code wants to
use it.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-04 15:39           ` Stephen Leake
@ 2014-11-04 18:14             ` Stefan Monnier
  0 siblings, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-04 18:14 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

>> - jump to file (among those listed in the TAGS file)
> So looking recursively inside subdirs is wrong.

The TAGS file can (and in several cases does) contain data about files
in subdirectories.  Basically "jump to file listed in TAGS" lets (or
wants to do) you jump to any file in the current project.

> So the function we are defining is 'goto-file-in-project', with the user
> prompted for the file name.

Exactly.  And I don't think ff-get-file quite does it.

> In ada-mode, the definition of 'file in project' is the combination of
> compilation-search-path with ada-spec-suffixes, ada-body-suffixes. I
> believe EDE projects are similar.

That sounds right.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-04 18:12                     ` Stefan Monnier
@ 2014-11-04 23:13                       ` Stephen Leake
  2014-11-05  2:00                         ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-11-04 23:13 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> I would also, but I would not expect a generic Emacs function to return
>> that.  It certainly requires a backend function.
>
> Agreed.
>
>> I'm still not clear what 'identifier-at-point' is good for outside "goto
>> definition", "show uses", and completion. Since the meaning is highly
>> backend specific, I don't see what other parts of Emacs might use it.
>
> I just know that it's used commonly.  Maybe it's always used by
> backend-specific code, so it could stay completely in each backend, but
> I don't see any reason why we couldn't standardize the name of that
> backend function, in case some non-backend specific code wants to
> use it.

The reason we can't standardize it is that "identifier-at-point" appears
to have different meanings/different results in different situations.

For "goto definition" and "show uses", ada-mode needs the simple word at
point (no dots; 'baz' in the example), together with the file name,
line, and column. "ada-identifier-at-point" only returns the simple
word, not all the information needed by "goto definition". Python needs
the file name, buffer text, line and column; it does not have an
implementation of "identifier-at-point", apparently.

For "completion", Python also apparently needs the same information as
for "goto definition". Ada mode will probably need the same info as for
"goto definition" as well.

None of these make sense for something called "identifier at point".
"xref-info-at-point" might make sense.

So without a list of other uses, it's hard to know how to define
"identifier-at-point".

I grepped thru ada-*.el for "-at-point", and found:

ada-case-adjust-at-point

    Adjust case of the simple word before point according to the casing
    mode (typically capitalize first letter). Does _not_ call
    ada-identifier-at-point. "word" is defined by the syntax classes
    word and symbol. 

ada-identifier-at-point

    discussed above

(thing-at-point 'symbol)

    Used to retrieve the simple word for the external xref engine, when
    in a C++ buffer; same meaning as ada-identifier-at-point. The
    external xref engine used by ada-mode is multi-lingual (see
    gpr-query in the ada-mode sources).

Note that thing-at-point does _not_ have an option 'identifier. Also
note that the meaning of 'symbol is not given anywhere; the docstring
for thing-at-point says "see thingatpt.el", but I don't see a definition
there. So apparently the definition is "whatever (thing-at-point
'symbol) returns", which is not very helpful.

The default implementation of (thing-at-point 'symbol) relies on the
word and symbol syntax classes, which is why it works for C++ mode. That
won't work for Ada mode, because "*" is an identifier for a function
that overrides *. (Hmm, I'm not sure 'symbol works for overloaded C++
operators, now that I think about it).

Note that the default implementation can be overridden by setting symbol
properties. So we could require a backend to do that to return the above
info, so "goto definition" could start by calling (thing-at-point
'symbol). But if we did take that approach, I'd argue it should be
(thing-at-point 'xref) instead.

So I'm still puzzled as to what other uses "identifier-at-point" might
have. If they are all either satisfied by thing-at-point or are strictly
back-end-specific, then we don't need anything new.

I grep'd thru emacs/lisp/* for "identifier-at-point"; there were _no_
occurrences.

I started grep'ing thru emacs/lisp/* for "-at-point". So far I've found
(ignoring things that don't return strings):

allout-parse-symbol-at-point - appears to be specific to allout

highlight-symbol-at-point - uses find-tag-default-as-symbol-regexp,
    which defaults to word and symbol syntax classes; could be
    (thing-at-point 'symbol).

thing-at-point - used by calc, cmuscheme, others

number-at-point - used by calc, defined in thingatpt.el, uses
    (thing-at-point 'sexp)

completion-at-point-functions - bound by functions in semantic, other modes

semantic-completion-at-point-function - defaults to
    semantic-ia-complete-symbol, which uses parse results. I'll call
    that backend-specific.

variable-at-point - used by cus-edit.el, uses word and symbol syntax
    classes, then does some more checks on whether it is a lisp symbol.
    could use (thing-at-point 'symbol)

... and I got tired. So far it's either backend or (thing-at-point
'symbol). There are about 1900 matches; that's a lot of information to
sort thru. 

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-04 23:13                       ` Stephen Leake
@ 2014-11-05  2:00                         ` Stefan Monnier
  0 siblings, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-05  2:00 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

> For "goto definition" and "show uses", ada-mode needs the simple word at
> point (no dots; 'baz' in the example), together with the file name,
> line, and column. "ada-identifier-at-point" only returns the simple
> word, not all the information needed by "goto definition". Python needs
> the file name, buffer text, line and column; it does not have an
> implementation of "identifier-at-point", apparently.

That's OK.  We already agreed that find-definition-function should
accept a buffer-position as argument (from which it can extract any
identifier it likes, if that's what it needs).

> For "completion", Python also apparently needs the same information as
> for "goto definition". Ada mode will probably need the same info as for
> "goto definition" as well.

Completion doesn't need the identifier at point, AFAICT.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 14:30         ` Stefan Monnier
  2014-11-03 18:28           ` Jorgen Schaefer
@ 2014-11-06 15:22           ` Dmitry Gutov
  2014-11-06 16:51             ` Stefan Monnier
  1 sibling, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-11-06 15:22 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel, Jorgen Schaefer

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> The only benefit would be that M-, is now "go back after M-.", which is
>> what a lot of the packages that redefine M-. do to the key.  Hence the
>> suggestion to swap the two keys, to unify that apparently rather
>> widespread use of M-,.  If Emacs would prefer to keep M-, as is, that is
>> fine with me, too.
>
> I see, so it's compatibility with other packages vs. compatibility with
> etags.el.  Luckily, I think there is no hurry to make this choice.
> So we could poll the users.

I believe ergonomics and frequency of use have also been mentioned. And
if find-tag will provide the "multiple occurences" interface on its own,
there won't be a need for the "next tag" command.

So, +1 from me. You might also take into account that elisp-slime-nav is
a relatively popular package: http://melpa.org/#/elisp-slime-nav



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-03 22:38                 ` Stefan Monnier
  2014-11-04 14:52                   ` Stephen Leake
@ 2014-11-06 15:33                   ` Dmitry Gutov
  2014-11-06 19:40                     ` Stephen Leake
  1 sibling, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-11-06 15:33 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel, Jorgen Schaefer

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> The only single string that reliably would allow to find the correct
>> definition would be "foo.Foo.baz", but I do not think that anyone
>> would consider that to be "the identifier at point" here.
>
> I would.

I think it should return "baz" here, if we want to go this way at all.
And then that value could be passed to a "search-definition-function",
which will perform a full non-precise scan.

It apparent that we won't be able to do precise searches for identifiers
in most cases, so having a separate variable and function for this seems
to be the way to go, considering that the implementations are likely to
be fairly different anyway.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-06 15:22           ` Dmitry Gutov
@ 2014-11-06 16:51             ` Stefan Monnier
  2014-11-06 17:00               ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-06 16:51 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: emacs-devel, Jorgen Schaefer

> And if find-tag will provide the "multiple occurences" interface on
> its own, there won't be a need for the "next tag" command.

Good point.  If we don't have a such a global-map "next-tag" command
anymore, then I guess it's OK to change M-, to do the job of M-*.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-06 16:51             ` Stefan Monnier
@ 2014-11-06 17:00               ` Helmut Eller
  2014-11-06 17:08                 ` Multiple next-error sources Jorgen Schaefer
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-11-06 17:00 UTC (permalink / raw)
  To: emacs-devel

On Thu, Nov 06 2014, Stefan Monnier wrote:

>> And if find-tag will provide the "multiple occurences" interface on
>> its own, there won't be a need for the "next tag" command.
>
> Good point.  If we don't have a such a global-map "next-tag" command
> anymore, then I guess it's OK to change M-, to do the job of M-*.

An idea would be to merge the next-error and next-tag commands. After
all, they are quite similar.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Multiple next-error sources
  2014-11-06 17:00               ` Helmut Eller
@ 2014-11-06 17:08                 ` Jorgen Schaefer
  2014-11-06 23:15                   ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-06 17:08 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On Thu, 06 Nov 2014 18:00:24 +0100
Helmut Eller <eller.helmut@gmail.com> wrote:

> On Thu, Nov 06 2014, Stefan Monnier wrote:
> 
> >> And if find-tag will provide the "multiple occurences" interface on
> >> its own, there won't be a need for the "next tag" command.
> >
> > Good point.  If we don't have a such a global-map "next-tag" command
> > anymore, then I guess it's OK to change M-, to do the job of M-*.
> 
> An idea would be to merge the next-error and next-tag commands. After
> all, they are quite similar.

I'm not sure if they are sufficiently similar, but there are other use
cases for something like this. For example, I recently wanted to extend
`next-error' to find flymake highlights.

An idea I had was to replace/extend `next-error-function' with a hook
where the first non-nil return value is used as the location of the
next error.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-06 15:33                   ` Dmitry Gutov
@ 2014-11-06 19:40                     ` Stephen Leake
  2014-11-07  2:57                       ` Yuri Khan
  2014-11-07 20:56                       ` Dmitry Gutov
  0 siblings, 2 replies; 172+ messages in thread
From: Stephen Leake @ 2014-11-06 19:40 UTC (permalink / raw)
  To: emacs-devel

Dmitry Gutov <dgutov@yandex.ru> writes:

> Stefan Monnier <monnier@iro.umontreal.ca> writes:
>
>>> The only single string that reliably would allow to find the correct
>>> definition would be "foo.Foo.baz", but I do not think that anyone
>>> would consider that to be "the identifier at point" here.
>>
>> I would.
>
> I think it should return "baz" here, if we want to go this way at all.
> And then that value could be passed to a "search-definition-function",
> which will perform a full non-precise scan.

When would that be useful?

> It apparent that we won't be able to do precise searches for identifiers
> in most cases, 

It requires support from the compiler/backend, but that should always be
available (presumably you have the compiler if you are editing the
code). So why do you think we can't do precise searches in most cases?

Hmm, maybe you won't have the compiler installed if you are just reading
someone else's code. I'm not sure that's an important use case.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-06 17:08                 ` Multiple next-error sources Jorgen Schaefer
@ 2014-11-06 23:15                   ` Stefan Monnier
  2014-11-07  9:49                     ` Jorgen Schaefer
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-06 23:15 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: Helmut Eller, emacs-devel

> An idea I had was to replace/extend `next-error-function' with a hook
> where the first non-nil return value is used as the location of the
> next error.

Why?  Can't you already do it with add-function?


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-06 19:40                     ` Stephen Leake
@ 2014-11-07  2:57                       ` Yuri Khan
  2014-11-07 20:56                       ` Dmitry Gutov
  1 sibling, 0 replies; 172+ messages in thread
From: Yuri Khan @ 2014-11-07  2:57 UTC (permalink / raw)
  To: Stephen Leake; +Cc: Emacs developers

On Fri, Nov 7, 2014 at 1:40 AM, Stephen Leake
<stephen_leake@stephe-leake.org> wrote:

>> I think it should return "baz" here, if we want to go this way at all.
>> And then that value could be passed to a "search-definition-function",
>> which will perform a full non-precise scan.
>
> When would that be useful?

When function names are unique enough between interfaces and the
parser is not good enough to precisely identify which subset of
classes the current invocation may involve. The search-definition
engine may then say “oh screw it, I got confused, here are all
definitions of baz in all of your bazillion classes, figure it out
yourself, you’re human after all”.

>> It apparent that we won't be able to do precise searches for identifiers
>> in most cases,
>
> It requires support from the compiler/backend, but that should always be
> available (presumably you have the compiler if you are editing the
> code). So why do you think we can't do precise searches in most cases?

Some compilers don’t expose the necessary information in a
machine-readable form, or are not sufficiently fast to produce a
result within the interval the user is prepared to wait.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-06 23:15                   ` Stefan Monnier
@ 2014-11-07  9:49                     ` Jorgen Schaefer
  2014-11-07 14:59                       ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-07  9:49 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Helmut Eller, emacs-devel

On Thu, 06 Nov 2014 18:15:40 -0500
Stefan Monnier <monnier@IRO.UMontreal.CA> wrote:

> > An idea I had was to replace/extend `next-error-function' with a
> > hook where the first non-nil return value is used as the location
> > of the next error.
> 
> Why?  Can't you already do it with add-function?

Sure. There is a single-variable interface already, I thought it would
make sense to extend it.

The advantage of hooks is that it makes it easier for users to
customize the behavior by adding and removing various entries.

Is add-function intended to replace hooks like this in general?

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07  9:49                     ` Jorgen Schaefer
@ 2014-11-07 14:59                       ` Stefan Monnier
  2014-11-07 15:24                         ` Daniel Colascione
                                           ` (2 more replies)
  0 siblings, 3 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-07 14:59 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: Helmut Eller, emacs-devel

> The advantage of hooks is that it makes it easier for users to
> customize the behavior by adding and removing various entries.

While it's true that

    (add-hook 'next-error-functions #'my-function)

is shorter than

    (add-function :before-until next-error-function #'my-function)

I don't think it warrants the addition of a next-error-functions.

If the :before-until is the problematic part, then I guess we should
look for ways to improve that (e.g. a better name, or some way for
a variable to say that :before-until is the default when adding
functions to it?).

> Is add-function intended to replace hooks like this in general?

Somewhat, yes.  I have no intention to go around and replace existing
hooks in the forseeable future (except for those rare hooks that used
with-wrapper-hook), but I'll favor new foo-function over new
foo-functions.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 14:59                       ` Stefan Monnier
@ 2014-11-07 15:24                         ` Daniel Colascione
  2014-11-07 15:55                           ` Stefan Monnier
  2014-11-07 15:41                         ` Jorgen Schaefer
  2014-11-07 16:55                         ` Alan Mackenzie
  2 siblings, 1 reply; 172+ messages in thread
From: Daniel Colascione @ 2014-11-07 15:24 UTC (permalink / raw)
  To: Stefan Monnier, Jorgen Schaefer; +Cc: Helmut Eller, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1183 bytes --]

On 11/07/2014 02:59 PM, Stefan Monnier wrote:
>> The advantage of hooks is that it makes it easier for users to
>> customize the behavior by adding and removing various entries.
> 
> While it's true that
> 
>     (add-hook 'next-error-functions #'my-function)
> 
> is shorter than
> 
>     (add-function :before-until next-error-function #'my-function)
> 
> I don't think it warrants the addition of a next-error-functions.
> 
> If the :before-until is the problematic part, then I guess we should
> look for ways to improve that (e.g. a better name, or some way for
> a variable to say that :before-until is the default when adding
> functions to it?).
> 
>> Is add-function intended to replace hooks like this in general?
> 
> Somewhat, yes.  I have no intention to go around and replace existing
> hooks in the forseeable future (except for those rare hooks that used
> with-wrapper-hook), but I'll favor new foo-function over new
> foo-functions.

I strongly dislike this approach. It conflates customization points with
implementation details. Hook variables clearly separate the two ideas.

There's also no buffer-local add-function equivalent.


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 14:59                       ` Stefan Monnier
  2014-11-07 15:24                         ` Daniel Colascione
@ 2014-11-07 15:41                         ` Jorgen Schaefer
  2014-11-07 16:03                           ` Stefan Monnier
  2014-11-07 16:55                         ` Alan Mackenzie
  2 siblings, 1 reply; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-07 15:41 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

On Fri, 07 Nov 2014 09:59:12 -0500
Stefan Monnier <monnier@iro.umontreal.ca> wrote:

> > Is add-function intended to replace hooks like this in general?
> 
> Somewhat, yes.  I have no intention to go around and replace existing
> hooks in the forseeable future (except for those rare hooks that used
> with-wrapper-hook), but I'll favor new foo-function over new
> foo-functions.

Thanks for the clarification.

If this is the intended default for Emacs in the future, I would like
to use this in my packages, too. Therefore, I have a few questions.

First, how can I see the sequence of functions being run after
add-function? In the example for next-error, what I was looking at
would have been a list with a few functions from different packages,
such as:

- next-error-if-compile-buffer-is-visible
- next-error-from-flymake
- next-error-from-other-source
- next-error-if-compile-buffer-is-not-visible

With a hook variable, I can C-h v the variable and clearly see what is
happening in which order. I can also easily use setq or delq to change
the order if I find it problematic. Is there a way to see and change the
order of application when using add-function?

Second, also from this example, there would be two functions there by
default:

- next-error-if-compile-buffer-is-visible
- next-error-if-compile-buffer-is-not-visible

New functions likely want to be added in the middle, so visible
compilation buffers take precedence, but non-visible ones are chosen
last. Is there a way using add-function to add a new function in
between two existing functions?

Finally, hook variables can be added to customize to make them more
easily accessible to users. Is there some sort of customize support for
add-function, like there is for hooks/lists of functions?

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 15:24                         ` Daniel Colascione
@ 2014-11-07 15:55                           ` Stefan Monnier
  2014-11-07 16:08                             ` Daniel Colascione
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-07 15:55 UTC (permalink / raw)
  To: Daniel Colascione; +Cc: emacs-devel, Helmut Eller, Jorgen Schaefer

> I strongly dislike this approach.  It conflates customization points with
> implementation details.  Hook variables clearly separate the two ideas.

foo-function *is* a customization point and not an implementation detail.

That's the difference between using add-function on a foo-function (a
customization point) and using advice-add on some function (some
implementation detail).

> There's also no buffer-local add-function equivalent.

Of course there is:

   (add-function :before-until (local 'next-error-function) #'my-function)


-- Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 15:41                         ` Jorgen Schaefer
@ 2014-11-07 16:03                           ` Stefan Monnier
  0 siblings, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-07 16:03 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: emacs-devel

> With a hook variable, I can C-h v the variable and clearly see what is
> happening in which order.

Indeed, this is a downside of add-function.  If you know what you're
looking at, you can kind of figure out what's going on, but it's
definitely in the "I feel like I'm decrypting rather than
reading" category.

Of course, you can use advice-function-mapc to extract the info and turn
it into a normal list, but I didn't bother to write such a function, nor
to hook it into C-h v.  Patches welcome ;-)

> I can also easily use setq or delq to change
> the order if I find it problematic.

You can "delq" with remove-function.  I don't think there's a real
equivalent of "setq", OTOH.

> New functions likely want to be added in the middle, so visible
> compilation buffers take precedence, but non-visible ones are chosen
> last.  Is there a way using add-function to add a new function in
> between two existing functions?

You can specify a `depth' property, yes.

> Finally, hook variables can be added to customize to make them more
> easily accessible to users. Is there some sort of customize support for
> add-function, like there is for hooks/lists of functions?

Currently, no.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 15:55                           ` Stefan Monnier
@ 2014-11-07 16:08                             ` Daniel Colascione
  2014-11-07 18:17                               ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Daniel Colascione @ 2014-11-07 16:08 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel, Helmut Eller, Jorgen Schaefer

[-- Attachment #1: Type: text/plain, Size: 1667 bytes --]

On 11/07/2014 03:55 PM, Stefan Monnier wrote:
>> I strongly dislike this approach.  It conflates customization points with
>> implementation details.  Hook variables clearly separate the two ideas.
> 
> foo-function *is* a customization point and not an implementation detail.
> 
> That's the difference between using add-function on a foo-function (a
> customization point) and using advice-add on some function (some
> implementation detail).

Even the name is unintuitive. Realizing that you can add your own
function to something called "foo-function" (singular) requires a
special kind of comprehension of function composition. "So you're
telling me that I add a function to a function and get a function?"

The many add-function composition modes are useful for advice, but
counterproductive for customization points: the great variety of options
makes it hard to reason about the effect any particular effect. With a
hook, you have a simple list of functions, possibly with a sentinel that
delegates to a global value.

I don't see any compelling reason to avoid conventional hooks. They've
worked for many years. Requiring add-function for some customization and
add-hook for others will only confuse users. It doesn't add any real
power and doesn't make Emacs any better AFAICT.

>> There's also no buffer-local add-function equivalent.
> 
> Of course there is:
> 
>    (add-function :before-until (local 'next-error-function) #'my-function)

Okay, that actually works, including in some corner cases I tested
involving before- and after-functions in both the buffer-local and
global values. My other points still apply.


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 14:59                       ` Stefan Monnier
  2014-11-07 15:24                         ` Daniel Colascione
  2014-11-07 15:41                         ` Jorgen Schaefer
@ 2014-11-07 16:55                         ` Alan Mackenzie
  2014-11-07 17:10                           ` Daniel Colascione
  2014-11-07 18:08                           ` Stefan Monnier
  2 siblings, 2 replies; 172+ messages in thread
From: Alan Mackenzie @ 2014-11-07 16:55 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel, Helmut Eller, Jorgen Schaefer

Hello, Stefan.

On Fri, Nov 07, 2014 at 09:59:12AM -0500, Stefan Monnier wrote:
> > The advantage of hooks is that it makes it easier for users to
> > customize the behavior by adding and removing various entries.

> While it's true that

>     (add-hook 'next-error-functions #'my-function)

> is shorter than

>     (add-function :before-until next-error-function #'my-function)

> I don't think it warrants the addition of a next-error-functions.

Why not?

> If the :before-until is the problematic part, then I guess we should
> look for ways to improve that (e.g. a better name, or some way for
> a variable to say that :before-until is the default when adding
> functions to it?).

> > Is add-function intended to replace hooks like this in general?

> Somewhat, yes.  I have no intention to go around and replace existing
> hooks in the forseeable future (except for those rare hooks that used
> with-wrapper-hook), but I'll favor new foo-function over new
> foo-functions.

Why?  I'm adding my voice to the clamour of dissent.

It would seem that the use of single functions, with `add-function' is
inferior to the conventional hook mechanism in every way.  What am I
missing?  In addition to the things cited by Daniel, there's:

(i) the danger (near certainty) that somebody is going to use `setq'
rather than `add-function' to configure it;

(ii) the additional incompatibility with other Emacsen;

(iii) the difficulty (or perhaps clumsiness) in looking at the contents
of an advised function.  There would seem to be nothing equivalent to
M-: after-change-functions.

So why are you changing from the conventional hook mechanism, which works
so well?  What is the advantage of the new scheme.

Incidentally, I had a look at the documentation for add-advice, and
there's a problem with it.  "Advice" in English has no plural - there's
no such word as "advices".  If it's necessary to emphasize the plurality,
then "pieces of advice" can be used.  There's no such thing as "an
advice", rather you'd say "some advice".  It's a bit like you wouldn't
refer to a lake as "a big water"; you'd say it contains "a lot of water".
I think there's a term in linguistics for such a word, but I don't know
it off hand.

Incidentally 2, the verb corresponding to the noun "advice" is "to
advise".

>         Stefan

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 16:55                         ` Alan Mackenzie
@ 2014-11-07 17:10                           ` Daniel Colascione
  2014-11-07 17:40                             ` Alan Mackenzie
  2014-11-07 18:08                           ` Stefan Monnier
  1 sibling, 1 reply; 172+ messages in thread
From: Daniel Colascione @ 2014-11-07 17:10 UTC (permalink / raw)
  To: Alan Mackenzie, Stefan Monnier; +Cc: Jorgen Schaefer, Helmut Eller, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 2839 bytes --]

On 11/07/2014 04:55 PM, Alan Mackenzie wrote:
> Hello, Stefan.
> 
> On Fri, Nov 07, 2014 at 09:59:12AM -0500, Stefan Monnier wrote:
>>> The advantage of hooks is that it makes it easier for users to
>>> customize the behavior by adding and removing various entries.
> 
>> While it's true that
> 
>>     (add-hook 'next-error-functions #'my-function)
> 
>> is shorter than
> 
>>     (add-function :before-until next-error-function #'my-function)
> 
>> I don't think it warrants the addition of a next-error-functions.
> 
> Why not?
> 
>> If the :before-until is the problematic part, then I guess we should
>> look for ways to improve that (e.g. a better name, or some way for
>> a variable to say that :before-until is the default when adding
>> functions to it?).
> 
>>> Is add-function intended to replace hooks like this in general?
> 
>> Somewhat, yes.  I have no intention to go around and replace existing
>> hooks in the forseeable future (except for those rare hooks that used
>> with-wrapper-hook), but I'll favor new foo-function over new
>> foo-functions.
> 
> Why?  I'm adding my voice to the clamour of dissent.
> 
> It would seem that the use of single functions, with `add-function' is
> inferior to the conventional hook mechanism in every way.  What am I
> missing?  In addition to the things cited by Daniel, there's:
> 
> (i) the danger (near certainty) that somebody is going to use `setq'
> rather than `add-function' to configure it;

The same critique applies to regular hooks, doesn't it?

> (ii) the additional incompatibility with other Emacsen;

I'm not sure that compatibility with other Emacsen is as important as it
once was. AIUI, GNU Emacs is receiving the vast majority of development
effort.

> (iii) the difficulty (or perhaps clumsiness) in looking at the contents
> of an advised function.  There would seem to be nothing equivalent to
> M-: after-change-functions.
> 
> So why are you changing from the conventional hook mechanism, which works
> so well?  What is the advantage of the new scheme.
> 
> Incidentally, I had a look at the documentation for add-advice, and
> there's a problem with it.  "Advice" in English has no plural - there's
> no such word as "advices".  If it's necessary to emphasize the plurality,
> then "pieces of advice" can be used.  There's no such thing as "an
> advice", rather you'd say "some advice".  It's a bit like you wouldn't
> refer to a lake as "a big water"; you'd say it contains "a lot of water".
> I think there's a term in linguistics for such a word, but I don't know
> it off hand.

I think "advise" works like "code" in the software sense and
"furniture". The term is "mass noun".

> Incidentally 2, the verb corresponding to the noun "advice" is "to
> advise".

Isn't English fun?


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 17:10                           ` Daniel Colascione
@ 2014-11-07 17:40                             ` Alan Mackenzie
  2014-11-08  8:55                               ` Dmitry Gutov
  0 siblings, 1 reply; 172+ messages in thread
From: Alan Mackenzie @ 2014-11-07 17:40 UTC (permalink / raw)
  To: Daniel Colascione
  Cc: Jorgen Schaefer, Helmut Eller, Stefan Monnier, emacs-devel

Hello, Daniel.

On Fri, Nov 07, 2014 at 05:10:29PM +0000, Daniel Colascione wrote:
> On 11/07/2014 04:55 PM, Alan Mackenzie wrote:

> > It would seem that the use of single functions, with `add-function' is
> > inferior to the conventional hook mechanism in every way.  What am I
> > missing?  In addition to the things cited by Daniel, there's:

> > (i) the danger (near certainty) that somebody is going to use `setq'
> > rather than `add-function' to configure it;

> The same critique applies to regular hooks, doesn't it?

:-)  So it does!  What I was confused about is foo-function.  I think
this is going to be a defun in the future, whereas up to now it's always
been a defvar.  This is confusing.  So whereas you'd use "(setq
c-backspace-function 'foobar)", and use `funcall' to invoke it, you'd say
"(add-function 'foo-function 'foobar)" (or whatever), and just call
`foo-function' as a function.

> > (ii) the additional incompatibility with other Emacsen;

> I'm not sure that compatibility with other Emacsen is as important as it
> once was. AIUI, GNU Emacs is receiving the vast majority of development
> effort.

It may be less important than it was, but that's no reason for dismissing
it altogether.

> > (iii) the difficulty (or perhaps clumsiness) in looking at the contents
> > of an advised function.  There would seem to be nothing equivalent to
> > M-: after-change-functions.

> > So why are you changing from the conventional hook mechanism, which works
> > so well?  What is the advantage of the new scheme.

> > Incidentally, I had a look at the documentation for add-advice, and
> > there's a problem with it.  "Advice" in English has no plural - there's
> > no such word as "advices".  If it's necessary to emphasize the plurality,
> > then "pieces of advice" can be used.  There's no such thing as "an
> > advice", rather you'd say "some advice".  It's a bit like you wouldn't
> > refer to a lake as "a big water"; you'd say it contains "a lot of water".
> > I think there's a term in linguistics for such a word, but I don't know
> > it off hand.

> I think "advise" ...

"advice" ??

> ... works like "code" in the software sense and "furniture". The term
> is "mass noun".

Thanks!  "Furniture" is indeed a better example than "water".

> > Incidentally 2, the verb corresponding to the noun "advice" is "to
> > advise".

> Isn't English fun?

Indeed it is!  You get to appreciate it especially when you live in a
place where they don't speak your native language (much).

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 16:55                         ` Alan Mackenzie
  2014-11-07 17:10                           ` Daniel Colascione
@ 2014-11-07 18:08                           ` Stefan Monnier
  2014-11-07 18:21                             ` Alan Mackenzie
  1 sibling, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-07 18:08 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: emacs-devel, Helmut Eller, Jorgen Schaefer

>> While it's true that
>> (add-hook 'next-error-functions #'my-function)
>> is shorter than
>> (add-function :before-until next-error-function #'my-function)
>> I don't think it warrants the addition of a next-error-functions.
> Why not?

I'd return the question: given the above, why add next-error-functions?


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 16:08                             ` Daniel Colascione
@ 2014-11-07 18:17                               ` Stefan Monnier
  2014-11-07 18:22                                 ` Daniel Colascione
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-07 18:17 UTC (permalink / raw)
  To: Daniel Colascione; +Cc: Jorgen Schaefer, Helmut Eller, emacs-devel

> The many add-function composition modes are useful for advice, but
> counterproductive for customization points: the great variety of options
> makes it hard to reason about the effect any particular effect.  With a
> hook, you have a simple list of functions, possibly with a sentinel that
> delegates to a global value.

It's a tradeoff, indeed.  It gives you extra flexibility (instead of
the hook specifying that the functions will be composed with
"until-failure", each and every function gets to decide how it's
composed with the others), which of course means more choices to make.

As I said:

   If the :before-until is the problematic part, then I guess we should
   look for ways to improve that (e.g. a better name, or some way for
   a variable to say that :before-until is the default when adding
   functions to it?).

So maybe we should arrange that the "typical" way to compose the
functions for a particular variable be specified along with that
variable, so that you can use (for example)

   (add-function nil next-error-function #'my-function)

and add-function would know to use :before-until.

> I don't see any compelling reason to avoid conventional hooks.

The extra flexibility.

> They've worked for many years.

Obviously not completely, since there are occurrences of foo-function
variables, and for several of those, packages have had to "stash the
previous value, then install their own" on those vars, with latent bugs
when several packages do that on the same var.

> Requiring add-function for some customization and add-hook for others
> will only confuse users.

Yes, I'm not particularly happy about that part, admittedly.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 18:08                           ` Stefan Monnier
@ 2014-11-07 18:21                             ` Alan Mackenzie
  2014-11-07 18:48                               ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Alan Mackenzie @ 2014-11-07 18:21 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel, Helmut Eller, Jorgen Schaefer

Hello, Stefan.

On Fri, Nov 07, 2014 at 01:08:57PM -0500, Stefan Monnier wrote:
> >> While it's true that
> >> (add-hook 'next-error-functions #'my-function)
> >> is shorter than
> >> (add-function :before-until next-error-function #'my-function)
> >> I don't think it warrants the addition of a next-error-functions.
> > Why not?

> I'd return the question: given the above, why add next-error-functions?

Because "(add-hook 'next-error-functions #'my-function)" is shorter than
the `add-function' variant, along with all the other good reasons which
have been posted in this thread.

Let me repeat my question: in what respect is the `add-function' way of
doing things superior to the conventional hook mechanism?  If the answer
is "in no respect", then I would suggest that the project continue to
use hooks in preference to `add-function' functions.

>         Stefan

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 18:17                               ` Stefan Monnier
@ 2014-11-07 18:22                                 ` Daniel Colascione
  2014-11-07 19:06                                   ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Daniel Colascione @ 2014-11-07 18:22 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Jorgen Schaefer, Helmut Eller, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1828 bytes --]

On 11/07/2014 06:17 PM, Stefan Monnier wrote:
>> The many add-function composition modes are useful for advice, but
>> counterproductive for customization points: the great variety of options
>> makes it hard to reason about the effect any particular effect.  With a
>> hook, you have a simple list of functions, possibly with a sentinel that
>> delegates to a global value.
> 
> It's a tradeoff, indeed.  It gives you extra flexibility (instead of
> the hook specifying that the functions will be composed with
> "until-failure", each and every function gets to decide how it's
> composed with the others), which of course means more choices to make.
> 
> As I said:
> 
>    If the :before-until is the problematic part, then I guess we should
>    look for ways to improve that (e.g. a better name, or some way for
>    a variable to say that :before-until is the default when adding
>    functions to it?).
> 
> So maybe we should arrange that the "typical" way to compose the
> functions for a particular variable be specified along with that
> variable, so that you can use (for example)

With the existing system, the hook runner gets to specify the "method
combiner" (to use CLOS terminology); with add-function, each hook gets
to specify a different method combiner. It's that possibility that
bothers me, although the lack of a good default increases the risk of
harmful diversity here.

Can you come up with a concrete example of an instance where a
hook-specified method combiner is actually useful?

>    (add-function nil next-error-function #'my-function)
> 
> and add-function would know to use :before-until.
> 
>> I don't see any compelling reason to avoid conventional hooks.
> 
> The extra flexibility.

I'm not yet convinced that the flexibility is worth the cost.


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 18:21                             ` Alan Mackenzie
@ 2014-11-07 18:48                               ` Stefan Monnier
  2014-11-07 19:51                                 ` Alan Mackenzie
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-07 18:48 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: emacs-devel, Helmut Eller, Jorgen Schaefer

> Because "(add-hook 'next-error-functions #'my-function)" is shorter than
> the `add-function' variant,

The text I wrote and you quoted already said that it was shorter but not
enough to warrant an additional variable.  So you think saving 16
characters is worth an additional hook?
Based on that, I guess I should just change the naming conventions:

   (add-hook 'next-error-functions #'my-function)
   (add-f :before-until next-error-f #'my-f)

Look ma!  We should change all hooks to be plain foo-f variables!

> along with all the other good reasons which have been posted in
> this thread.

None of those reasons seem to apply the question of whether it's worth
adding a next-error-functions hook (remember: next-error-function exists
already, since long before add-function was born).

> Let me repeat my question: in what respect is the `add-function' way of
> doing things superior to the conventional hook mechanism?  If the answer
> is "in no respect", then I would suggest that the project continue to
> use hooks in preference to `add-function' functions.

At this point I feel like you're confusing me with an idiot.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 18:22                                 ` Daniel Colascione
@ 2014-11-07 19:06                                   ` Stefan Monnier
  0 siblings, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-07 19:06 UTC (permalink / raw)
  To: Daniel Colascione; +Cc: Jorgen Schaefer, Helmut Eller, emacs-devel

> Can you come up with a concrete example of an instance where a
> hook-specified method combiner is actually useful?

No, currently, there is no variable that is modified via add-function in
different ways in different cases.

I'm sure you can cook up your own artificial example, but currently
practice says this flexibility is indeed not used.

If add-function had existed when post-self-insert-hook had been
introduced, then for sure I would have used a self-insert-function
instead and that would have benefited from such flexibility: some of the
post-self-insert-hook functions currently have to undo what the
self-insert-command did (i.e. they would rather use :around), and others
would really like to know where point was at the beginning of the
command (i.e. they would rather use :around as well).

Of course, a self-insert-function modified with add-function would also
have solved more cleanly the issue of ordering between functions
(currently resolved in an ad-hoc way in
electric--sort-post-self-insertion-hook).  But that would just be
a side-benefit.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 18:48                               ` Stefan Monnier
@ 2014-11-07 19:51                                 ` Alan Mackenzie
  0 siblings, 0 replies; 172+ messages in thread
From: Alan Mackenzie @ 2014-11-07 19:51 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

Hello, Stefan.

On Fri, Nov 07, 2014 at 01:48:25PM -0500, Stefan Monnier wrote:
> > Because "(add-hook 'next-error-functions #'my-function)" is shorter than
> > the `add-function' variant,

> The text I wrote and you quoted already said that it was shorter but not
> enough to warrant an additional variable.  So you think saving 16
> characters is worth an additional hook?

It saves one parameter, that `:before-until', which is difficult to
understand.

> None of those reasons seem to apply the question of whether it's worth
> adding a next-error-functions hook (remember: next-error-function exists
> already, since long before add-function was born).

> > Let me repeat my question: in what respect is the `add-function' way of
> > doing things superior to the conventional hook mechanism?  If the answer
> > is "in no respect", then I would suggest that the project continue to
> > use hooks in preference to `add-function' functions.

> At this point I feel like you're confusing me with an idiot.

Sorry, I don't mean to give that impression.

Anyhow, you've answered my question in a post to Daniel.  It's all about
increased flexibility.  So, thanks!

>         Stefan

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-06 19:40                     ` Stephen Leake
  2014-11-07  2:57                       ` Yuri Khan
@ 2014-11-07 20:56                       ` Dmitry Gutov
  1 sibling, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-11-07 20:56 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

Stephen Leake <stephen_leake@stephe-leake.org> writes:

>> I think it should return "baz" here, if we want to go this way at all.
>> And then that value could be passed to a "search-definition-function",
>> which will perform a full non-precise scan.
>
> When would that be useful?

In general? In situations similar to whenever a user would call
`apropos' when working with ELisp. Sometimes you don't actually know
what you're looking for, so you try one or several keywords.

>> It apparent that we won't be able to do precise searches for identifiers
>> in most cases, 
>
> It requires support from the compiler/backend, but that should always be
> available (presumably you have the compiler if you are editing the
> code). So why do you think we can't do precise searches in most cases?

You're assuming a statically typed language. Arguably, people writing in
dynamic languages are at least a significant portion of Emacs users, if
not the majority.

And C/C++ users will also be at a disadvantage here, unless they use a
Clang-based tool, which we won't support in Emacs proper at least in the
near future. Or GCC developers deliver on that front.

> Hmm, maybe you won't have the compiler installed if you are just reading
> someone else's code. I'm not sure that's an important use case.

If we don't have a tool which can respond to a
search-definition-function request, this is not an interesting situation
anyway, even if a plausible one.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Multiple next-error sources
  2014-11-07 17:40                             ` Alan Mackenzie
@ 2014-11-08  8:55                               ` Dmitry Gutov
  0 siblings, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-11-08  8:55 UTC (permalink / raw)
  To: Alan Mackenzie
  Cc: emacs-devel, Daniel Colascione, Helmut Eller, Stefan Monnier,
	Jorgen Schaefer

Alan Mackenzie <acm@muc.de> writes:

>> > (i) the danger (near certainty) that somebody is going to use `setq'
>> > rather than `add-function' to configure it;
>
>> The same critique applies to regular hooks, doesn't it?
>
> :-)  So it does!  What I was confused about is foo-function.  I think
> this is going to be a defun in the future, whereas up to now it's always
> been a defvar.

Will it? In that case, it probably should be named differently, like
foo-default (next-error-default, in the current example).

As long as there is a foo-function custom var, the critique applies.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-02 15:34 ` Stefan Monnier
  2014-11-02 16:29   ` Jorgen Schaefer
@ 2014-11-17 20:10   ` Jorgen Schaefer
  2014-11-18  8:07     ` Stephen Leake
                       ` (2 more replies)
  1 sibling, 3 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-17 20:10 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1498 bytes --]

On Sun, 02 Nov 2014 10:34:28 -0500
Stefan Monnier <monnier@iro.umontreal.ca> wrote:

> > M-. is bound to a new command `find-definition', which primarily
> > calls the value of a new variable `find-definition-function' (by
> > default a wrapper around `find-tag' to keep the current
> > functionality intact). `find-definition' also keeps track of the
> > tag ring, so this would move `find-tag-marker-ring' and related
> > functionality out of etags.el, too.
> 
> Yes, this sounds great.
>
> [...]
>
> > Comments?
> 
> I'm all for it,

Some initial patch attached. Is this what you had in mind when you said
the above? Anything I should change to make it fit into Emacs better? I
do not know much about Emacs coding conventions.

Currently missing:

- find-definition does not store locations in the tag ring yet
  (ran out of time and wanted to post the initial patch for feedback)
- The whole "find uses" interface
- etags.el integration
- emacs-lisp-mode integration

This also does define a minor mode instead of changing the global key
bindings. I think in the final version it should replace the key
binding definitions done in etags.el. Is this correct? Do I need to
hook it up elsewhere in the build process?

Public git repo for those who find that easier to read / clone (nothing
there that isn't in the attached patch):

https://github.com/jorgenschaefer/emacs/tree/find-definition
https://github.com/jorgenschaefer/emacs/commit/ecac500ac7dee3095863df23c4cd661ba62e2187

Regards,
Jorgen

[-- Attachment #2: 0001-lisp-progmodes-find-definition.el-New-file.patch --]
[-- Type: text/x-patch, Size: 16839 bytes --]

From ecac500ac7dee3095863df23c4cd661ba62e2187 Mon Sep 17 00:00:00 2001
From: Jorgen Schaefer <contact@jorgenschaefer.de>
Date: Mon, 17 Nov 2014 20:43:57 +0100
Subject: [PATCH] * lisp/progmodes/find-definition.el: New file. *
 test/automated/find-definition-test.el: New file.

---
 lisp/ChangeLog                         |    4 +
 lisp/progmodes/find-definition.el      |  194 +++++++++++++++++++++++++++++
 test/ChangeLog                         |    4 +
 test/automated/find-definition-test.el |  208 ++++++++++++++++++++++++++++++++
 4 files changed, 410 insertions(+)
 create mode 100644 lisp/progmodes/find-definition.el
 create mode 100644 test/automated/find-definition-test.el

diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 912b69a..0334bb4 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,7 @@
+2014-11-17  Jorgen Schaefer <contact@jorgenschaefer.de>
+
+	* progmodes/find-definition.el: New file.
+
 2014-11-17  Lars Magne Ingebrigtsen  <larsi@gnus.org>
 
 	* bindings.el (search-map): Move `eww-search-words' to `M-s M-w'.
diff --git a/lisp/progmodes/find-definition.el b/lisp/progmodes/find-definition.el
new file mode 100644
index 0000000..d9846e7
--- /dev/null
+++ b/lisp/progmodes/find-definition.el
@@ -0,0 +1,194 @@
+;;; find-definition.el --- Find definition at point  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+
+;; Author: Jorgen Schaefer <contact@jorgenschaefer.de>
+;; Keywords: tools
+
+;; This file is part of GNU Emacs.
+
+;; This program 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
+;; of the License, or (at your option) any later version.
+
+;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'ring)
+
+(defgroup find-definition nil "Finding definitions of things at point."
+  :group 'tools)
+
+(defcustom find-definition-marker-ring-length 16
+  "Length of marker rings `find-definition-marker-ring'."
+  :group 'find-definition
+  :type 'integer)
+
+(defvar find-definition-function nil
+  "The function `find-definition' calls to find the definition.
+
+Will be called with no arguments with point at the location of
+the thing to find the definition for. It should return a list
+with each element being a list of one to three elements. The
+first element should be the file name, the second the
+line (defaulting to 1) and the third the column (defaulting to
+0).")
+
+(defvar find-definition-identifier-function nil
+  "Find the definition of a named identifier.
+
+Will be called with the result of prompting the user for a
+completion using `find-definition-completion-table', and should
+return a list like `find-definition-function'.")
+
+(defvar find-definition-identifier-completion-table nil
+  "The completion table to complete known symbols.
+
+Will be passed as COLLECTION to `completing-read'.")
+
+(defvar find-definition-marker-ring
+  (make-ring find-definition-marker-ring-length)
+  "Ring of positions visited by `find-definition'.")
+
+(defvar find-definition-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "M-.") 'find-definition)
+    (define-key map (kbd "C-x 4 .") 'find-definition-other-window)
+    (define-key map (kbd "C-x 5 .") 'find-definition-other-frame)
+    ;; (define-key map (kbd "M-_") 'find-definition-uses)
+    (define-key map (kbd "M-,") 'find-definition-goto-last-position)
+    map)
+  "The key map for `find-definition-mode'.")
+
+;;;###autoload
+(define-minor-mode find-definition-mode
+  "Minor mode to provide some key bindings to find definitions.
+
+\\{find-definition-mode-map}"
+  :keymap 'find-definition-mode)
+
+;;;###autoload
+(defun find-definition (&optional ask)
+  "Go to the definition of the thing at point.
+
+If the definition can not be found, or with a prefix argument,
+prompt for a symbol to use."
+  (interactive "P")
+  (switch-to-buffer (find-definition--noselect ask)))
+
+;;;###autoload
+(defun find-definition-other-window (&optional ask)
+  "Display the definition of the thing at point in another window.
+
+If the definition can not be found, or with a prefix argument,
+prompt for a symbol to use."
+  (interactive "P")
+  (switch-to-buffer-other-window (find-definition--noselect ask)))
+
+;;;###autoload
+(defun find-definition-other-frame (&optional ask)
+  "Display the definition of the thing at point in another frame.
+
+If the definition can not be found, or with a prefix argument,
+prompt for a symbol to use."
+  (interactive "P")
+  (switch-to-buffer-other-frame (find-definition--noselect ask)))
+
+(defun find-definition--noselect (&optional ask)
+  "Internal function for `find-definition'.
+
+Does all the work, but returns the buffer instead of displaying
+it."
+  (let* ((locations (when (not ask)
+                      (funcall find-definition-function))))
+    (cond
+     (locations
+      (find-definition--find-locations locations))
+     ((and find-definition-identifier-completion-table
+           find-definition-identifier-function)
+      (let* ((identifier (completing-read
+                          "Find definition: "
+                          find-definition-identifier-completion-table
+                          nil t))
+             (locations (funcall find-definition-identifier-function
+                                 identifier)))
+        (find-definition--find-locations locations)))
+     (t
+      (error "Can't find the definition of the thing at point")))))
+
+(defun find-definition--find-locations (locations)
+  "Go to the location in LOCATIONS.
+
+If there is exactly one location, go directly there. Otherwise,
+prompt the user for a location choice."
+  (if (null (cdr locations))
+      ;; Exactly one definition
+      (let* ((location (car locations))
+             (filename (elt location 0))
+             (line (or (elt location 1)
+                       1))
+             (col (or (elt location 2)
+                      0))
+             (buf (find-file-noselect filename)))
+        (with-current-buffer buf
+          (widen)
+          (goto-char (point-min))
+          (forward-line (- line 1))
+          (forward-char col))
+        buf)
+    ;; More than one definition
+    (let ((outbuf (get-buffer-create "*Definitions*"))
+          (dir default-directory)
+          (inhibit-read-only t))
+      (with-current-buffer outbuf
+        (erase-buffer)
+        (setq default-directory dir)
+        (compilation-mode)
+        (dolist (location locations)
+          (let* ((filename (elt location 0))
+                 (line (or (elt location 1)
+                           1))
+                 (col (or (elt location 2)
+                          0))
+                 (buffer (find-buffer-visiting filename))
+                 (line-string
+                  (when buffer
+                    (with-current-buffer buffer
+                      (save-excursion
+                        (save-restriction
+                          (widen)
+                          (goto-char (point-min))
+                          (forward-line (- line 1))
+                          (buffer-substring (line-beginning-position)
+                                            (line-end-position))))))))
+            (insert (format "%s:%s:%s:%s\n"
+                            filename line col
+                            (or line-string
+                                "")))))
+        (goto-char (point-min)))
+      outbuf)))
+
+;;;###autoload
+(defun find-definition-goto-last-position ()
+  "Pop back to where \\[find-definition] was last invoked."
+  (interactive)
+  (when (ring-empty-p find-definition-marker-ring)
+    (error "No previous locations for find-definition invocation"))
+  (let ((marker (ring-remove find-definition-marker-ring)))
+    (switch-to-buffer (or (marker-buffer marker)
+                          (error "The marked buffer has been deleted")))
+    (goto-char (marker-position marker))
+    (set-marker marker nil nil)))
+
+(provide 'find-definition)
+;;; find-definition.el ends here
diff --git a/test/ChangeLog b/test/ChangeLog
index fb00410..217672e 100644
--- a/test/ChangeLog
+++ b/test/ChangeLog
@@ -1,3 +1,7 @@
+2014-11-17  Jorgen Schaefer <contact@jorgenschaefer.de>
+
+	* automated/find-definition-test.el: New file.
+
 2014-11-17  Glenn Morris  <rgm@gnu.org>
 
 	* automated/occur-tests.el (occur-test-case, occur-test-create):
diff --git a/test/automated/find-definition-test.el b/test/automated/find-definition-test.el
new file mode 100644
index 0000000..920eb30
--- /dev/null
+++ b/test/automated/find-definition-test.el
@@ -0,0 +1,208 @@
+;;; find-definition-test.el --- Test suite for find-definition.el -*- lexical-binding: t -*-
+
+;; Copyright (C) 2014  Jorgen Schaefer <contact@jorgenschaefer.de>
+
+;; This program 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
+;; of the License, or (at your option) any later version.
+
+;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'ert)
+(require 'find-definition)
+
+
+(defmacro find-definition-with-temp-file (variable &rest body)
+  "Create a temporary file, bind it to VARIABLE, and evaluated BODY.
+
+Delete the file after BODY finishes."
+  (declare (indent 1))
+  `(let ((,variable (make-temp-file "find-definition-test-")))
+     (unwind-protect
+         (progn ,@body)
+       (delete-file ,variable))))
+
+;;;;;;;;;;;;;;;;;;;
+;;; find-definition
+
+(ert-deftest find-definition ()
+  ;; It should go to the definition if there is exactly one.
+  (find-definition-with-temp-file filename
+    (with-temp-file filename
+      (insert "Hello\n"
+              "World\n"))
+    (let ((find-definition-function (lambda ()
+                                      (list (list filename 2 3)))))
+
+      (find-definition)
+
+      (should (equal (buffer-file-name) filename))
+      (should (looking-at "ld")))
+    (kill-buffer))
+
+  ;; If the backend function can't find a definition for the thing at
+  ;; point, find-definition should prompt the user for a symbol
+  ;; completed by a backend-provided completion table. This symbol
+  ;; then is passed to a backend function to get the location.
+  (find-definition-with-temp-file filename
+    (cl-letf* ( ;; User function definitions
+               (find-definition-function (lambda ()
+                                           nil))
+               (find-definition-identifier-completion-table 'test-table)
+               (fdsf-identifier nil)
+               (find-definition-identifier-function
+                (lambda (sym)
+                  (setq fdsf-identifier sym)
+                  (list (list filename 1 0))))
+               ;; Mocking
+               (cr-collection nil)
+               ((symbol-function 'completing-read)
+                (lambda (prompt collection &rest args)
+                  (setq cr-collection collection)
+                  "test-symbol")))
+
+      (find-definition)
+
+      (should (equal cr-collection 'test-table))
+      (should (equal fdsf-identifier "test-symbol"))
+      (should (equal (buffer-file-name) filename))))
+
+  ;; Do the same with a prefix argument.
+  (find-definition-with-temp-file filename
+    (cl-letf* ( ;; User function definitions
+               (find-definition-function (lambda ()
+                                           (error "Should not be called")))
+               (find-definition-identifier-completion-table 'test-table)
+               (find-definition-identifier-function
+                (lambda (sym)
+                  (list (list filename 1 0))))
+               ;; Mocking
+               (cr-collection nil)
+               ((symbol-function 'completing-read)
+                (lambda (prompt collection &rest args)
+                  (setq cr-collection collection)
+                  "test-symbol")))
+
+      (find-definition '(4))
+
+      (should (equal cr-collection 'test-table))))
+
+  ;; Without a completion table and no identifier at point, the
+  ;; function should throw an error.
+  (cl-letf* ((find-definition-function (lambda ()
+                                         nil))
+             (find-definition-identifier-completion-table nil)
+             (find-definition-identifier-function nil))
+
+    (should-error
+     (find-definition)))
+
+  ;; Without a completion table and a prefix argument, the function
+  ;; should throw an error as well
+  (cl-letf* ((find-definition-function (lambda ()
+                                         (error "Should not be called")))
+             (find-definition-identifier-completion-table nil)
+             (find-definition-identifier-function nil))
+
+    (should-error
+     (find-definition '(4))))
+
+  ;; It should pop up a selection dialog if there is more than one
+  ;; definition.
+  (find-definition-with-temp-file filename
+    (with-temp-file filename
+      (insert "Hello\n"
+              "World\n"))
+    (let ((find-definition-function (lambda ()
+                                      (list (list filename 1)
+                                            (list filename 2)))))
+
+      (find-definition)
+
+      (should (equal (buffer-name) "*Definitions*"))
+      (should (eq major-mode 'compilation-mode))))
+  )
+
+;;;;;;;;;;;;;;;;;;;;;;;;
+;;; find-definition-uses
+
+;; - find-definition-uses should go the use if there is exactly one.
+;; - find-definition-uses should pop up a selection dialog if there is
+;;   more than one use.
+;; - find-definition-uses should prompt for name if there is no definition.
+;; - find-definition-uses should prompt for name with prefix argument.
+
+;; - Provide a new function, etags-find-definition
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; find-definition-goto-last-position
+
+(ert-deftest find-definition-goto-last-position ()
+  ;; It should move to the last position in the same buffer
+  (with-temp-buffer
+    (let* ((find-definition-marker-ring (make-ring 5))
+           (start (point))
+           (marker (make-marker)))
+      (set-marker marker start)
+      (ring-insert find-definition-marker-ring marker)
+      (insert "Bla bla\n")
+      (should (not (= (point) start)))
+      (find-definition-goto-last-position)
+      (should (= (point) start))))
+
+  ;; It should move to the last position in another buffer
+  (with-temp-buffer
+    (let* ((find-definition-marker-ring (make-ring 5))
+           (start (point))
+           (start-buffer (current-buffer))
+           (marker (make-marker)))
+      (set-marker marker start start-buffer)
+      (ring-insert find-definition-marker-ring marker)
+      (with-temp-buffer
+        (find-definition-goto-last-position)
+        (should (equal (current-buffer) start-buffer))
+        (should (= (point) start)))))
+
+  ;; It should should be interactive
+  (should (interactive-form 'find-definition-goto-last-position))
+
+  ;; It should raise an error when the marker ring is empty
+  (let ((find-definition-marker-ring (make-ring 1)))
+    (should-error (find-definition-goto-last-position)))
+
+  ;; It should raise an error if the original buffer is deleted
+  (let* ((find-definition-marker-ring (make-ring 5))
+         (marker (make-marker)))
+    (with-temp-buffer
+      (set-marker marker (point))
+      (ring-insert find-definition-marker-ring marker))
+    (should-error
+     (find-definition-goto-last-position)))
+
+  ;; It should reset the marker
+  ;;
+  ;; Why? This is copied from pop-to-mark, but why would this be
+  ;; needed?
+  (let* ((find-definition-marker-ring (make-ring 5))
+         (marker (make-marker)))
+    (with-temp-buffer
+      (set-marker marker (point))
+      (ring-insert find-definition-marker-ring marker)
+      (find-definition-goto-last-position)
+      (should (null (marker-position marker)))
+      (should (null (marker-buffer marker)))))
+  )
+
+(provide 'find-definition-test)
+;;; find-definition-test.el ends here
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-17 20:10   ` Jorgen Schaefer
@ 2014-11-18  8:07     ` Stephen Leake
  2014-11-18 11:24       ` Helmut Eller
                         ` (2 more replies)
  2014-11-18 16:31     ` Stefan Monnier
  2014-11-20 13:44     ` Helmut Eller
  2 siblings, 3 replies; 172+ messages in thread
From: Stephen Leake @ 2014-11-18  8:07 UTC (permalink / raw)
  To: emacs-devel

Jorgen Schaefer <forcer@forcix.cx> writes:

> This also does define a minor mode instead of changing the global key
> bindings. I think in the final version it should replace the key binding
> definitions done in etags.el. Is this correct?

+1

> Do I need to hook it up elsewhere in the build process?

Not sure what you are asking here; the keybindings need to be autoloaded
as in etags.el.


> +(defvar find-definition-function nil
> +(defvar find-definition-identifier-function nil

These variables will be set by the major mode, so they should be
buffer-local; use defvar-local.


> +(defvar find-definition-marker-ring
> +  (make-ring find-definition-marker-ring-length)
> +  "Ring of positions visited by `find-definition'.")

This variable might want to be mode-specific, but not buffer-local; I'm
not sure how to accomplish that.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-18  8:07     ` Stephen Leake
@ 2014-11-18 11:24       ` Helmut Eller
  2014-11-18 12:48         ` Dmitry Gutov
  2014-11-18 12:03       ` Helmut Eller
  2014-11-19 14:27       ` Stefan Monnier
  2 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-11-18 11:24 UTC (permalink / raw)
  To: emacs-devel

On Tue, Nov 18 2014, Stephen Leake wrote:

>> +(defvar find-definition-marker-ring
>> +  (make-ring find-definition-marker-ring-length)
>> +  "Ring of positions visited by `find-definition'.")
>
> This variable might want to be mode-specific, but not buffer-local; I'm
> not sure how to accomplish that.

I think this should be global and not mode-specific.  Making it global
makes it easier to switch between languages, e.g. from ELisp to C and
back.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-18  8:07     ` Stephen Leake
  2014-11-18 11:24       ` Helmut Eller
@ 2014-11-18 12:03       ` Helmut Eller
  2014-11-19 14:27       ` Stefan Monnier
  2 siblings, 0 replies; 172+ messages in thread
From: Helmut Eller @ 2014-11-18 12:03 UTC (permalink / raw)
  To: emacs-devel

On Tue, Nov 18 2014, Stephen Leake wrote:

>> +(defvar find-definition-function nil
>> +(defvar find-definition-identifier-function nil

Is it possible to use EIEIO instead?  I have a hunch that a "backend"
will need many "methods" in the long run.  My idea is that a mode
creates a subclass of something like a find-definition-backend class and
defines/overrides methods as appropriate for the mode.  The backend
object could then be stored in a buffer local variable.  Using EIEIO
classes and methods would arguably be clearer and more flexible than
defining a bunch of FOO-function variables.

In pseudocode:

;; interface of backend
(defclass find-definition-backend () ())
(defgeneric find-definition-identifier (backend))
(defgeneric find-definitions (backend identifier))

;; implementation of Elisp backend
(defclass elisp-backend (find-definition-backend) ())

(defmethod find-definition-identifier ((backend elisp-backend))
  (symbol-at-point))

(defmethod find-definitions ((backend elisp-backend) symbol)
  ...)

;; initialize buffer local backend variable in for elisp-mode:
(set (make-local-variable 'find-definition-backend)
     (make-instance 'elisp-backend))

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-18 11:24       ` Helmut Eller
@ 2014-11-18 12:48         ` Dmitry Gutov
  0 siblings, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-11-18 12:48 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

Helmut Eller <eller.helmut@gmail.com> writes:

> I think this should be global and not mode-specific.  Making it global
> makes it easier to switch between languages, e.g. from ELisp to C and
> back.

Global sounds better to me, too. But making storage flexible would be
even better. Some might prefer it to be project-local.

Personally, I'd rather make it local to a window, like implemented here:
https://github.com/dgutov/point-stack/blob/master/point-stack.el#L83



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-17 20:10   ` Jorgen Schaefer
  2014-11-18  8:07     ` Stephen Leake
@ 2014-11-18 16:31     ` Stefan Monnier
  2014-11-20  0:21       ` Stephen Leake
  2014-11-20 13:44     ` Helmut Eller
  2 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-18 16:31 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: emacs-devel

> - find-definition does not store locations in the tag ring yet
>   (ran out of time and wanted to post the initial patch for feedback)

That needs to be written before it can be installed.

> - The whole "find uses" interface

That can be written later.

> - etags.el integration

That needs to be written before it can be installed.

> - emacs-lisp-mode integration

That can be written later.  It should probably obsolete
find-definition-noselect.

> This also does define a minor mode instead of changing the global key
> bindings.  I think in the final version it should replace the key
> binding definitions done in etags.el.  Is this correct?

Yes, that's correct.

> Do I need to hook it up elsewhere in the build process?

I don't think so.

Thanks, it looks pretty good.  See further comments below.


        Stefan


> +(defcustom find-definition-marker-ring-length 16
> +  "Length of marker rings `find-definition-marker-ring'."
> +  :group 'find-definition
> +  :type 'integer)

The :group is redundant.

> +(defvar find-definition-function nil
> +  "The function `find-definition' calls to find the definition.
> +
> +Will be called with no arguments with point at the location of
> +the thing to find the definition for. It should return a list
> +with each element being a list of one to three elements. The
> +first element should be the file name, the second the
> +line (defaulting to 1) and the third the column (defaulting to
> +0).")

Please use two spaces between sentences.  Also we usually prefer to
describe things using "patterns", as in:

   Will be called with no arguments with point at the location of
   the thing to find the definition for.  It should return a list
   of elements of the form (FILE LINE COL) where LINE and COL can be
   omitted.")

> +(defvar find-definition-identifier-function nil

I suggest you collapse those two function variables into one: if called
with a nil value (or without argument), then just find the definitions
of "thing at point" and if called with a string, then find the
definitions of that identifier.

> +  (let ((map (make-sparse-keymap)))
> +    (define-key map (kbd "M-.") 'find-definition)
> +    (define-key map (kbd "C-x 4 .") 'find-definition-other-window)
> +    (define-key map (kbd "C-x 5 .") 'find-definition-other-frame)
> +    ;; (define-key map (kbd "M-_") 'find-definition-uses)
> +    (define-key map (kbd "M-,") 'find-definition-goto-last-position)

We should probably keep a M-* binding for now.

> +;;;###autoload
> +(define-minor-mode find-definition-mode
> +  "Minor mode to provide some key bindings to find definitions.
> +\\{find-definition-mode-map}"
> +  :keymap 'find-definition-mode)
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I don't think this really does what you want.  Better just completely
remove it [ Not that it matters since find-definition-mode should
simply disappear.  ].

> +;;;###autoload
> +(defun find-definition (&optional ask)
> +  "Go to the definition of the thing at point.
> +If the definition can not be found, or with a prefix argument,
> +prompt for a symbol to use."
> +  (interactive "P")
> +  (switch-to-buffer (find-definition--noselect ask)))

Rather than ask from the body of the function, I think the prompting
would be better done in the interactive form.  I.e.

   (defun find-definition (&optional identifier)
     "Go to the definition of the thing at point.
   If the definition can not be found, or with a prefix argument,
   prompt for a symbol to use."
     (interactive
      (if current-prefix-arg (list (find-definition--read-identifier))))
     (switch-to-buffer (find-definition--noselect identifier)))

This of course also suggests we should not do the "if the definition can
not be found, prompt for a symbol to use".

> +      ;; Exactly one definition

Please punctuate your comments.

> +      (with-current-buffer outbuf
> +        (erase-buffer)
> +        (setq default-directory dir)
> +        (compilation-mode)
> +        (dolist (location locations)
> +          (let* ((filename (elt location 0))
> +                 (line (or (elt location 1)
> +                           1))
> +                 (col (or (elt location 2)
> +                          0))
> +                 (buffer (find-buffer-visiting filename))
> +                 (line-string
> +                  (when buffer
> +                    (with-current-buffer buffer
> +                      (save-excursion
> +                        (save-restriction
> +                          (widen)
> +                          (goto-char (point-min))
> +                          (forward-line (- line 1))
> +                          (buffer-substring (line-beginning-position)
> +                                            (line-end-position))))))))
> +            (insert (format "%s:%s:%s:%s\n"
> +                            filename line col
> +                            (or line-string
> +                                "")))))

We can probably use a new mode that derives from compilation-mode.
That will let us use a more efficient and reliable regexp to match the
entries we put in there.
Also, I think that when the LINE&COL are absent, we should not move to
LINE=1, but instead just leave point alone in that buffer (and in the
list, we should not explicitly say "FILE:1:0" but just "FILE:" or
something like that).

One remaining issue is with the filenames themselves: the
find-definition-function may very well (and maybe should usually) return
absolute file names, so we should "prettify" them (make them relative)
before displaying them in the *Definitions* buffer.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-18  8:07     ` Stephen Leake
  2014-11-18 11:24       ` Helmut Eller
  2014-11-18 12:03       ` Helmut Eller
@ 2014-11-19 14:27       ` Stefan Monnier
  2014-11-19 14:51         ` Ivan Shmakov
  2014-11-20  0:15         ` Stephen Leake
  2 siblings, 2 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-19 14:27 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

>> +(defvar find-definition-function nil
>> +(defvar find-definition-identifier-function nil
> These variables will be set by the major mode, so they should be
> buffer-local; use defvar-local.

Bad idea: if the major modes set those with `setq' rather than with
`setq-local', they'll end up setting the global value if the `setq'
happens to be run before loading find-definition.el (which is quite
possible since find-definition.el will typically be loaded via an
autoload rather than via a `require').


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-19 14:27       ` Stefan Monnier
@ 2014-11-19 14:51         ` Ivan Shmakov
  2014-11-19 22:31           ` Stefan Monnier
  2014-11-20  0:15         ` Stephen Leake
  1 sibling, 1 reply; 172+ messages in thread
From: Ivan Shmakov @ 2014-11-19 14:51 UTC (permalink / raw)
  To: emacs-devel

>>>>> Stefan Monnier <monnier@IRO.UMontreal.CA> writes:

 >>> +(defvar find-definition-function nil
 >>> +(defvar find-definition-identifier-function nil

 >> These variables will be set by the major mode, so they should be
 >> buffer-local; use defvar-local.

 > Bad idea: if the major modes set those with `setq' rather than with
 > `setq-local', they'll end up setting the global value if the `setq'
 > happens to be run before loading find-definition.el (which is quite
 > possible since find-definition.el will typically be loaded via an
 > autoload rather than via a `require').

	Can’t we ;;;###autoload the defvar-local forms themselves then?

-- 
FSF associate member #7257  np. Benediction — Jami Sieber   … B6A0 230E 334A



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-19 14:51         ` Ivan Shmakov
@ 2014-11-19 22:31           ` Stefan Monnier
  0 siblings, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-19 22:31 UTC (permalink / raw)
  To: Ivan Shmakov; +Cc: emacs-devel

> 	Can’t we ;;;###autoload the defvar-local forms themselves then?

Why would we want to?  Just tell the major-modes to use `setq-local',
like they do for all other variables.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-19 14:27       ` Stefan Monnier
  2014-11-19 14:51         ` Ivan Shmakov
@ 2014-11-20  0:15         ` Stephen Leake
  2014-11-20  4:18           ` Stefan Monnier
  1 sibling, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-11-20  0:15 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@IRO.UMontreal.CA> writes:

>>> +(defvar find-definition-function nil
>>> +(defvar find-definition-identifier-function nil
>> These variables will be set by the major mode, so they should be
>> buffer-local; use defvar-local.
>
> Bad idea: if the major modes set those with `setq' rather than with
> `setq-local', they'll end up setting the global value if the `setq'
> happens to be run before loading find-definition.el (which is quite
> possible since find-definition.el will typically be loaded via an
> autoload rather than via a `require').

This _should_ be caught by a byte-compiler warning
("find-definition-function not known"). So I'm ok with requiring major
modes to either have "(require find-definition)" or use setq-local.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-18 16:31     ` Stefan Monnier
@ 2014-11-20  0:21       ` Stephen Leake
  2014-11-20  4:19         ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-11-20  0:21 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@IRO.UMontreal.CA> writes:

>> +(defvar find-definition-identifier-function nil
>
> I suggest you collapse those two function variables into one: if called
> with a nil value (or without argument), then just find the definitions
> of "thing at point" and if called with a string, then find the
> definitions of that identifier.

Keeping two variables puts that logic in the front-end, so all the
backends don't have to implement it, and we can change it for everyone
consistently. 

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20  0:15         ` Stephen Leake
@ 2014-11-20  4:18           ` Stefan Monnier
  0 siblings, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-20  4:18 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

> This _should_ be caught by a byte-compiler warning
> ("find-definition-function not known").

I wouldn't bet on it.

> So I'm ok with requiring major modes to either have "(require
> find-definition)" or use setq-local.

The normal course of event for major mode is to set variables via
`setq-local' (or its equivalent for use on older Emacsen).  So there's
no good reason to use make-variable-buffer-local for that; it'd just be
asking for trouble.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20  0:21       ` Stephen Leake
@ 2014-11-20  4:19         ` Stefan Monnier
  2014-11-20 20:21           ` Jorgen Schaefer
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-20  4:19 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

>>> +(defvar find-definition-identifier-function nil
>> I suggest you collapse those two function variables into one: if called
>> with a nil value (or without argument), then just find the definitions
>> of "thing at point" and if called with a string, then find the
>> definitions of that identifier.
> Keeping two variables puts that logic in the front-end, so all the
> backends don't have to implement it, and we can change it for everyone
> consistently. 

I think both in the front-end and in many backends, the two cases will
share a lot of code, so using a single var will simplify code in the
front-end and in many backends as well.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-17 20:10   ` Jorgen Schaefer
  2014-11-18  8:07     ` Stephen Leake
  2014-11-18 16:31     ` Stefan Monnier
@ 2014-11-20 13:44     ` Helmut Eller
  2014-11-20 20:28       ` Jorgen Schaefer
  2014-11-23 13:44       ` Johan Claesson
  2 siblings, 2 replies; 172+ messages in thread
From: Helmut Eller @ 2014-11-20 13:44 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 524 bytes --]

On Mon, Nov 17 2014, Jorgen Schaefer wrote:

> Some initial patch attached.

I'm proposing an alternative implementation.  This version uses EIOIO
which, I think, makes it more flexible.  Proof-of-concept-quality
backends for elisp and etags are included.  The UI is lifted from SLIME
(with some simplifications).  To try it out, load xref.el and then M-x
xref-minor-mode to turn it on.  It's not fully polished but good enough
to show the main ideas.

Also available at: https://github.com/ellerh/xref/blob/master/xref.el


[-- Attachment #2: xref.el --]
[-- Type: application/emacs-lisp, Size: 15519 bytes --]

[-- Attachment #3: Type: text/plain, Size: 8 bytes --]


Helmut

^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20  4:19         ` Stefan Monnier
@ 2014-11-20 20:21           ` Jorgen Schaefer
  0 siblings, 0 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-20 20:21 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Stephen Leake, emacs-devel

On Wed, 19 Nov 2014 23:19:28 -0500
Stefan Monnier <monnier@IRO.UMontreal.CA> wrote:

> >>> +(defvar find-definition-identifier-function nil
> >> I suggest you collapse those two function variables into one: if
> >> called with a nil value (or without argument), then just find the
> >> definitions of "thing at point" and if called with a string, then
> >> find the definitions of that identifier.
> > Keeping two variables puts that logic in the front-end, so all the
> > backends don't have to implement it, and we can change it for
> > everyone consistently. 
> 
> I think both in the front-end and in many backends, the two cases will
> share a lot of code, so using a single var will simplify code in the
> front-end and in many backends as well.

As one concrete example, for Elpy, the two will share absolutely no
code. But the difference is minuscle:

(setq find-definition-function 'elpy-find-definition-by-location)
(setq find-definition-identifier-function 'elpy-find-definition-by-identifier)

vs.

(setq find-definition-function 'elpy-find-definition-by-location-or-identifier)

(defun elpy-find-definition-by-location-or-identifier (&optional identifier)
  (if symbol
      (elpy-find-definition-by-identifier identifier)
    (elpy-find-definition-by-location)))

So I do not really care either way.

Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20 13:44     ` Helmut Eller
@ 2014-11-20 20:28       ` Jorgen Schaefer
  2014-11-20 20:42         ` Helmut Eller
  2014-11-20 23:27         ` Stefan Monnier
  2014-11-23 13:44       ` Johan Claesson
  1 sibling, 2 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-20 20:28 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On Thu, 20 Nov 2014 14:44:54 +0100
Helmut Eller <eller.helmut@gmail.com> wrote:

> On Mon, Nov 17 2014, Jorgen Schaefer wrote:
> 
> > Some initial patch attached.
> 
> I'm proposing an alternative implementation.

Fine with me, too, as long as I have a trivial way of providing common
functionality. (M-. is by far not the only place.)

I did not see any tests in the repo, did I look in the wrong spot?

Before I continue to put work into this, Stefan, which way do you
prefer - functions or EIEIO?

Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20 20:28       ` Jorgen Schaefer
@ 2014-11-20 20:42         ` Helmut Eller
  2014-11-20 23:27         ` Stefan Monnier
  1 sibling, 0 replies; 172+ messages in thread
From: Helmut Eller @ 2014-11-20 20:42 UTC (permalink / raw)
  To: emacs-devel

On Thu, Nov 20 2014, Jorgen Schaefer wrote:

> I did not see any tests in the repo, did I look in the wrong spot?

No, there are no tests.  To try things out use M-x xref-minor-mode and
then press M-. on a symbol.  I only tried it with emacs-lisp-mode and
the C code of Emacs.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20 20:28       ` Jorgen Schaefer
  2014-11-20 20:42         ` Helmut Eller
@ 2014-11-20 23:27         ` Stefan Monnier
  2014-11-20 23:42           ` Jorgen Schaefer
  1 sibling, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-11-20 23:27 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: Helmut Eller, emacs-devel

> Before I continue to put work into this, Stefan, which way do you
> prefer - functions or EIEIO?

Either way is fine by me,


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20 23:27         ` Stefan Monnier
@ 2014-11-20 23:42           ` Jorgen Schaefer
  2014-11-21  3:05             ` Stefan Monnier
  2014-11-21  8:24             ` martin rudalics
  0 siblings, 2 replies; 172+ messages in thread
From: Jorgen Schaefer @ 2014-11-20 23:42 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Helmut Eller, emacs-devel

On Thu, 20 Nov 2014 18:27:08 -0500
Stefan Monnier <monnier@IRO.UMontreal.CA> wrote:

> > Before I continue to put work into this, Stefan, which way do you
> > prefer - functions or EIEIO?
> 
> Either way is fine by me,

I am afraid someone will have to pick one, because we can hardly
include both in Emacs. And I am not sure who would be able to pick one
if not you.

How would you like us to decide which one to include?

(Pistols at dawn might be overdoing it!)

Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20 23:42           ` Jorgen Schaefer
@ 2014-11-21  3:05             ` Stefan Monnier
  2014-11-21  8:24             ` martin rudalics
  1 sibling, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-21  3:05 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: Helmut Eller, emacs-devel

> (Pistols at dawn might be overdoing it!)

Just remember to record it in a Free format,


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20 23:42           ` Jorgen Schaefer
  2014-11-21  3:05             ` Stefan Monnier
@ 2014-11-21  8:24             ` martin rudalics
  2014-11-30 13:29               ` Stefan Monnier
  1 sibling, 1 reply; 172+ messages in thread
From: martin rudalics @ 2014-11-21  8:24 UTC (permalink / raw)
  To: Jorgen Schaefer, Stefan Monnier; +Cc: Helmut Eller, emacs-devel

> I am afraid someone will have to pick one, because we can hardly
> include both in Emacs.

Why not?  If you're interested I can send you mine too ;-)

martin




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-20 13:44     ` Helmut Eller
  2014-11-20 20:28       ` Jorgen Schaefer
@ 2014-11-23 13:44       ` Johan Claesson
  2014-12-01 17:31         ` Helmut Eller
  1 sibling, 1 reply; 172+ messages in thread
From: Johan Claesson @ 2014-11-23 13:44 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel



Awesome library.  For people just as lazy as me i recommend the
following keybindings to further reduce finger movement:

(define-key xref--xref-buffer-mode-map [?,] 'xref-prev-line)
(define-key xref--xref-buffer-mode-map [?.] 'xref-next-line)

Regards,

/Johan



Helmut Eller <eller.helmut@gmail.com> writes:

> On Mon, Nov 17 2014, Jorgen Schaefer wrote:
>
>> Some initial patch attached.
>
> I'm proposing an alternative implementation.  This version uses EIOIO
> which, I think, makes it more flexible.  Proof-of-concept-quality
> backends for elisp and etags are included.  The UI is lifted from SLIME
> (with some simplifications).  To try it out, load xref.el and then M-x
> xref-minor-mode to turn it on.  It's not fully polished but good enough
> to show the main ideas.
>
> Also available at: https://github.com/ellerh/xref/blob/master/xref.el
>
>
>
>
> Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-21  8:24             ` martin rudalics
@ 2014-11-30 13:29               ` Stefan Monnier
  0 siblings, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-11-30 13:29 UTC (permalink / raw)
  To: martin rudalics; +Cc: emacs-devel, Helmut Eller, Jorgen Schaefer

>> I am afraid someone will have to pick one, because we can hardly
>> include both in Emacs.
> Why not?  If you're interested I can send you mine too ;-)

We can include them all.  But we can only bind M-. to one of them in the
default configuration.  And I don't think we want to ask major modes to
provide more than one backend.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-11-23 13:44       ` Johan Claesson
@ 2014-12-01 17:31         ` Helmut Eller
  2014-12-04  3:13           ` Stephen Leake
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-01 17:31 UTC (permalink / raw)
  To: Johan Claesson; +Cc: emacs-devel

On Sun, Nov 23 2014, Johan Claesson wrote:

> Awesome library.  For people just as lazy as me i recommend the
> following keybindings to further reduce finger movement:
>
> (define-key xref--xref-buffer-mode-map [?,] 'xref-prev-line)
> (define-key xref--xref-buffer-mode-map [?.] 'xref-next-line)

Perhaps a bit exotic, but I guess it doesn't hurt to have them.
I added it to the code at: https://github.com/ellerh/xref.

I also changed the interface to find the backend: the buffer-local
variable xref-backend is replaced by xref-backend-function -- a function
that should return the backend.  This indirection makes it easier to
autoload xref.el.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-01 17:31         ` Helmut Eller
@ 2014-12-04  3:13           ` Stephen Leake
  2014-12-04  8:07             ` Stephen Leake
  2014-12-04  9:11             ` Helmut Eller
  0 siblings, 2 replies; 172+ messages in thread
From: Stephen Leake @ 2014-12-04  3:13 UTC (permalink / raw)
  To: emacs-devel

Using the code at: https://github.com/ellerh/xref, I've written an
ada-mode backend (not completion yet). It was quite straight-forward,
and I like the dispatching that eieio provides.

So I recommend we merge this code into emacs master.

A couple comments:

The current copyright on xref.el is Helmut Eller; I assume you've got a
copyright assignment on file.

I'd like to add to xref.el:

(defun xref-find-definition-at-point ()
  (interactive)
  (xref--find-definition (xref-identifier-at-point (xref--backend)) nil))

That's the function I use most often.


There is a FIXME on xref-push-marker-stack. I gather you'd like this to
be independent of etags?

It would not be hard to implement an independent marker ring/stack. But
I think it makes more sense to use the etags marker ring; that way,
if I am navigating thru code that uses multiple languages, and one
language mode uses xref while another uses tags, there is still only one
tag ring.

Eventually, when most modes have migrated to xref, it might make sense
to switch to a separate marker ring.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-04  3:13           ` Stephen Leake
@ 2014-12-04  8:07             ` Stephen Leake
  2014-12-04 12:45               ` Helmut Eller
  2014-12-04  9:11             ` Helmut Eller
  1 sibling, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-12-04  8:07 UTC (permalink / raw)
  To: emacs-devel

Stephen Leake <stephen_leake@stephe-leake.org> writes:

> Using the code at: https://github.com/ellerh/xref, I've written an
> ada-mode backend (not completion yet). It was quite straight-forward,
> and I like the dispatching that eieio provides.
>
> So I recommend we merge this code into emacs master.
>
> A couple comments:

I used xref--file-location, which is an internal symbol:

(defmethod xref-find-definitions ((_ xref-ada-backend) identifier)
    ...
    (list
     (xref-make
     ""
     (make-instance
      'xref--file-location
      :file (nth 0 target)
      :line (nth 1 target)
      :column (nth 2 target))))
    ))

There is a non-internal alternative xref-file-location:

(defmethod xref-find-definitions ((_ xref-ada-backend) identifier)
    ...
    (list
     (xref-make
     ""
     (xref-file-location
      (nth 0 target)
      (nth 1 target)
      (nth 2 target)))
    ))

I find the first more readable; that's part of the point of using the
class paradigm.

So I suggest promoting the classes in xref.el to a supported part of the
API.

In addition, that allows the code in '...' to change to also use the
xref--file-location class, which would be a Good Thing.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-04  3:13           ` Stephen Leake
  2014-12-04  8:07             ` Stephen Leake
@ 2014-12-04  9:11             ` Helmut Eller
  2014-12-04 16:19               ` Stephen Leake
  1 sibling, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-04  9:11 UTC (permalink / raw)
  To: emacs-devel; +Cc: Stephen Leake

On Wed, Dec 03 2014, Stephen Leake wrote:

[...]
> A couple comments:
>
> The current copyright on xref.el is Helmut Eller; I assume you've got a
> copyright assignment on file.

Yes, I have the paperwork.

> I'd like to add to xref.el:
>
> (defun xref-find-definition-at-point ()
>   (interactive)
>   (xref--find-definition (xref-identifier-at-point (xref--backend)) nil))
>
> That's the function I use most often.

Hmm, xref-find-definition does this, except for the case when it's not
called interactively.  Do you need the non-interactive version?  Maybe
it would be better to make xref-find-definition more useful
non-interactively than to define an almost identical function.

> There is a FIXME on xref-push-marker-stack. I gather you'd like this to
> be independent of etags?
>
> It would not be hard to implement an independent marker ring/stack. But
> I think it makes more sense to use the etags marker ring; that way,
> if I am navigating thru code that uses multiple languages, and one
> language mode uses xref while another uses tags, there is still only one
> tag ring.
>
> Eventually, when most modes have migrated to xref, it might make sense
> to switch to a separate marker ring.

My idea was to move the ring from etags.el to xref.el and perhaps define
some aliases for backward compatibility.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-04  8:07             ` Stephen Leake
@ 2014-12-04 12:45               ` Helmut Eller
  0 siblings, 0 replies; 172+ messages in thread
From: Helmut Eller @ 2014-12-04 12:45 UTC (permalink / raw)
  To: emacs-devel

On Thu, Dec 04 2014, Stephen Leake wrote:

[...]
> I used xref--file-location, which is an internal symbol:
[...]
> There is a non-internal alternative xref-file-location:
[...]

My intention for that was to export constructors but keep the
representation private.

> So I suggest promoting the classes in xref.el to a supported part of the
> API.

OK. I have export the class symbols now and renamed the constructors
to xref-make-FOO.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-04  9:11             ` Helmut Eller
@ 2014-12-04 16:19               ` Stephen Leake
  2014-12-04 16:49                 ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-12-04 16:19 UTC (permalink / raw)
  To: emacs-devel

Helmut Eller <eller.helmut@gmail.com> writes:

> On Wed, Dec 03 2014, Stephen Leake wrote:
>
> [...]
>> A couple comments:
>>
>> The current copyright on xref.el is Helmut Eller; I assume you've got a
>> copyright assignment on file.
>
> Yes, I have the paperwork.
>
>> I'd like to add to xref.el:
>>
>> (defun xref-find-definition-at-point ()
>>   (interactive)
>>   (xref--find-definition (xref-identifier-at-point (xref--backend)) nil))
>>
>> That's the function I use most often.
>
> Hmm, xref-find-definition does this, except for the case when it's not
> called interactively.  

I bound xref-find-definition to a key. When I invoke that key, I'm
prompted for the identifier; the default is the identifier at point, but
I still have to hit enter. I want to eliminate that prompt/enter step.

Currently in ada-mode, C-c C-d gives the non-prompt identifier at point
behavior; that makes it very fluid to drill down thru several layers of
calls.

> Do you need the non-interactive version? Maybe
> it would be better to make xref-find-definition more useful
> non-interactively than to define an almost identical function.

I think xref-find-definition is fine as is; you can call it
interactively to be prompted with completion, or programmatically and
pass it an identifier. But that misses the interactive non-prompt case.
We could use C-u or some other prefix to control that, but a separate
function is easier to bind to a key.

>> There is a FIXME on xref-push-marker-stack. I gather you'd like this to
>> be independent of etags?
>>
>> It would not be hard to implement an independent marker ring/stack. But
>> I think it makes more sense to use the etags marker ring; that way,
>> if I am navigating thru code that uses multiple languages, and one
>> language mode uses xref while another uses tags, there is still only one
>> tag ring.
>>
>> Eventually, when most modes have migrated to xref, it might make sense
>> to switch to a separate marker ring.
>
> My idea was to move the ring from etags.el to xref.el and perhaps define
> some aliases for backward compatibility.

+1

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-04 16:19               ` Stephen Leake
@ 2014-12-04 16:49                 ` Helmut Eller
  2014-12-05  9:43                   ` Stephen Leake
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-04 16:49 UTC (permalink / raw)
  To: emacs-devel

On Thu, Dec 04 2014, Stephen Leake wrote:

>> Hmm, xref-find-definition does this, except for the case when it's not
>> called interactively.  
>
> I bound xref-find-definition to a key. When I invoke that key, I'm
> prompted for the identifier; the default is the identifier at point, but
> I still have to hit enter.

Something's odd here.  xref-find-definition does NOT prompt by default.
E.g. if you invoke M-x xref-find-definition in an elisp buffer with the
text "cons" around point then it should take you immediately to alloc.c
without any prompting.  xref-find-definition should prompt only if
either xref-identifier-at-point returns nil or if it's invoked with
prefix argument.

> I want to eliminate that prompt/enter step.

So do I.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-04 16:49                 ` Helmut Eller
@ 2014-12-05  9:43                   ` Stephen Leake
  2014-12-05 13:25                     ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-12-05  9:43 UTC (permalink / raw)
  To: emacs-devel

Helmut Eller <eller.helmut@gmail.com> writes:

> On Thu, Dec 04 2014, Stephen Leake wrote:
>
>>> Hmm, xref-find-definition does this, except for the case when it's not
>>> called interactively.  
>>
>> I bound xref-find-definition to a key. When I invoke that key, I'm
>> prompted for the identifier; the default is the identifier at point, but
>> I still have to hit enter.
>
> Something's odd here.  xref-find-definition does NOT prompt by
> default.

Ah. It only prompts if xref-identifier-at-point returns nil; I must have
tested find-definition before I finished implementing
identifier-at-point.

Sorry for the noise.


Other comments:

xref.el needs (provide 'xref).


For elisp, when I have a variable and function with the same name, the
*xref* buffer shows:

c:/Projects/org.emacs.ada-mode/wisi.el
  wisi-number-p
  (defvar wisi-number-p)

That would be more consistent if the first reference was shown as:

  (defun wisi-number-p)


In ada-mode, I use compilation-mode for showing multiple references;
that is a familiar UI. I gather the mode you implemented is similar to
SLIME? Perhaps we need another dispatch/user option to choose this UI?


For Ada, only "find uses of identifier" returns multiple locations. That
function is not in xref.el yet. I suggest:

(defun xref-find-references (&optional identifier)
  (interactive (list (xref--read-identifier "Find references of: ")))
  (xref--find-references identifier nil))

similar to find-definitions.

Hmm, the dispatching name has to be different; perhaps
xref-find-references-m (for 'method')?

I'd actually prefer xref-find-definitions as the user function, and
xref-find-defintions-m as the dispatching function; the user function
can show more than one definition.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-05  9:43                   ` Stephen Leake
@ 2014-12-05 13:25                     ` Helmut Eller
  2014-12-05 17:41                       ` Stephen Leake
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-05 13:25 UTC (permalink / raw)
  To: emacs-devel

On Fri, Dec 05 2014, Stephen Leake wrote:

> Other comments:
>
> xref.el needs (provide 'xref).

Added.

[...]
> That would be more consistent if the first reference was shown as:
>
>   (defun wisi-number-p)

OK.

> In ada-mode, I use compilation-mode for showing multiple references;
> that is a familiar UI. I gather the mode you implemented is similar to
> SLIME?

Yes, it's a simplified version of SLIME's UI.

> Perhaps we need another dispatch/user option to choose this UI?

I added a variable xref-show-xrefs-function so that people can
experiment with alternative UIs.  I'm not sure if compilation-mode is
able to handle locations that can't be represented as simple strings.

[...]
> I'd actually prefer xref-find-definitions as the user function, and
> xref-find-defintions-m as the dispatching function; the user function
> can show more than one definition.

I decided to call the backend functions xref-lookup-defintions and
xref-lookup-references and the commands xref-find-defintions
resp. xref-find-references.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-05 13:25                     ` Helmut Eller
@ 2014-12-05 17:41                       ` Stephen Leake
  2014-12-06  8:55                         ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-12-05 17:41 UTC (permalink / raw)
  To: emacs-devel

Helmut Eller <eller.helmut@gmail.com> writes:

>> Perhaps we need another dispatch/user option to choose this UI?
>
> I added a variable xref-show-xrefs-function so that people can
> experiment with alternative UIs.  I'm not sure if compilation-mode is
> able to handle locations that can't be represented as simple strings.

Ok, I'll give that a try.

> [...]
>> I'd actually prefer xref-find-definitions as the user function, and
>> xref-find-defintions-m as the dispatching function; the user function
>> can show more than one definition.
>
> I decided to call the backend functions xref-lookup-defintions and
> xref-lookup-references and the commands xref-find-defintions
> resp. xref-find-references.

Ok.


Another aspect of "cross reference" is to follow links such as:

1) http://www.gnu.org/software/emacs

2) admin/notes/commits

3) (info "(elisp)Syntax Class Table" "*info syntax class table*")

Currently, 1) is handled by browse-url-at-point, which is not bound to
any key by default.

Similarly, 2) is find-file-at-point

3) is handled by C-x C-e; I don't think we need to change that.

I have a function that combines 1 and 2 and similar links; it calls
ffap-string-at-point, compares that to an alist of (regexp . command),
and then defaults to find-file. The alist has:

      (cons "^ftp://" 'browse-url-at-point)
      (cons "^http://" 'browse-url-at-point)
      (cons "^https://" 'browse-url-at-point)
      (cons "\\.bmp$" 'sal-w32-open)
      (cons "\\.bz2$" 'sal-w32-open)

etc.

Perhaps a similar function could be included in xref?

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-05 17:41                       ` Stephen Leake
@ 2014-12-06  8:55                         ` Helmut Eller
  2014-12-06 18:19                           ` Stephen Leake
                                             ` (2 more replies)
  0 siblings, 3 replies; 172+ messages in thread
From: Helmut Eller @ 2014-12-06  8:55 UTC (permalink / raw)
  To: emacs-devel

On Fri, Dec 05 2014, Stephen Leake wrote:

[...]
> I have a function that combines 1 and 2 and similar links; it calls
> ffap-string-at-point, compares that to an alist of (regexp . command),
> and then defaults to find-file. The alist has:
[...]
> Perhaps a similar function could be included in xref?

Just thinking aloud:

I guess such filenames or URLs (URIs?) typically occur in comments,
string literals, or perhaps in things like #include <stdio.h> or
(require 'some-library).  M-x ffap seems cover this partially, but then
there's also M-x find-library.  Certainly a lot of things to find!

A problem seems to be that xref-identifier-at-point would need a
possibly complicated heuristic to determine if we are at such a filename
or a "normal" identifier.  Maybe it's easier to have a separate
xref-file-name-at-point which would by default do what
ffap-guess-file-name-at-point does.  For ELisp it should additionally
recognize (require 'foo) and somehow reuse find-library.

There's also the problem which keybinding to use.  We will probably
steal the global bindings for M-. and M-, from etags but beyond that
we don't have keys for our commands.  At least one additional prefix key
for a xref keymap would be nice.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-06  8:55                         ` Helmut Eller
@ 2014-12-06 18:19                           ` Stephen Leake
  2014-12-06 18:38                           ` Drew Adams
  2014-12-06 22:57                           ` Stefan Monnier
  2 siblings, 0 replies; 172+ messages in thread
From: Stephen Leake @ 2014-12-06 18:19 UTC (permalink / raw)
  To: emacs-devel

Helmut Eller <eller.helmut@gmail.com> writes:

> On Fri, Dec 05 2014, Stephen Leake wrote:
>
> [...]
>> I have a function that combines 1 and 2 and similar links; it calls
>> ffap-string-at-point, compares that to an alist of (regexp . command),
>> and then defaults to find-file. The alist has:
> [...]
>> Perhaps a similar function could be included in xref?
>
> Just thinking aloud:
>
> I guess such filenames or URLs (URIs?) typically occur in comments,
> string literals, or perhaps in things like #include <stdio.h> or
> (require 'some-library).  M-x ffap seems cover this partially, but then
> there's also M-x find-library.  Certainly a lot of things to find!

I just added this for (require 'foo):

  (set (make-local-variable 'ff-search-directories) 'load-path)
  (set (make-local-variable 'ff-special-constructs) nil)
  (add-to-list
   'ff-special-constructs
   (cons "^(require '\\(.*\\))" (lambda () (file-name-nondirectory (locate-library (match-string 1))))))

That's used by ff-find-other-file.

> A problem seems to be that xref-identifier-at-point would need a
> possibly complicated heuristic to determine if we are at such a filename
> or a "normal" identifier.  

Yes, I think that requires input from the user. Currently, I use C-F11
(bound to 'ff-find-other-file') when the thing at point is something
related to a filename ('require' in elisp, '#include' in C++, 'with' in
Ada), and C-c C-d (now bound to 'xref-find-definitions') when the thing
at point is a symbol.

We could just search for both, and present a list, but I think that
would be more cumbersome than it's worth.

> Maybe it's easier to have a separate xref-file-name-at-point which
> would by default do what ffap-guess-file-name-at-point does. For ELisp
> it should additionally recognize (require 'foo) and somehow reuse
> find-library.

find-file.el and files.el provide similarly customizable 'find a file'
functions. It would be nice to unify them, but that's a lot of work.

I'm most familiar with find-file, but it could easily be that files is a
better structure.

> There's also the problem which keybinding to use.  We will probably
> steal the global bindings for M-. and M-, from etags but beyond that
> we don't have keys for our commands.  At least one additional prefix key
> for a xref keymap would be nice.

ada-mode uses C-c C-o for a wrapper around ff-find-other-file. I don't
see a similar binding in c-mode.

We would probably need to keep xref-minor-mode for now, and provide a
binding in there. Then if it becomes popular, it can be promoted to a
global binding.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* RE: Generalizing find-definition
  2014-12-06  8:55                         ` Helmut Eller
  2014-12-06 18:19                           ` Stephen Leake
@ 2014-12-06 18:38                           ` Drew Adams
  2014-12-07 16:52                             ` Stephen Leake
  2014-12-06 22:57                           ` Stefan Monnier
  2 siblings, 1 reply; 172+ messages in thread
From: Drew Adams @ 2014-12-06 18:38 UTC (permalink / raw)
  To: Helmut Eller, emacs-devel

> A problem seems to be that xref-identifier-at-point would need a
> possibly complicated heuristic to determine if we are at such a
> filename or a "normal" identifier. Maybe it's easier to have a
> separate xref-file-name-at-point which would by default do what
> ffap-guess-file-name-at-point does.  For ELisp it should
> additionally recognize (require 'foo) and somehow reuse find-library.

And..., and somehow..., and..., and somehow...

In general, this is the wrong approach, I think.

Q. When does reuse of a file name or a URL or... at point make
   sense?
A. As input to a command that acts on a file name or a URL or...

The *particular command* already knows what kind of thing it
expects.  All that's needed, as a *general* mechanism, is a
general way to pull text at point into the minibuffer.

That's where the design effort should be, IMO.  And this
mechanism should be entirely decoupled from any particular
use of the grabbed text.  That's my main point here.

In particular, `find-definition' itself might be misguided.
I cannot really speak to whether it is, but that's my hunch.

Doesn't a user know what kind of thing definition s?he wants
to look up?  Does s?he really need Emacs to guess what kind
of thing is at point (a guess that is fraught with ambiguity)?

The problem comes down, I think, to providing a key (or keys)
that will *grab what the user wants at point*.

We should then bind that key in `minibuffer-local-map' so that
it is available *always*, regardless of the minibuffer-reading
command and the particular way of reading (with completion or
not, and for any kind of completion).

There is room for imagination and invention here, and that is
where the effort should be placed, IMHO.

Let's suppose that we start by trying for just a single key -
`M-.', for instance.  Clearly, adding more "grab" keys would
make it easier for a user to make known to Emacs what to grab,
but it would also make users know more keys etc.  We can always
add more.  Let's see what can be done with one, for starters.

As food for thought, here is a description of what `M-.' does
in Icicles, from the minibuffer.  I don't claim that this is
the full solution - at all.  As I said, I think there is plenty
of room for invention, to come up with good ways for a user to
tell Emacs what thing(s) to grab at point.

Icicles currently uses a single key, `M-.', to grab stuff into
the minibuffer from point.  You customize what your general
preferences are in this regard.

The choice you make is whether repeated use of `M-.' should,
by default, grab (a) additional stuff of the same type or
(b) alternative stuff of different types (i.e., replacing,
in the minibuffer, what the previous invocation grabbed).

You can override your general preference (alternatives vs
more-of-the-same) on the fly when you use `M-.', by
providing a prefix arg.

For example, if you generally prefer that repeated `M-.'
grab different kinds of thing at point (default behavior),
then `C-u M-.' switches the behavior temporarily so that
repeating `M-.' grabs successive occurrences of the same
kind of thing.

There is additional, more complex behavior available as
well.  You can customize the sequence of functions used to
grab different kinds of thing.  You can grab successive
things of the same type to the left or to the right of
point.  You can grab alternative things at point and
insert not the grabbed text but the result of evaluating
it as a Lisp sexp.

This page describes the behavior of `M-.' in Icicles:
http://www.emacswiki.org/Icicles_-_Inserting_Text_from_Cursor
And this part describes the details outlined above:
http://www.emacswiki.org/Icicles_-_Inserting_Text_from_Cursor#RepeatedM.

To be clear, I am not at all proposing this same behavior
for Emacs.  I point to it as food for thought.

What I am suggesting is only this:

1. It makes sense to concentrate on coming up with a
   useful, general-purpose grab-text-at-point minibuffer
   command, not with a general-purpose `find-definition'
   top-level command.

2. What Icicles does in this regard might serve as some
   food for thought.  But there are surely more and
   better possibilities.

3. Everything useful need not be combined in a single
   minibuffer command (such as Icicles tries to do with
   `M-.').  Having different, easily understood behaviors
   on separate keys can also be a good way to go.

HTH.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-06  8:55                         ` Helmut Eller
  2014-12-06 18:19                           ` Stephen Leake
  2014-12-06 18:38                           ` Drew Adams
@ 2014-12-06 22:57                           ` Stefan Monnier
  2014-12-07  9:55                             ` Helmut Eller
  2 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-12-06 22:57 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

> I guess such filenames or URLs (URIs?) typically occur in comments,
> string literals, or perhaps in things like #include <stdio.h> or
> (require 'some-library).  M-x ffap seems cover this partially, but then
> there's also M-x find-library.  Certainly a lot of things to find!

Indeed, and I don't think M-. should care about those cases.
C-x C-f is supposed to handle those cases instead.

This said, I think the most urgent step is to get such a generalized
find-definition into Emacs.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-06 22:57                           ` Stefan Monnier
@ 2014-12-07  9:55                             ` Helmut Eller
  2014-12-08 14:33                               ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-07  9:55 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 164 bytes --]

On Sat, Dec 06 2014, Stefan Monnier wrote:

> This said, I think the most urgent step is to get such a generalized
> find-definition into Emacs.

Here is a patch:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Generalized-infrastructure-for-find-definition.patch --]
[-- Type: text/x-diff, Size: 26291 bytes --]

From 001c23b21450001667adaa27d0b27061a4c26c3c Mon Sep 17 00:00:00 2001
From: Helmut Eller <eller.helmut@gmail.com>
Date: Sun, 7 Dec 2014 10:46:33 +0100
Subject: [PATCH] Generalized infrastructure for find-definition

* progmodes/xref.el: New file.

* progmodes/etags.el (find-tag-marker-ring, pop-tag-mark): Move to
xref but keep aliases for backward compatibility.
(tags-reset-tags-tables): Use xref marker stack instead of
find-tag-marker-ring.
(etags--xref-backend, etags--xref-backend-var)
(etags-xref-backend-function): New xref backend.
(esc-map, ctl-x-4-map, ctl-x-5-map): Move key bindings for M-.,
M-, C-x 4 M-., and C-x 5 M-. to xref.el

* progmodes/elisp-mode.el (emacs-lisp-mode): Initialize
xref-backend-function.
---
 lisp/ChangeLog               |   18 ++
 lisp/progmodes/elisp-mode.el |    1 +
 lisp/progmodes/etags.el      |   80 +++++--
 lisp/progmodes/xref.el       |  491 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 567 insertions(+), 23 deletions(-)
 create mode 100644 lisp/progmodes/xref.el

diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index b3cb2fa..46baac5 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,21 @@
+2014-12-07  Helmut Eller  <eller.helmut@gmail.com>
+
+	Generalized infrastructure for find-definition
+
+	* progmodes/xref.el: New file.
+
+	* progmodes/etags.el (find-tag-marker-ring, pop-tag-mark): Move to
+	xref but keep aliases for backward compatibility.
+	(tags-reset-tags-tables): Use xref marker stack instead of
+	find-tag-marker-ring.
+	(etags--xref-backend, etags--xref-backend-var)
+	(etags-xref-backend-function): New xref backend.
+	(esc-map, ctl-x-4-map, ctl-x-5-map): Move key bindings for M-.,
+	M-, C-x 4 M-., and C-x 5 M-. to xref.el
+
+	* progmodes/elisp-mode.el (emacs-lisp-mode): Initialize
+	xref-backend-function.
+
 2014-12-05  Juri Linkov  <juri@linkov.net>
 
 	* comint.el (comint-history-isearch-search)
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index ba70f90..2e68516 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -231,6 +231,7 @@ Blank lines separate paragraphs.  Semicolons start comments.
   (setq imenu-case-fold-search nil)
   (setq-local eldoc-documentation-function
               #'elisp-eldoc-documentation-function)
+  (setq-local xref-backend-function 'xref-elisp-backend-function)
   (add-hook 'completion-at-point-functions
             #'elisp-completion-at-point nil 'local))
 
diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el
index b89b4cf..c1c6303 100644
--- a/lisp/progmodes/etags.el
+++ b/lisp/progmodes/etags.el
@@ -28,6 +28,7 @@
 
 (require 'ring)
 (require 'button)
+(require 'xref)
 
 ;;;###autoload
 (defvar tags-file-name nil
@@ -182,8 +183,8 @@ Example value:
 		       (sexp :tag "Tags to search")))
   :version "21.1")
 
-(defvar find-tag-marker-ring (make-ring find-tag-marker-ring-length)
-  "Ring of markers which are locations from which \\[find-tag] was invoked.")
+(defvaralias 'find-tag-marker-ring 'xref--marker-ring)
+(make-obsolete-variable 'find-tag-marker-ring nil "25.1")
 
 (defvar default-tags-table-function nil
   "If non-nil, a function to choose a default tags file for a buffer.
@@ -716,12 +717,10 @@ Returns t if it visits a tags table, or nil if there are no more in the list."
     (while (< i find-tag-marker-ring-length)
       (if (aref (cddr tags-location-ring) i)
 	  (set-marker (aref (cddr tags-location-ring) i) nil))
-      (if (aref (cddr find-tag-marker-ring) i)
-	  (set-marker (aref (cddr find-tag-marker-ring) i) nil))
       (setq i (1+ i))))
+  (xref-clear-marker-stack)
   (setq tags-file-name nil
 	tags-location-ring (make-ring find-tag-marker-ring-length)
-	find-tag-marker-ring (make-ring find-tag-marker-ring-length)
 	tags-table-list nil
 	tags-table-computed-list nil
 	tags-table-computed-list-for nil
@@ -898,7 +897,7 @@ See documentation of variable `tags-file-name'."
 	      ;; Run the user's hook.  Do we really want to do this for pop?
 	      (run-hooks 'local-find-tag-hook))))
       ;; Record whence we came.
-      (ring-insert find-tag-marker-ring (point-marker))
+      (xref-push-marker-stack)
       (if (and next-p last-tag)
 	  ;; Find the same table we last used.
 	  (visit-tags-table-buffer 'same)
@@ -954,7 +953,6 @@ See documentation of variable `tags-file-name'."
 	(switch-to-buffer buf)
       (error (pop-to-buffer buf)))
     (goto-char pos)))
-;;;###autoload (define-key esc-map "." 'find-tag)
 
 ;;;###autoload
 (defun find-tag-other-window (tagname &optional next-p regexp-p)
@@ -995,7 +993,6 @@ See documentation of variable `tags-file-name'."
 			;; the window's point from the buffer.
 			(set-window-point (selected-window) tagpoint))
 		      window-point)))
-;;;###autoload (define-key ctl-x-4-map "." 'find-tag-other-window)
 
 ;;;###autoload
 (defun find-tag-other-frame (tagname &optional next-p)
@@ -1020,7 +1017,6 @@ See documentation of variable `tags-file-name'."
   (interactive (find-tag-interactive "Find tag other frame: "))
   (let ((pop-up-frames t))
     (find-tag-other-window tagname next-p)))
-;;;###autoload (define-key ctl-x-5-map "." 'find-tag-other-frame)
 
 ;;;###autoload
 (defun find-tag-regexp (regexp &optional next-p other-window)
@@ -1049,20 +1045,8 @@ See documentation of variable `tags-file-name'."
 ;;;###autoload (define-key esc-map "*" 'pop-tag-mark)
 
 ;;;###autoload
-(defun pop-tag-mark ()
-  "Pop back to where \\[find-tag] was last invoked.
+(defalias 'pop-tag-mark 'xref-pop-marker-stack)
 
-This is distinct from invoking \\[find-tag] with a negative argument
-since that pops a stack of markers at which tags were found, not from
-where they were found."
-  (interactive)
-  (if (ring-empty-p find-tag-marker-ring)
-      (error "No previous locations for find-tag invocation"))
-  (let ((marker (ring-remove find-tag-marker-ring 0)))
-    (switch-to-buffer (or (marker-buffer marker)
-                          (error "The marked buffer has been deleted")))
-    (goto-char (marker-position marker))
-    (set-marker marker nil nil)))
 \f
 (defvar tag-lines-already-matched nil
   "Matches remembered between calls.") ; Doc string: calls to what?
@@ -1859,7 +1843,6 @@ nil, we exit; otherwise we scan the next file."
     (and messaged
 	 (null tags-loop-operate)
 	 (message "Scanning file %s...found" buffer-file-name))))
-;;;###autoload (define-key esc-map "," 'tags-loop-continue)
 
 ;;;###autoload
 (defun tags-search (regexp &optional file-list-form)
@@ -2077,6 +2060,57 @@ for \\[find-tag] (which see)."
       (completion-in-region (car comp-data) (cadr comp-data)
 			    (nth 2 comp-data)
 			    (plist-get (nthcdr 3 comp-data) :predicate)))))
+
+\f
+;;; Xref backed
+
+(defclass etags--xref-backend (xref-backend-class) ())
+
+(defvar etags--xref-backend-var (make-instance 'etags--xref-backend))
+
+;;;###autoload
+(defun etags-xref-backend-function () etags--xref-backend-var)
+
+(defmethod xref-lookup-definitions ((_ etags--xref-backend) id)
+  ;; This emulates the behaviour of `find-tag-in-order' but instead of
+  ;; returning one match at a time all matches are returned as list.
+  ;; NOTE: find-tag-tag-order is typically a buffer-local variable.
+  (let* ((xrefs '())
+	 (first-time t)
+	 (regexp? (consp id))
+	 (pattern (if regexp? (cadr id) id))
+	 (search-fun (if regexp? #'re-search-forward #'search-forward))
+	 (marks (make-hash-table :test 'equal)))
+    (save-excursion
+      (while (visit-tags-table-buffer (not first-time))
+	(setq first-time nil)
+	(dolist (order-fun (cond (regexp? find-tag-regexp-tag-order)
+				 (t find-tag-tag-order)))
+	  (goto-char (point-min))
+	  (while (funcall search-fun pattern nil t)
+	    (when (funcall order-fun pattern)
+	      (beginning-of-line)
+	      (cl-destructuring-bind (hint line &rest pos) (etags-snarf-tag)
+		(unless (eq hint t) ; hint==t if we are in a filename line
+		  (let* ((file (file-of-tag))
+			 (mark-key (cons file line)))
+		    (unless (gethash mark-key marks)
+		      (let ((loc (xref-make-file-location
+				  (expand-file-name file) line 0)))
+			(push (xref-make hint loc) xrefs)
+			(puthash mark-key t marks)))))))))))
+    (nreverse xrefs)))
+
+;; If the text in the minibuffer starts with " it's interpreted as a
+;; regexp.  This is an example for a non-trivial identifier type.
+(defmethod xref-read-identifier-from-minibuffer ((_ etags--xref-backend)
+						 prompt init)
+  (let ((string (read-from-minibuffer prompt init)))
+    (cond ((string-match "^\"" string)
+	   `(rx ,(read string)))
+	  (t
+	   string))))
+
 \f
 (provide 'etags)
 
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
new file mode 100644
index 0000000..911b3bc
--- /dev/null
+++ b/lisp/progmodes/xref.el
@@ -0,0 +1,491 @@
+;; xref.el --- Cross referencing commands              -*-lexical-binding:t-*-
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+
+;; Author: Helmut Eller <eller.helmut@gmail.com>
+
+;; This work 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 of the License, or
+;; (at your option) any later version.
+
+;; This work 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a somewhat generic infrastructure for cross
+;; referencing commands, in particular "find-definition".  Some part of
+;; the functionality must be implemented in a language dependent way
+;; and that's done by defining a "backend".  The generic code finds
+;; the backend by calling the function stored in the variable
+;; `xref-backend-function'.  A language specific mode usually makes
+;; `xref-backend-function' buffer local before storing into it.
+;;
+;; A backend is an instance of the EIEIO class xref-backend-class.
+;; Various generic functions (in the EIEIO sense of the word) are
+;; defined on xref-backend-class.  A language specific mode usually
+;; creates a subclasses of xref-backend-class and provides specialized
+;; methods for the generic functions.  See the `xref--elisp-backend'
+;; and `etags--xref-backend' classes for examples.
+
+(require 'cl-lib)
+(require 'eieio)
+(require 'ring)
+(require 'find-func) ; for elisp backend
+
+\f
+;;; Locations
+
+;; A location represents a position in a file or buffer.
+(defclass xref-location () ())
+
+;; If a backend decides to subclass xref-location it can provide
+;; methods for some of the following functions:
+(defgeneric xref-location-buffer (location))
+(defgeneric xref-location-position (location))
+(defgeneric xref-location= (location1 location2))
+(defmethod xref-location= ((l1 xref-location) l2)
+  (equal l1 l2))
+
+;;;; Commonly needed location classes are defined here:
+
+;; A file location is a file/line/colum triple.  Line numbers start
+;; from 1 and columns from 0 (as inconstistent as the rest of Emacs).
+;;
+;; FIXME: might be useful to have an optional "hint" i.e. a string to
+;; search for in case the line number is sightly out of date.
+(defclass xref-file-location (xref-location)
+  ((file :type string :initarg :file)
+   (line :type fixnum :initarg :line)
+   (column :type fixnum :initarg :column)))
+
+(defun xref-make-file-location (file line column)
+  (make-instance 'xref-file-location :file file :line line :column column))
+
+(defmethod xref-location-buffer ((l xref-file-location))
+  (with-slots (file) l
+    (or (get-file-buffer file)
+	(let ((find-file-suppress-same-file-warnings t))
+	  (find-file-noselect file)))))
+
+(defmethod xref-location-position ((l xref-file-location))
+  (with-slots (line column) l
+    (with-current-buffer (xref-location-buffer l)
+      (save-restriction
+	(widen)
+	(save-excursion
+	  (goto-char (point-min))
+	  (beginning-of-line line)
+	  (move-to-column column)
+	  (point))))))
+
+(defclass xref-buffer-location (xref-location)
+  ((buffer :type buffer :initarg :buffer :reader xref-location-buffer)
+   (position :type fixnum :initarg :position :reader xref-location-position)))
+
+(defun xref-make-buffer-location (buffer position)
+  (make-instance 'xref-buffer-location :buffer buffer :position position))
+
+;; "Bogus" locations are sometimes useful to indicate errors,
+;; e.g. when we know that a function exists but the actual location is
+;; not known.
+(defclass xref-bogus-location (xref-location)
+  ((message :type string :initarg :message
+	    :reader xref-bogus-location-message)))
+
+(defun xref-make-bogus-location (message)
+  (make-instance 'xref-bogus-location :message message))
+
+(defmethod xref-location-buffer ((l xref-bogus-location))
+  (with-slots (message) l
+    (error "%s" message)))
+
+(defmethod xref-location-position ((l xref-bogus-location))
+  (with-slots (message) l
+    (error "%s" message)))
+
+\f
+;;; cross reference
+
+;; An xref is used to display and locate constructs like variables or
+;; functions.
+(defclass xref--xref ()
+  ((description :type string :initarg :description
+		:reader xref--xref-description)
+   (location :type xref-location :initarg :location
+	     :reader xref--xref-location)))
+
+(defun xref-make (description location)
+  (make-instance 'xref--xref :description description :location location))
+
+\f
+;;; Backend
+
+;; Setting the variable `xref-backend-function' to a function that
+;; returns a subclass of xref-backend-class can be used to provide
+;; language specific behaviour, primarily `xref-lookup-definitions'.
+
+;; Ugly name because defclass stores the class object in the symbol.
+(defclass xref-backend-class () ())
+
+;; For now, make the etags backend the default.
+(defvar xref-backend-function 'etags-xref-backend-function)
+
+(defun xref--backend ()
+  (funcall xref-backend-function))
+
+;;;; Backend interface functions
+
+(defgeneric xref-lookup-definitions (backend identifier))
+(defgeneric xref-lookup-references (backend identifier))
+
+;; An identifier is backend specific.  By default it's a string but it
+;; can be any type.  Well, nil means "no identifier at point" so that
+;; can't be used.
+(defgeneric xref-identifier-at-point (backend))
+(defgeneric xref-read-identifier-from-minibuffer (backend prompt default))
+(defgeneric xref-identifier-to-string (backend identifier))
+
+;; default implementation for identifiers
+(defmethod xref-identifier-at-point (_backend)
+  (let ((thing (thing-at-point 'symbol)))
+    (and thing (substring-no-properties thing))))
+
+(defmethod xref-read-identifier-from-minibuffer (_backend prompt init)
+  (read-from-minibuffer prompt init))
+
+(defmethod xref-identifier-to-string (_backend identifier)
+  (with-output-to-string (princ identifier)))
+
+\f
+;;; misc utilities
+(defun xref--alistify (list key test)
+  "Partition the elements of LIST into an alist.
+KEY extracts the key from an element and TEST is used to compare
+keys."
+  (let ((alist '()))
+    (dolist (e list)
+      (let* ((k (funcall key e))
+	     (probe (cl-assoc k alist :test test)))
+	(if probe
+	    (setcdr probe (cons e (cdr probe)))
+          (push (cons k (list e)) alist))))
+    ;; Put them back in order.
+    (cl-loop for (key . value) in (reverse alist)
+             collect (cons key (reverse value)))))
+
+(defun xref--insert-propertized (props &rest strings)
+  "Insert STRINGS with text properties PROPS."
+  (let ((start (point)))
+    (apply #'insert strings)
+    (add-text-properties start (point) props)))
+
+(defun xref--search-property (property &optional backward)
+    "Search the next text range where text property PROPERTY is non-nil.
+Return the value of PROPERTY.  If BACKWARD is non-nil, search
+backward."
+  (let ((next (if backward
+		  #'previous-single-char-property-change
+		#'next-single-char-property-change))
+        (start (point))
+        (value nil))
+    (while (progn
+             (goto-char (funcall next (point) property))
+             (not (or (setq value (get-text-property (point) property))
+                      (eobp)
+                      (bobp)))))
+    (cond (value)
+	  (t (goto-char start) nil))))
+
+\f
+;;; Marker stack  (M-. pushes, M-, pops)
+
+(defconst xref--marker-ring-length 16)
+
+(defvar xref--marker-ring (make-ring xref--marker-ring-length)
+  "Ring of markers to implement the marker stack.")
+
+(defun xref-push-marker-stack ()
+  "Add point to the marker stack."
+  (ring-insert xref--marker-ring (point-marker)))
+
+;;;###autoload
+(defun xref-pop-marker-stack ()
+  "Pop back to where \\[xref-find-definitions] was last invoked."
+  (interactive)
+  (let ((ring xref--marker-ring))
+    (when (ring-empty-p ring)
+      (error "Marker stack is empty"))
+    (let ((marker (ring-remove ring 0)))
+      (switch-to-buffer (or (marker-buffer marker)
+			    (error "The marked buffer has been deleted")))
+      (goto-char (marker-position marker))
+      (set-marker marker nil nil))))
+
+;; etags.el needs this
+(defun xref-clear-marker-stack ()
+  "Discard all markers from the marker stack."
+  (let ((ring xref--marker-ring))
+    (while (not (ring-empty-p ring))
+      (let ((marker (ring-remove ring)))
+	(set-marker marker nil nil)))))
+
+\f
+(defun xref--goto-location (location)
+  "Set buffer and point according to xref-location LOCATION."
+  (set-buffer (xref-location-buffer location))
+  (let ((pos (xref-location-position location)))
+    (cond ((and (<= (point-min) pos) (<= pos (point-max))))
+	  (widen-automatically (widen))
+	  (t (error "Location is outside accessible part of buffer")))
+    (goto-char pos)))
+
+(defun xref--pop-to-location (location &optional window)
+  "Goto xref-location LOCATION and display the buffer.
+WINDOW controls how the buffer is displayed:
+  nil      -- switch-to-buffer
+  'window  -- pop-to-buffer (other window)
+  'frame   -- pop-to-buffer (other frame)"
+  (xref--goto-location location)
+  (cl-ecase window
+    ((nil)  (switch-to-buffer (current-buffer)))
+    (window (pop-to-buffer (current-buffer) t))
+    (frame  (let ((pop-up-frames t)) (pop-to-buffer (current-buffer) t)))))
+
+\f
+;;; XREF buffer (part of the UI)
+
+;; The xref buffer is used to display a set of xrefs.
+
+(defun xref--display-position (pos other-window recenter-arg)
+  ;; show the location, but don't hijack focus.
+  (with-selected-window (display-buffer (current-buffer) other-window)
+    (goto-char pos)
+    (recenter recenter-arg)))
+
+(defgeneric xref--show-location (location))
+(defmethod xref--show-location ((l xref-bogus-location))
+  (with-slots (message) l
+    (message "%s" message)))
+
+(defmethod xref--show-location (location)
+  (xref--goto-location location)
+  (xref--display-position (point) t 1))
+
+(defun xref--next-line (backward)
+  (let ((loc (xref--search-property 'xref-location backward)))
+    (when loc
+      (xref--show-location loc))))
+
+(defun xref-next-line ()
+  "Move to the next xref and display its source in the other window."
+  (interactive)
+  (xref--next-line nil))
+
+(defun xref-prev-line ()
+  "Move to the previous xref and display its source in the other window."
+  (interactive)
+  (xref--next-line t))
+
+(defun xref--location-at-point ()
+  (or (get-text-property (point) 'xref-location)
+      (error "No reference at point.")))
+
+(defun xref-goto-xref ()
+  "Jump to the xref at point and close the xref buffer."
+  (interactive)
+  (xref--show-location (xref--location-at-point))
+  (quit-window))
+
+(define-derived-mode xref--xref-buffer-mode fundamental-mode "XREF"
+  "Mode for displaying cross refenences."
+  (setq buffer-read-only t))
+
+(let ((map xref--xref-buffer-mode-map))
+  (define-key map (kbd "q") 'quit-window)
+  (define-key map [remap next-line] 'xref-next-line)
+  (define-key map [remap previous-line] 'xref-prev-line)
+  (define-key map (kbd "RET") 'xref-goto-xref)
+
+  ;; suggested by Johan Claesson "to further reduce finger movement":
+  (define-key map (kbd ".") 'xref-next-line)
+  (define-key map (kbd ",") 'xref-prev-line))
+
+(defun xref--buffer-name () "*xref*")
+
+(defun xref--insert-xrefs (xref-alist)
+  "Insert XREF-ALIST in the current-buffer.
+XREF-ALIST is of the form ((GROUP . (XREF ...)) ...).  Where
+GROUP is a string for decoration purposes and XREF is an
+`xref--xref' object."
+  (cl-loop for ((group . xrefs) . more1) on xref-alist do
+           (xref--insert-propertized '(face bold) group "\n")
+           (cl-loop for (xref . more2) on xrefs do
+		    (insert "  ")
+		    (with-slots (description location) xref
+		      (xref--insert-propertized
+		       (list 'xref-location location
+			     'face 'font-lock-keyword-face)
+		       description))
+		    (when (or more1 more2)
+		      (insert "\n")))))
+
+;; Return a string used to group a set of locations (this is typically
+;; the filename).
+(defgeneric xref-location-group (location))
+(defmethod xref-location-group ((_ xref-bogus-location)) "(No location)")
+(defmethod xref-location-group ((l xref-file-location))
+  (with-slots (file) l
+    file))
+(defmethod xref-location-group ((l xref-buffer-location))
+  (with-slots (buffer) l
+    (or (buffer-file-name buffer)
+	(format "(buffer %s)" (buffer-name buffer)))))
+
+(defun xref--analyze (xrefs)
+  "Find common filenames in XREFS.
+Return an alist of the form ((FILENAME . (XREF ...)) ...)."
+  (xref--alistify xrefs
+		  (lambda (x)
+		    (xref-location-group (xref--xref-location x)))
+		  #'equal))
+
+(defun xref--show-xref-buffer (xrefs)
+  (let ((xref-alist (xref--analyze xrefs)))
+    (with-current-buffer (get-buffer-create (xref--buffer-name))
+      (let ((inhibit-read-only t))
+	(erase-buffer)
+	(xref--insert-xrefs xref-alist)
+	(xref--xref-buffer-mode)
+	(pop-to-buffer (current-buffer))
+	(goto-char (point-min))
+	(current-buffer)))))
+
+\f
+;; This part of the UI seems fairly uncontroversial: it reads the
+;; identifier and deals with the single definition case.
+;;
+;; The controversial multiple definitions case is handed of to
+;; xref-show-xrefs-function.
+
+(defun xref--unique-location (xrefs)
+  "If it exists, return the single location in the list XREFS.
+If there are multiple or no locations in XREFS return nil."
+  (and xrefs
+       (let ((loc (xref--xref-location (car xrefs))))
+	 (and (cl-every (lambda (x)
+			  (xref-location= (xref--xref-location x) loc))
+			(cdr xrefs))
+	      loc))))
+
+(defvar xref-show-xrefs-function 'xref--show-xref-buffer
+  "Function to display a list of xrefs.")
+
+(defun xref--show-xrefs (id kind xrefs window)
+  (let ((1loc (xref--unique-location xrefs)))
+    (cond ((null xrefs)
+	   (error "No known %s for: %s"
+		  kind (xref-identifier-to-string (xref--backend) id)))
+	  (1loc
+	   (xref-push-marker-stack)
+	   (xref--pop-to-location 1loc window))
+	  (t
+	   (xref-push-marker-stack)
+	   (funcall xref-show-xrefs-function xrefs)))))
+
+(defun xref--read-identifier (prompt)
+  "Return the identifier at point or read it from the minibuffer."
+  (let* ((backend (xref--backend))
+	 (id (xref-identifier-at-point backend)))
+    (cond ((or current-prefix-arg (not id))
+	   (let ((init (if id (xref-identifier-to-string backend id))))
+	     (xref-read-identifier-from-minibuffer backend prompt init)))
+	  (t id))))
+
+\f
+;;; Commands
+
+(defun xref--find-definitions (id window)
+  (xref--show-xrefs id "definitions"
+		    (xref-lookup-definitions (xref--backend) id)
+		    window))
+
+;;;###autoload
+(defun xref-find-definitions (identifier)
+  "Find the definition of the identifier at point.
+With prefix argument, prompt for the identifier."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier nil))
+
+;;;###autoload
+(defun xref-find-definitions-other-window (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'window))
+
+;;;###autoload
+(defun xref-find-definitions-other-frame (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'frame))
+
+;;;###autoload
+(defun xref-find-references (identifier)
+  (interactive (list (xref--read-identifier "Find references of: ")))
+  (xref--show-xrefs identifier "references"
+		    (xref-lookup-references (xref--backend) identifier)
+		    nil))
+
+\f
+;;; Key bindings
+
+;;;###autoload (define-key esc-map "." 'xref-find-definitions)
+;;;###autoload (define-key esc-map "," 'xref-pop-marker-stack)
+;;;###autoload (define-key ctl-x-4-map "." 'xref-find-definitions-other-window)
+;;;###autoload (define-key ctl-x-5-map "." 'xref-find-definitions-other-frame)
+
+\f
+;;; ELisp backend
+
+;; This is defined here and not in elisp-mode.el so that we don't need
+;; to load xref.el just to create the *scratch* buffer.
+
+;; For now, this is just a wrapper around find-func.el in particular
+;; `find-definition-noselect'.
+
+(defclass xref--elisp-backend (xref-backend-class) ())
+
+(defvar xref--elisp-backend-var (make-instance 'xref--elisp-backend))
+
+;;;###autoload
+(defun xref-elisp-backend-function () xref--elisp-backend-var)
+
+(defun xref--elisp-find-definition (symbol type)
+  (let ((loc (condition-case err
+		 (let ((loc (save-excursion
+			      (find-definition-noselect symbol type))))
+		   (xref-make-buffer-location (car loc) (or (cdr loc) 1)))
+	       (error
+		(xref-make-bogus-location (error-message-string err)))))
+	(desc (format "(%s %s)" (or type 'defun) symbol)))
+    (xref-make desc loc)))
+
+;; FIXME: include other stuff likes faces, compiler-macros, methods...
+(defmethod xref-lookup-definitions ((_ xref--elisp-backend) id)
+  (let ((sym (intern-soft id)))
+    (if (null sym)
+	'()
+      (let ((fun (if (fboundp sym) (xref--elisp-find-definition sym nil)))
+	    (var (if (boundp sym) (xref--elisp-find-definition sym 'defvar))))
+	(remove nil (list fun var))))))
+
+\f
+(provide 'xref)
+
+;;; xref.el ends here
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-06 18:38                           ` Drew Adams
@ 2014-12-07 16:52                             ` Stephen Leake
  0 siblings, 0 replies; 172+ messages in thread
From: Stephen Leake @ 2014-12-07 16:52 UTC (permalink / raw)
  To: emacs-devel

Drew Adams <drew.adams@oracle.com> writes:

>> A problem seems to be that xref-identifier-at-point would need a
>> possibly complicated heuristic to determine if we are at such a
>> filename or a "normal" identifier. Maybe it's easier to have a
>> separate xref-file-name-at-point which would by default do what
>> ffap-guess-file-name-at-point does.  For ELisp it should
>> additionally recognize (require 'foo) and somehow reuse find-library.
>
> And..., and somehow..., and..., and somehow...
>
> In general, this is the wrong approach, I think.
>
> Q. When does reuse of a file name or a URL or... at point make
>    sense?
> A. As input to a command that acts on a file name or a URL or...
>
> The *particular command* already knows what kind of thing it
> expects.  

Not in my use case.

I'd like to have one "goto ref" key that Does the Right Thing as much as
possible. That way I don't have to remember different keys for different
cases. 

> Doesn't a user know what kind of thing definition s?he wants
> to look up?  

Yes, but I don't want to _also_ have to know which key to invoke.

> Does s?he really need Emacs to guess what kind of thing is at point 

Not "guess", no. Determine precisely, yes.

> (a guess that is fraught with ambiguity)?

If Emacs can't figure it out, put up a list of possibilities, or report
error.

This works very well for me. I have three related keys for this kind of
thing (Emacs isn't quite as intelligent as I'd like yet);

C-c C-d for symbols, whose behavior is mostly determined by the
compiler generated cross reference info; now bound to
'xref-find-definitions'. 

C-F11 for files, whose behavior is determined by parsing the string at
point, influenced by the major mode. Mostly a wrapper around
ff-find-the-other-file.

C-F12 whose behavior is totally determined by parsing the string at
point or active region, via an alisp of regexps. Sometimes the
"at-point" version does the wrong thing; then the "active region"
version does what I actually want (and vice versa).

I started the current discussion (I changed the Subject line) to cover
implementing the C-F12 functionality in a more general way, or just make
it available in Emacs core or an ELPA package.

It might also make sense to make the C-F11 functionality available in
Emacs core.

> The problem comes down, I think, to providing a key (or keys)
> that will *grab what the user wants at point*.

Thing-at-point does that pretty well, and xref-identifier-at-point
provides a way to extend that.

I'm talking about what to do with the string after we get it, and to
some extent UI design to give more information to Emacs about what to
get and what to do with it (at point vs region, for example).

> We should then bind that key in `minibuffer-local-map' so that
> it is available *always*, regardless of the minibuffer-reading
> command and the particular way of reading (with completion or
> not, and for any kind of completion).

This might make sense, but that requires being in a prompt in the first
place; I try to avoid that whenever possible.

> 1. It makes sense to concentrate on coming up with a
>    useful, general-purpose grab-text-at-point minibuffer
>    command, not with a general-purpose `find-definition'
>    top-level command.

We disagree; that's fine.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-07  9:55                             ` Helmut Eller
@ 2014-12-08 14:33                               ` Stefan Monnier
  2014-12-08 19:58                                 ` Helmut Eller
  2014-12-08 22:36                                 ` Stephen Leake
  0 siblings, 2 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-12-08 14:33 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

>> This said, I think the most urgent step is to get such a generalized
>> find-definition into Emacs.
> Here is a patch:

What happened to the other contenders?

> --- a/lisp/progmodes/elisp-mode.el
> +++ b/lisp/progmodes/elisp-mode.el
> @@ -231,6 +231,7 @@ Blank lines separate paragraphs.  Semicolons start comments.
>    (setq imenu-case-fold-search nil)
>    (setq-local eldoc-documentation-function
>                #'elisp-eldoc-documentation-function)
> +  (setq-local xref-backend-function 'xref-elisp-backend-function)
>    (add-hook 'completion-at-point-functions
>              #'elisp-completion-at-point nil 'local))

Hmm, so xref-elisp-backend-function is not in elisp-mode.el?
That's too bad.

> +(defvaralias 'find-tag-marker-ring 'xref--marker-ring)
> +(make-obsolete-variable 'find-tag-marker-ring nil "25.1")

You can use define-obsolete-variable-alias.

> +;; and `etags--xref-backend' classes for examples.
> +
> +(require 'cl-lib)

You need a ";;; Code:" up there.  Try C-u M-x checkdoc-current-buffer
(you don't have to heed all its recommendations, tho).

> +;; For now, make the etags backend the default.
> +(defvar xref-backend-function 'etags-xref-backend-function)

IIUC this is the main interface between xref and its backends, so it
very much needs a good docstring.

Also, I don't understand why it should be a function that returns
a "backend object" rather than being the backend object itself.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-08 14:33                               ` Stefan Monnier
@ 2014-12-08 19:58                                 ` Helmut Eller
  2014-12-08 21:38                                   ` Stefan Monnier
  2014-12-08 22:36                                 ` Stephen Leake
  1 sibling, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-08 19:58 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1543 bytes --]

On Mon, Dec 08 2014, Stefan Monnier wrote:

> What happened to the other contenders?

Nothing in particular.  The competition is still open.

>> +  (setq-local xref-backend-function 'xref-elisp-backend-function)
>>    (add-hook 'completion-at-point-functions
>>              #'elisp-completion-at-point nil 'local))
>
> Hmm, so xref-elisp-backend-function is not in elisp-mode.el?
> That's too bad.

The elisp backend code needs to define a subclass of xref-backend-class
and that can't be done without loading xref.el and eieio, I think.
elisp-mode is needed to create the *scratch* buffer and it seemed to me
that loading xref.el so early increases startup time for no good reason.

find-func.el might be a reasonable place for the elisp-xref backend
code.

>> +(defvaralias 'find-tag-marker-ring 'xref--marker-ring)
>> +(make-obsolete-variable 'find-tag-marker-ring nil "25.1")
>
> You can use define-obsolete-variable-alias.

OK.

>> +;; For now, make the etags backend the default.
>> +(defvar xref-backend-function 'etags-xref-backend-function)
>
> IIUC this is the main interface between xref and its backends, so it
> very much needs a good docstring.

I added a docstring here and in a few other places.

> Also, I don't understand why it should be a function that returns
> a "backend object" rather than being the backend object itself.

The indirection makes it easier to autoload xref.el.  If emacs-lisp-mode
wants to create a backend object it has to load xref.el first, which as
I said above seems undesirable.

Updated patch:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Generalized-infrastructure-for-find-definition.patch --]
[-- Type: text/x-diff, Size: 27172 bytes --]

From 031e27d9836aa6e0dc6223f3beaf3d0120e4007a Mon Sep 17 00:00:00 2001
From: Helmut Eller <eller.helmut@gmail.com>
Date: Mon, 8 Dec 2014 20:26:50 +0100
Subject: [PATCH] Generalized infrastructure for find-definition

* progmodes/xref.el: New file.

* progmodes/etags.el (find-tag-marker-ring, pop-tag-mark): Move to
xref but keep aliases for backward compatibility.
(tags-reset-tags-tables): Use xref marker stack instead of
find-tag-marker-ring.
(etags--xref-backend, etags--xref-backend-var)
(etags-xref-backend-function): New xref backend.
(esc-map, ctl-x-4-map, ctl-x-5-map): Move key bindings for M-.,
M-, C-x 4 M-., and C-x 5 M-. to xref.el

* progmodes/elisp-mode.el (emacs-lisp-mode): Initialize
xref-backend-function.
---
 lisp/ChangeLog               |   18 ++
 lisp/progmodes/elisp-mode.el |    1 +
 lisp/progmodes/etags.el      |   80 +++++--
 lisp/progmodes/xref.el       |  522 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 598 insertions(+), 23 deletions(-)
 create mode 100644 lisp/progmodes/xref.el

diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index b3cb2fa..46baac5 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,21 @@
+2014-12-07  Helmut Eller  <eller.helmut@gmail.com>
+
+	Generalized infrastructure for find-definition
+
+	* progmodes/xref.el: New file.
+
+	* progmodes/etags.el (find-tag-marker-ring, pop-tag-mark): Move to
+	xref but keep aliases for backward compatibility.
+	(tags-reset-tags-tables): Use xref marker stack instead of
+	find-tag-marker-ring.
+	(etags--xref-backend, etags--xref-backend-var)
+	(etags-xref-backend-function): New xref backend.
+	(esc-map, ctl-x-4-map, ctl-x-5-map): Move key bindings for M-.,
+	M-, C-x 4 M-., and C-x 5 M-. to xref.el
+
+	* progmodes/elisp-mode.el (emacs-lisp-mode): Initialize
+	xref-backend-function.
+
 2014-12-05  Juri Linkov  <juri@linkov.net>
 
 	* comint.el (comint-history-isearch-search)
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index ba70f90..b9caf69 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -231,6 +231,7 @@ Blank lines separate paragraphs.  Semicolons start comments.
   (setq imenu-case-fold-search nil)
   (setq-local eldoc-documentation-function
               #'elisp-eldoc-documentation-function)
+  (setq-local xref-backend-function #'xref-elisp-backend-function)
   (add-hook 'completion-at-point-functions
             #'elisp-completion-at-point nil 'local))
 
diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el
index b89b4cf..cd2e00b 100644
--- a/lisp/progmodes/etags.el
+++ b/lisp/progmodes/etags.el
@@ -28,6 +28,7 @@
 
 (require 'ring)
 (require 'button)
+(require 'xref)
 
 ;;;###autoload
 (defvar tags-file-name nil
@@ -182,8 +183,8 @@ Example value:
 		       (sexp :tag "Tags to search")))
   :version "21.1")
 
-(defvar find-tag-marker-ring (make-ring find-tag-marker-ring-length)
-  "Ring of markers which are locations from which \\[find-tag] was invoked.")
+(define-obsolete-variable-alias 'find-tag-marker-ring 'xref--marker-ring
+  "25.1")
 
 (defvar default-tags-table-function nil
   "If non-nil, a function to choose a default tags file for a buffer.
@@ -716,12 +717,10 @@ Returns t if it visits a tags table, or nil if there are no more in the list."
     (while (< i find-tag-marker-ring-length)
       (if (aref (cddr tags-location-ring) i)
 	  (set-marker (aref (cddr tags-location-ring) i) nil))
-      (if (aref (cddr find-tag-marker-ring) i)
-	  (set-marker (aref (cddr find-tag-marker-ring) i) nil))
       (setq i (1+ i))))
+  (xref-clear-marker-stack)
   (setq tags-file-name nil
 	tags-location-ring (make-ring find-tag-marker-ring-length)
-	find-tag-marker-ring (make-ring find-tag-marker-ring-length)
 	tags-table-list nil
 	tags-table-computed-list nil
 	tags-table-computed-list-for nil
@@ -898,7 +897,7 @@ See documentation of variable `tags-file-name'."
 	      ;; Run the user's hook.  Do we really want to do this for pop?
 	      (run-hooks 'local-find-tag-hook))))
       ;; Record whence we came.
-      (ring-insert find-tag-marker-ring (point-marker))
+      (xref-push-marker-stack)
       (if (and next-p last-tag)
 	  ;; Find the same table we last used.
 	  (visit-tags-table-buffer 'same)
@@ -954,7 +953,6 @@ See documentation of variable `tags-file-name'."
 	(switch-to-buffer buf)
       (error (pop-to-buffer buf)))
     (goto-char pos)))
-;;;###autoload (define-key esc-map "." 'find-tag)
 
 ;;;###autoload
 (defun find-tag-other-window (tagname &optional next-p regexp-p)
@@ -995,7 +993,6 @@ See documentation of variable `tags-file-name'."
 			;; the window's point from the buffer.
 			(set-window-point (selected-window) tagpoint))
 		      window-point)))
-;;;###autoload (define-key ctl-x-4-map "." 'find-tag-other-window)
 
 ;;;###autoload
 (defun find-tag-other-frame (tagname &optional next-p)
@@ -1020,7 +1017,6 @@ See documentation of variable `tags-file-name'."
   (interactive (find-tag-interactive "Find tag other frame: "))
   (let ((pop-up-frames t))
     (find-tag-other-window tagname next-p)))
-;;;###autoload (define-key ctl-x-5-map "." 'find-tag-other-frame)
 
 ;;;###autoload
 (defun find-tag-regexp (regexp &optional next-p other-window)
@@ -1049,20 +1045,8 @@ See documentation of variable `tags-file-name'."
 ;;;###autoload (define-key esc-map "*" 'pop-tag-mark)
 
 ;;;###autoload
-(defun pop-tag-mark ()
-  "Pop back to where \\[find-tag] was last invoked.
+(defalias 'pop-tag-mark 'xref-pop-marker-stack)
 
-This is distinct from invoking \\[find-tag] with a negative argument
-since that pops a stack of markers at which tags were found, not from
-where they were found."
-  (interactive)
-  (if (ring-empty-p find-tag-marker-ring)
-      (error "No previous locations for find-tag invocation"))
-  (let ((marker (ring-remove find-tag-marker-ring 0)))
-    (switch-to-buffer (or (marker-buffer marker)
-                          (error "The marked buffer has been deleted")))
-    (goto-char (marker-position marker))
-    (set-marker marker nil nil)))
 \f
 (defvar tag-lines-already-matched nil
   "Matches remembered between calls.") ; Doc string: calls to what?
@@ -1859,7 +1843,6 @@ nil, we exit; otherwise we scan the next file."
     (and messaged
 	 (null tags-loop-operate)
 	 (message "Scanning file %s...found" buffer-file-name))))
-;;;###autoload (define-key esc-map "," 'tags-loop-continue)
 
 ;;;###autoload
 (defun tags-search (regexp &optional file-list-form)
@@ -2077,6 +2060,57 @@ for \\[find-tag] (which see)."
       (completion-in-region (car comp-data) (cadr comp-data)
 			    (nth 2 comp-data)
 			    (plist-get (nthcdr 3 comp-data) :predicate)))))
+
+\f
+;;; Xref backed
+
+(defclass etags--xref-backend (xref-backend-class) ())
+
+(defvar etags--xref-backend-var (make-instance 'etags--xref-backend))
+
+;;;###autoload
+(defun etags-xref-backend-function () etags--xref-backend-var)
+
+(defmethod xref-lookup-definitions ((_ etags--xref-backend) id)
+  ;; This emulates the behaviour of `find-tag-in-order' but instead of
+  ;; returning one match at a time all matches are returned as list.
+  ;; NOTE: find-tag-tag-order is typically a buffer-local variable.
+  (let* ((xrefs '())
+	 (first-time t)
+	 (regexp? (consp id))
+	 (pattern (if regexp? (cadr id) id))
+	 (search-fun (if regexp? #'re-search-forward #'search-forward))
+	 (marks (make-hash-table :test 'equal)))
+    (save-excursion
+      (while (visit-tags-table-buffer (not first-time))
+	(setq first-time nil)
+	(dolist (order-fun (cond (regexp? find-tag-regexp-tag-order)
+				 (t find-tag-tag-order)))
+	  (goto-char (point-min))
+	  (while (funcall search-fun pattern nil t)
+	    (when (funcall order-fun pattern)
+	      (beginning-of-line)
+	      (cl-destructuring-bind (hint line &rest pos) (etags-snarf-tag)
+		(unless (eq hint t) ; hint==t if we are in a filename line
+		  (let* ((file (file-of-tag))
+			 (mark-key (cons file line)))
+		    (unless (gethash mark-key marks)
+		      (let ((loc (xref-make-file-location
+				  (expand-file-name file) line 0)))
+			(push (xref-make hint loc) xrefs)
+			(puthash mark-key t marks)))))))))))
+    (nreverse xrefs)))
+
+;; If the text in the minibuffer starts with " it's interpreted as a
+;; regexp.  This is an example for a non-trivial identifier type.
+(defmethod xref-read-identifier-from-minibuffer ((_ etags--xref-backend)
+						 prompt init)
+  (let ((string (read-from-minibuffer prompt init)))
+    (cond ((string-match "^\"" string)
+	   `(rx ,(read string)))
+	  (t
+	   string))))
+
 \f
 (provide 'etags)
 
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
new file mode 100644
index 0000000..6afc82d
--- /dev/null
+++ b/lisp/progmodes/xref.el
@@ -0,0 +1,522 @@
+;; xref.el --- Cross referencing commands              -*-lexical-binding:t-*-
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a somewhat generic infrastructure for cross
+;; referencing commands, in particular "find-definition".  Some part of
+;; the functionality must be implemented in a language dependent way
+;; and that's done by defining a "backend".  The generic code finds
+;; the backend by calling the function stored in the variable
+;; `xref-backend-function'.  A language specific mode usually makes
+;; `xref-backend-function' buffer local before storing into it.
+;;
+;; A backend is an instance of the EIEIO class `xref-backend-class'.
+;; Various generic functions (in the EIEIO sense of the word) are
+;; defined on xref-backend-class.  A language specific mode usually
+;; creates a subclasses of xref-backend-class and provides specialized
+;; methods for the generic functions.  See the `xref--elisp-backend'
+;; and `etags--xref-backend' classes for examples.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'eieio)
+(require 'ring)
+(require 'find-func) ; for elisp backend
+
+\f
+;;; Locations
+
+(defclass xref-location () ()
+  :documentation "A location represents a position in a file or buffer.")
+
+;; If a backend decides to subclass xref-location it can provide
+;; methods for some of the following functions:
+(defgeneric xref-location-buffer (location)
+  "Return the buffer for LOCATION.")
+
+(defgeneric xref-location-position (location)
+  "Return the position in LOCATIONs buffer.")
+
+(defgeneric xref-location= (location1 location2)
+  "Return t if two locations are equal.")
+
+(defmethod xref-location= ((l1 xref-location) l2)
+  (equal l1 l2))
+
+;;;; Commonly needed location classes are defined here:
+
+;; FIXME: might be useful to have an optional "hint" i.e. a string to
+;; search for in case the line number is sightly out of date.
+(defclass xref-file-location (xref-location)
+  ((file :type string :initarg :file)
+   (line :type fixnum :initarg :line)
+   (column :type fixnum :initarg :column))
+  :documentation "A file location is a file/line/column triple.
+Line numbers start from 1 and columns from 0.")
+
+(defun xref-make-file-location (file line column)
+  "Create and return a new xref-file-location."
+  (make-instance 'xref-file-location :file file :line line :column column))
+
+(defmethod xref-location-buffer ((l xref-file-location))
+  (with-slots (file) l
+    (or (get-file-buffer file)
+	(let ((find-file-suppress-same-file-warnings t))
+	  (find-file-noselect file)))))
+
+(defmethod xref-location-position ((l xref-file-location))
+  (with-slots (line column) l
+    (with-current-buffer (xref-location-buffer l)
+      (save-restriction
+	(widen)
+	(save-excursion
+	  (goto-char (point-min))
+	  (beginning-of-line line)
+	  (move-to-column column)
+	  (point))))))
+
+(defclass xref-buffer-location (xref-location)
+  ((buffer :type buffer :initarg :buffer :reader xref-location-buffer)
+   (position :type fixnum :initarg :position :reader xref-location-position)))
+
+(defun xref-make-buffer-location (buffer position)
+  "Create and return a new xref-buffer-location."
+  (make-instance 'xref-buffer-location :buffer buffer :position position))
+
+(defclass xref-bogus-location (xref-location)
+  ((message :type string :initarg :message
+	    :reader xref-bogus-location-message))
+  :documentation "Bogus locations are sometimes useful to
+indicate errors, e.g. when we know that a function exists but the
+actual location is not known.")
+
+(defun xref-make-bogus-location (message)
+  "Create and return a new xref-bogus-location."
+  (make-instance 'xref-bogus-location :message message))
+
+(defmethod xref-location-buffer ((l xref-bogus-location))
+  (with-slots (message) l
+    (error "%s" message)))
+
+(defmethod xref-location-position ((l xref-bogus-location))
+  (with-slots (message) l
+    (error "%s" message)))
+
+\f
+;;; cross reference
+
+(defclass xref--xref ()
+  ((description :type string :initarg :description
+		:reader xref--xref-description)
+   (location :type xref-location :initarg :location
+	     :reader xref--xref-location))
+  :comment "An xref is used to display and locate constructs like
+variables or functions.")
+
+(defun xref-make (description location)
+  "Create and return an new xref.
+DESCRIPTION is a short string to describe the xref.
+LOCATION is an `xref-location'."
+  (make-instance 'xref--xref :description description :location location))
+
+\f
+;;; Backend
+
+;; Ugly name because defclass stores the class object in the symbol.
+(defclass xref-backend-class () ()
+  :documentation "Abstract superclass for backends.")
+
+;; For now, make the etags backend the default.
+(defvar xref-backend-function #'etags-xref-backend-function
+  "Function called to find the current xref-backend.
+The function is called with no arguments and should return
+a subclass of `xref-backend-class'.")
+
+(defun xref--backend ()
+  (funcall xref-backend-function))
+
+;;;; Backend interface functions
+
+(defgeneric xref-lookup-definitions (backend identifier)
+  "Find definitions of IDENTIFIER.
+The result is a list of `xref--xref' objects.
+If no definition can be found, return nil.")
+
+(defgeneric xref-lookup-references (backend identifier)
+  "Find references of IDENTIFIER.
+The result is a list of `xref--xref' objects.
+If no reference can be found, return nil.")
+
+;; An identifier is backend specific.  By default it's a string but it
+;; can be any type, expect nil.
+(defgeneric xref-identifier-at-point (backend)
+  "Search and return the identfier near point.
+If no identifier can be found, return nil.")
+
+(defgeneric xref-read-identifier-from-minibuffer (backend prompt init)
+  "Read an identifier from the minibuffer.
+PROMPT is a string used for prompting.
+INIT is either an identifier or nil.")
+
+(defgeneric xref-identifier-to-string (backend identifier)
+  "Return a string representing IDENTIFIER.")
+
+;; default implementation for identifiers
+(defmethod xref-identifier-at-point (_backend)
+  (let ((thing (thing-at-point 'symbol)))
+    (and thing (substring-no-properties thing))))
+
+(defmethod xref-read-identifier-from-minibuffer (backend prompt init)
+  (read-from-minibuffer prompt (xref-identifier-to-string backend init)))
+
+(defmethod xref-identifier-to-string (_backend identifier)
+  (with-output-to-string (princ identifier)))
+
+\f
+;;; misc utilities
+(defun xref--alistify (list key test)
+  "Partition the elements of LIST into an alist.
+KEY extracts the key from an element and TEST is used to compare
+keys."
+  (let ((alist '()))
+    (dolist (e list)
+      (let* ((k (funcall key e))
+	     (probe (cl-assoc k alist :test test)))
+	(if probe
+	    (setcdr probe (cons e (cdr probe)))
+          (push (cons k (list e)) alist))))
+    ;; Put them back in order.
+    (cl-loop for (key . value) in (reverse alist)
+             collect (cons key (reverse value)))))
+
+(defun xref--insert-propertized (props &rest strings)
+  "Insert STRINGS with text properties PROPS."
+  (let ((start (point)))
+    (apply #'insert strings)
+    (add-text-properties start (point) props)))
+
+(defun xref--search-property (property &optional backward)
+    "Search the next text range where text property PROPERTY is non-nil.
+Return the value of PROPERTY.  If BACKWARD is non-nil, search
+backward."
+  (let ((next (if backward
+		  #'previous-single-char-property-change
+		#'next-single-char-property-change))
+        (start (point))
+        (value nil))
+    (while (progn
+             (goto-char (funcall next (point) property))
+             (not (or (setq value (get-text-property (point) property))
+                      (eobp)
+                      (bobp)))))
+    (cond (value)
+	  (t (goto-char start) nil))))
+
+\f
+;;; Marker stack  (M-. pushes, M-, pops)
+
+(defconst xref--marker-ring-length 16)
+
+(defvar xref--marker-ring (make-ring xref--marker-ring-length)
+  "Ring of markers to implement the marker stack.")
+
+(defun xref-push-marker-stack ()
+  "Add point to the marker stack."
+  (ring-insert xref--marker-ring (point-marker)))
+
+;;;###autoload
+(defun xref-pop-marker-stack ()
+  "Pop back to where \\[xref-find-definitions] was last invoked."
+  (interactive)
+  (let ((ring xref--marker-ring))
+    (when (ring-empty-p ring)
+      (error "Marker stack is empty"))
+    (let ((marker (ring-remove ring 0)))
+      (switch-to-buffer (or (marker-buffer marker)
+			    (error "The marked buffer has been deleted")))
+      (goto-char (marker-position marker))
+      (set-marker marker nil nil))))
+
+;; etags.el needs this
+(defun xref-clear-marker-stack ()
+  "Discard all markers from the marker stack."
+  (let ((ring xref--marker-ring))
+    (while (not (ring-empty-p ring))
+      (let ((marker (ring-remove ring)))
+	(set-marker marker nil nil)))))
+
+\f
+(defun xref--goto-location (location)
+  "Set buffer and point according to xref-location LOCATION."
+  (set-buffer (xref-location-buffer location))
+  (let ((pos (xref-location-position location)))
+    (cond ((and (<= (point-min) pos) (<= pos (point-max))))
+	  (widen-automatically (widen))
+	  (t (error "Location is outside accessible part of buffer")))
+    (goto-char pos)))
+
+(defun xref--pop-to-location (location &optional window)
+  "Goto xref-location LOCATION and display the buffer.
+WINDOW controls how the buffer is displayed:
+  nil      -- switch-to-buffer
+  'window  -- pop-to-buffer (other window)
+  'frame   -- pop-to-buffer (other frame)"
+  (xref--goto-location location)
+  (cl-ecase window
+    ((nil)  (switch-to-buffer (current-buffer)))
+    (window (pop-to-buffer (current-buffer) t))
+    (frame  (let ((pop-up-frames t)) (pop-to-buffer (current-buffer) t)))))
+
+\f
+;;; XREF buffer (part of the UI)
+
+;; The xref buffer is used to display a set of xrefs.
+
+(defun xref--display-position (pos other-window recenter-arg)
+  ;; show the location, but don't hijack focus.
+  (with-selected-window (display-buffer (current-buffer) other-window)
+    (goto-char pos)
+    (recenter recenter-arg)))
+
+(defgeneric xref--show-location (location))
+(defmethod xref--show-location ((l xref-bogus-location))
+  (with-slots (message) l
+    (message "%s" message)))
+
+(defmethod xref--show-location (location)
+  (xref--goto-location location)
+  (xref--display-position (point) t 1))
+
+(defun xref--next-line (backward)
+  (let ((loc (xref--search-property 'xref-location backward)))
+    (when loc
+      (xref--show-location loc))))
+
+(defun xref-next-line ()
+  "Move to the next xref and display its source in the other window."
+  (interactive)
+  (xref--next-line nil))
+
+(defun xref-prev-line ()
+  "Move to the previous xref and display its source in the other window."
+  (interactive)
+  (xref--next-line t))
+
+(defun xref--location-at-point ()
+  (or (get-text-property (point) 'xref-location)
+      (error "No reference at point")))
+
+(defun xref-goto-xref ()
+  "Jump to the xref at point and close the xref buffer."
+  (interactive)
+  (xref--show-location (xref--location-at-point))
+  (quit-window))
+
+(define-derived-mode xref--xref-buffer-mode fundamental-mode "XREF"
+  "Mode for displaying cross refenences."
+  (setq buffer-read-only t))
+
+(let ((map xref--xref-buffer-mode-map))
+  (define-key map (kbd "q") #'quit-window)
+  (define-key map [remap next-line] #'xref-next-line)
+  (define-key map [remap previous-line] #'xref-prev-line)
+  (define-key map (kbd "RET") #'xref-goto-xref)
+
+  ;; suggested by Johan Claesson "to further reduce finger movement":
+  (define-key map (kbd ".") #'xref-next-line)
+  (define-key map (kbd ",") #'xref-prev-line))
+
+(defun xref--buffer-name () "*xref*")
+
+(defun xref--insert-xrefs (xref-alist)
+  "Insert XREF-ALIST in the current-buffer.
+XREF-ALIST is of the form ((GROUP . (XREF ...)) ...).  Where
+GROUP is a string for decoration purposes and XREF is an
+`xref--xref' object."
+  (cl-loop for ((group . xrefs) . more1) on xref-alist do
+           (xref--insert-propertized '(face bold) group "\n")
+           (cl-loop for (xref . more2) on xrefs do
+		    (insert "  ")
+		    (with-slots (description location) xref
+		      (xref--insert-propertized
+		       (list 'xref-location location
+			     'face 'font-lock-keyword-face)
+		       description))
+		    (when (or more1 more2)
+		      (insert "\n")))))
+
+(defgeneric xref-location-group (location)
+  "Return a string used to group a set of locations.
+This is typically the filename.")
+
+(defmethod xref-location-group ((_ xref-bogus-location)) "(No location)")
+(defmethod xref-location-group ((l xref-file-location))
+  (with-slots (file) l
+    file))
+(defmethod xref-location-group ((l xref-buffer-location))
+  (with-slots (buffer) l
+    (or (buffer-file-name buffer)
+	(format "(buffer %s)" (buffer-name buffer)))))
+
+(defun xref--analyze (xrefs)
+  "Find common filenames in XREFS.
+Return an alist of the form ((FILENAME . (XREF ...)) ...)."
+  (xref--alistify xrefs
+		  (lambda (x)
+		    (xref-location-group (xref--xref-location x)))
+		  #'equal))
+
+(defun xref--show-xref-buffer (xrefs)
+  (let ((xref-alist (xref--analyze xrefs)))
+    (with-current-buffer (get-buffer-create (xref--buffer-name))
+      (let ((inhibit-read-only t))
+	(erase-buffer)
+	(xref--insert-xrefs xref-alist)
+	(xref--xref-buffer-mode)
+	(pop-to-buffer (current-buffer))
+	(goto-char (point-min))
+	(current-buffer)))))
+
+\f
+;; This part of the UI seems fairly uncontroversial: it reads the
+;; identifier and deals with the single definition case.
+;;
+;; The controversial multiple definitions case is handed of to
+;; xref-show-xrefs-function.
+
+(defun xref--unique-location (xrefs)
+  "If it exists, return the single location in the list XREFS.
+If there are multiple or no locations in XREFS return nil."
+  (and xrefs
+       (let ((loc (xref--xref-location (car xrefs))))
+	 (and (cl-every (lambda (x)
+			  (xref-location= (xref--xref-location x) loc))
+			(cdr xrefs))
+	      loc))))
+
+(defvar xref-show-xrefs-function 'xref--show-xref-buffer
+  "Function to display a list of xrefs.")
+
+(defun xref--show-xrefs (id kind xrefs window)
+  (let ((1loc (xref--unique-location xrefs)))
+    (cond ((null xrefs)
+	   (error "No known %s for: %s"
+		  kind (xref-identifier-to-string (xref--backend) id)))
+	  (1loc
+	   (xref-push-marker-stack)
+	   (xref--pop-to-location 1loc window))
+	  (t
+	   (xref-push-marker-stack)
+	   (funcall xref-show-xrefs-function xrefs)))))
+
+(defun xref--read-identifier (prompt)
+  "Return the identifier at point or read it from the minibuffer."
+  (let* ((backend (xref--backend))
+	 (id (xref-identifier-at-point backend)))
+    (cond ((or current-prefix-arg (not id))
+	   (xref-read-identifier-from-minibuffer backend prompt id))
+	  (t id))))
+
+\f
+;;; Commands
+
+(defun xref--find-definitions (id window)
+  (xref--show-xrefs id "definitions"
+		    (xref-lookup-definitions (xref--backend) id)
+		    window))
+
+;;;###autoload
+(defun xref-find-definitions (identifier)
+  "Find the definition of the identifier at point.
+With prefix argument, prompt for the identifier."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier nil))
+
+;;;###autoload
+(defun xref-find-definitions-other-window (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'window))
+
+;;;###autoload
+(defun xref-find-definitions-other-frame (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'frame))
+
+;;;###autoload
+(defun xref-find-references (identifier)
+  "Find references for the identifier at point.
+With prefix argument, prompt for the identifier."
+  (interactive (list (xref--read-identifier "Find references of: ")))
+  (xref--show-xrefs identifier "references"
+		    (xref-lookup-references (xref--backend) identifier)
+		    nil))
+
+\f
+;;; Key bindings
+
+;;;###autoload
+(progn
+  (define-key esc-map "." #'xref-find-definitions)
+  (define-key esc-map "," #'xref-pop-marker-stack)
+  (define-key ctl-x-4-map "." #'xref-find-definitions-other-window)
+  (define-key ctl-x-5-map "." #'xref-find-definitions-other-frame))
+
+\f
+;;; ELisp backend
+
+;; This is defined here and not in elisp-mode.el so that we don't need
+;; to load xref.el just to create the *scratch* buffer.
+
+;; For now, this is just a wrapper around find-func.el in particular
+;; `find-definition-noselect'.
+
+(defclass xref--elisp-backend (xref-backend-class) ())
+
+(defvar xref--elisp-backend-var (make-instance 'xref--elisp-backend))
+
+;;;###autoload
+(defun xref-elisp-backend-function () xref--elisp-backend-var)
+
+(defun xref--elisp-find-definition (symbol type)
+  (let ((loc (condition-case err
+		 (let ((loc (save-excursion
+			      (find-definition-noselect symbol type))))
+		   (xref-make-buffer-location (car loc) (or (cdr loc) 1)))
+	       (error
+		(xref-make-bogus-location (error-message-string err)))))
+	(desc (format "(%s %s)" (or type 'defun) symbol)))
+    (xref-make desc loc)))
+
+;; FIXME: include other stuff likes faces, compiler-macros, methods...
+(defmethod xref-lookup-definitions ((_ xref--elisp-backend) id)
+  (let ((sym (intern-soft id)))
+    (if (null sym)
+	'()
+      (let ((fun (if (fboundp sym) (xref--elisp-find-definition sym nil)))
+	    (var (if (boundp sym) (xref--elisp-find-definition sym 'defvar))))
+	(remove nil (list fun var))))))
+
+\f
+(provide 'xref)
+
+;;; xref.el ends here
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-08 19:58                                 ` Helmut Eller
@ 2014-12-08 21:38                                   ` Stefan Monnier
  2014-12-08 21:58                                     ` Jorgen Schaefer
  2014-12-09  2:34                                     ` Stefan Monnier
  0 siblings, 2 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-12-08 21:38 UTC (permalink / raw)
  To: Helmut Eller, Jorgen Schaefer; +Cc: emacs-devel

> > What happened to the other contenders?
> Nothing in particular.  The competition is still open.

Jorgen, what's your opinion?

> The elisp backend code needs to define a subclass of xref-backend-class
> and that can't be done without loading xref.el and eieio, I think.

Yes, I saw that.  I guess that's one advantage of the "plain function"
API compared to the EIEIO-based API.

> elisp-mode is needed to create the *scratch* buffer and it seemed to me
> that loading xref.el so early increases startup time for no good reason.

If it's needed for a normal "emacs -Q", then it should be preloaded.
And as it currently stands, I'd rather not preload EIEIO, so indeed the
elisp xref code can't be in elisp-mode.el.

> find-func.el might be a reasonable place for the elisp-xref
> backend code.

Indeed, that sounds like a good solution, thanks.

> The indirection makes it easier to autoload xref.el.

Hmm... I don't see why that'd make a difference, unless by "xref.el" you
mean "the file in which the elisp-xref-backend-function is defined".

> If emacs-lisp-mode wants to create a backend object it has to load
> xref.el first, which as I said above seems undesirable.

Indeed, I see that this makes a significant difference.

Another cosmetic issue I see with this EIEIO-based API is the need to
define a new class with a new instance which is really just a dummy
constant (no instance fields) and is only used to get the
method-dynamic-dispatch working (I guess in CLOS we could circumvent
this dummy class&instance by using an (eql <foo>) as the "class"
specializer).


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-08 21:38                                   ` Stefan Monnier
@ 2014-12-08 21:58                                     ` Jorgen Schaefer
  2014-12-09  2:33                                       ` Stefan Monnier
  2014-12-09  2:34                                     ` Stefan Monnier
  1 sibling, 1 reply; 172+ messages in thread
From: Jorgen Schaefer @ 2014-12-08 21:58 UTC (permalink / raw)
  To: Stefan Monnier, Helmut Eller; +Cc: emacs-devel

On 12/08/14 22:38, Stefan Monnier wrote:
>>> What happened to the other contenders?
>> Nothing in particular.  The competition is still open.
>
> Jorgen, what's your opinion?

I was waiting for a decision by the project leader as to which of the 
proposed solutions is preferable before continuing to spend time on 
something that might not get used at all (question asked in 
<20141120212840.103e5758@forcix>).

I do not particularly care either way. I wanted a solution to a problem, 
and whether I write the solution myself or someone else does it is fine 
with me, as long as the problem is solved. :-)

Sadly, I will be mostly away from computers in the next weeks. If you 
decide for the function-based solution, I will not be able to work on it 
until after new year.

Regards,
Jorgen



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-08 14:33                               ` Stefan Monnier
  2014-12-08 19:58                                 ` Helmut Eller
@ 2014-12-08 22:36                                 ` Stephen Leake
  1 sibling, 0 replies; 172+ messages in thread
From: Stephen Leake @ 2014-12-08 22:36 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>>> This said, I think the most urgent step is to get such a generalized
>>> find-definition into Emacs.
>> Here is a patch:
>
> What happened to the other contenders?

I voted for this one; I have seen no other traffic on this.


-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-08 21:58                                     ` Jorgen Schaefer
@ 2014-12-09  2:33                                       ` Stefan Monnier
  0 siblings, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-12-09  2:33 UTC (permalink / raw)
  To: Jorgen Schaefer; +Cc: Helmut Eller, emacs-devel

> I do not particularly care either way. I wanted a solution to a problem, and
> whether I write the solution myself or someone else does it is fine with me,
> as long as the problem is solved. :-)

OK.  It looks like Helmut has the momentum, but in any case, thank you
very much for pushing this and making it happen.  It's been among those
things that have crossed my mind many times but I never got around
to it.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-08 21:38                                   ` Stefan Monnier
  2014-12-08 21:58                                     ` Jorgen Schaefer
@ 2014-12-09  2:34                                     ` Stefan Monnier
  2014-12-09  8:40                                       ` Helmut Eller
  1 sibling, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-12-09  2:34 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel, Jorgen Schaefer

Alright, let's go ahead with this.  Can you send a "latest and greatest"
version of your code so I can install it into master?


        Stefan


>>>>> "Stefan" == Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> > What happened to the other contenders?
>> Nothing in particular.  The competition is still open.

> Jorgen, what's your opinion?

>> The elisp backend code needs to define a subclass of xref-backend-class
>> and that can't be done without loading xref.el and eieio, I think.

> Yes, I saw that.  I guess that's one advantage of the "plain function"
> API compared to the EIEIO-based API.

>> elisp-mode is needed to create the *scratch* buffer and it seemed to me
>> that loading xref.el so early increases startup time for no good reason.

> If it's needed for a normal "emacs -Q", then it should be preloaded.
> And as it currently stands, I'd rather not preload EIEIO, so indeed the
> elisp xref code can't be in elisp-mode.el.

>> find-func.el might be a reasonable place for the elisp-xref
>> backend code.

> Indeed, that sounds like a good solution, thanks.

>> The indirection makes it easier to autoload xref.el.

> Hmm... I don't see why that'd make a difference, unless by "xref.el" you
> mean "the file in which the elisp-xref-backend-function is defined".

>> If emacs-lisp-mode wants to create a backend object it has to load
>> xref.el first, which as I said above seems undesirable.

> Indeed, I see that this makes a significant difference.

> Another cosmetic issue I see with this EIEIO-based API is the need to
> define a new class with a new instance which is really just a dummy
> constant (no instance fields) and is only used to get the
> method-dynamic-dispatch working (I guess in CLOS we could circumvent
> this dummy class&instance by using an (eql <foo>) as the "class"
> specializer).


>         Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-09  2:34                                     ` Stefan Monnier
@ 2014-12-09  8:40                                       ` Helmut Eller
  2014-12-09 14:03                                         ` Dmitry Gutov
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-09  8:40 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 189 bytes --]

On Mon, Dec 08 2014, Stefan Monnier wrote:

> Alright, let's go ahead with this.  Can you send a "latest and greatest"
> version of your code so I can install it into master?

Here we go:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Generalized-infrastructure-for-find-definition.patch --]
[-- Type: text/x-diff, Size: 27397 bytes --]

From daa6a31e4903a5dd2d1450d4118fa0703ab88006 Mon Sep 17 00:00:00 2001
From: Helmut Eller <eller.helmut@gmail.com>
Date: Tue, 9 Dec 2014 09:37:34 +0100
Subject: [PATCH] Generalized infrastructure for find-definition

* progmodes/xref.el: New file.

* progmodes/etags.el (find-tag-marker-ring, pop-tag-mark): Move to
xref but keep aliases for backward compatibility.
(tags-reset-tags-tables): Use xref marker stack instead of
find-tag-marker-ring.
(etags--xref-backend, etags--xref-backend-var)
(etags-xref-backend-function): New xref backend.
(esc-map, ctl-x-4-map, ctl-x-5-map): Move key bindings for M-.,
M-,, C-x 4 M-., and C-x 5 M-. to xref.el

* emacs-lisp/find-func.el (find-function--xref-backend)
(find-function--xref-backend-var)
(find-function-xref-backend-function, find-function--find-xref):
New xref backend.

* progmodes/elisp-mode.el (emacs-lisp-mode): Initialize
xref-backend-function.
---
 lisp/emacs-lisp/find-func.el |   38 ++++
 lisp/progmodes/elisp-mode.el |    1 +
 lisp/progmodes/etags.el      |   88 ++++++--
 lisp/progmodes/xref.el       |  487 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 591 insertions(+), 23 deletions(-)
 create mode 100644 lisp/progmodes/xref.el

diff --git a/lisp/emacs-lisp/find-func.el b/lisp/emacs-lisp/find-func.el
index c372117..405135f 100644
--- a/lisp/emacs-lisp/find-func.el
+++ b/lisp/emacs-lisp/find-func.el
@@ -43,6 +43,8 @@
 
 ;;; Code:
 
+(require 'xref)
+
 ;;; User variables:
 
 (defgroup find-function nil
@@ -578,6 +580,42 @@ Set mark before moving, if the buffer already existed."
   (define-key ctl-x-4-map "V" 'find-variable-other-window)
   (define-key ctl-x-5-map "V" 'find-variable-other-frame))
 
+\f
+;;; Xref backend
+
+(defclass find-function--xref-backend (xref-backend-class) ())
+
+(defvar find-function--xref-backend-var
+  (make-instance 'find-function--xref-backend))
+
+;;;###autoload
+(defun find-function-xref-backend-function () find-function--xref-backend-var)
+
+(defun find-function--find-xref (symbol type)
+  (let ((loc (condition-case err
+		 (let ((loc (save-excursion
+			      (find-definition-noselect symbol type))))
+		   (xref-make-buffer-location (car loc) (or (cdr loc) 1)))
+	       (error
+		(xref-make-bogus-location (error-message-string err)))))
+	(desc (format "(%s %s)" (or type 'defun) symbol)))
+    (xref-make desc loc)))
+
+;; FIXME: include other stuff likes faces, compiler-macros, methods...
+(defmethod xref-lookup-definitions ((_ find-function--xref-backend) id)
+  (let ((sym (intern-soft id)))
+    (if (null sym)
+	'()
+      (let ((fun (if (fboundp sym) (find-function--find-xref sym nil)))
+	    (var (if (boundp sym) (find-function--find-xref sym 'defvar))))
+	(remove nil (list fun var))))))
+
+(defmethod xref-read-identifier-from-minibuffer
+  ((b find-function--xref-backend) prompt id)
+  (completing-read prompt obarray nil nil
+		   (if id (xref-identifier-to-string b id))))
+
+\f
 (provide 'find-func)
 
 ;;; find-func.el ends here
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index ba70f90..fa97890 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -231,6 +231,7 @@ Blank lines separate paragraphs.  Semicolons start comments.
   (setq imenu-case-fold-search nil)
   (setq-local eldoc-documentation-function
               #'elisp-eldoc-documentation-function)
+  (setq-local xref-backend-function #'find-function-xref-backend-function)
   (add-hook 'completion-at-point-functions
             #'elisp-completion-at-point nil 'local))
 
diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el
index b89b4cf..8696d8c 100644
--- a/lisp/progmodes/etags.el
+++ b/lisp/progmodes/etags.el
@@ -28,6 +28,7 @@
 
 (require 'ring)
 (require 'button)
+(require 'xref)
 
 ;;;###autoload
 (defvar tags-file-name nil
@@ -182,8 +183,8 @@ Example value:
 		       (sexp :tag "Tags to search")))
   :version "21.1")
 
-(defvar find-tag-marker-ring (make-ring find-tag-marker-ring-length)
-  "Ring of markers which are locations from which \\[find-tag] was invoked.")
+(define-obsolete-variable-alias 'find-tag-marker-ring 'xref--marker-ring
+  "25.1")
 
 (defvar default-tags-table-function nil
   "If non-nil, a function to choose a default tags file for a buffer.
@@ -716,12 +717,10 @@ Returns t if it visits a tags table, or nil if there are no more in the list."
     (while (< i find-tag-marker-ring-length)
       (if (aref (cddr tags-location-ring) i)
 	  (set-marker (aref (cddr tags-location-ring) i) nil))
-      (if (aref (cddr find-tag-marker-ring) i)
-	  (set-marker (aref (cddr find-tag-marker-ring) i) nil))
       (setq i (1+ i))))
+  (xref-clear-marker-stack)
   (setq tags-file-name nil
 	tags-location-ring (make-ring find-tag-marker-ring-length)
-	find-tag-marker-ring (make-ring find-tag-marker-ring-length)
 	tags-table-list nil
 	tags-table-computed-list nil
 	tags-table-computed-list-for nil
@@ -898,7 +897,7 @@ See documentation of variable `tags-file-name'."
 	      ;; Run the user's hook.  Do we really want to do this for pop?
 	      (run-hooks 'local-find-tag-hook))))
       ;; Record whence we came.
-      (ring-insert find-tag-marker-ring (point-marker))
+      (xref-push-marker-stack)
       (if (and next-p last-tag)
 	  ;; Find the same table we last used.
 	  (visit-tags-table-buffer 'same)
@@ -954,7 +953,6 @@ See documentation of variable `tags-file-name'."
 	(switch-to-buffer buf)
       (error (pop-to-buffer buf)))
     (goto-char pos)))
-;;;###autoload (define-key esc-map "." 'find-tag)
 
 ;;;###autoload
 (defun find-tag-other-window (tagname &optional next-p regexp-p)
@@ -995,7 +993,6 @@ See documentation of variable `tags-file-name'."
 			;; the window's point from the buffer.
 			(set-window-point (selected-window) tagpoint))
 		      window-point)))
-;;;###autoload (define-key ctl-x-4-map "." 'find-tag-other-window)
 
 ;;;###autoload
 (defun find-tag-other-frame (tagname &optional next-p)
@@ -1020,7 +1017,6 @@ See documentation of variable `tags-file-name'."
   (interactive (find-tag-interactive "Find tag other frame: "))
   (let ((pop-up-frames t))
     (find-tag-other-window tagname next-p)))
-;;;###autoload (define-key ctl-x-5-map "." 'find-tag-other-frame)
 
 ;;;###autoload
 (defun find-tag-regexp (regexp &optional next-p other-window)
@@ -1049,20 +1045,8 @@ See documentation of variable `tags-file-name'."
 ;;;###autoload (define-key esc-map "*" 'pop-tag-mark)
 
 ;;;###autoload
-(defun pop-tag-mark ()
-  "Pop back to where \\[find-tag] was last invoked.
+(defalias 'pop-tag-mark 'xref-pop-marker-stack)
 
-This is distinct from invoking \\[find-tag] with a negative argument
-since that pops a stack of markers at which tags were found, not from
-where they were found."
-  (interactive)
-  (if (ring-empty-p find-tag-marker-ring)
-      (error "No previous locations for find-tag invocation"))
-  (let ((marker (ring-remove find-tag-marker-ring 0)))
-    (switch-to-buffer (or (marker-buffer marker)
-                          (error "The marked buffer has been deleted")))
-    (goto-char (marker-position marker))
-    (set-marker marker nil nil)))
 \f
 (defvar tag-lines-already-matched nil
   "Matches remembered between calls.") ; Doc string: calls to what?
@@ -1859,7 +1843,6 @@ nil, we exit; otherwise we scan the next file."
     (and messaged
 	 (null tags-loop-operate)
 	 (message "Scanning file %s...found" buffer-file-name))))
-;;;###autoload (define-key esc-map "," 'tags-loop-continue)
 
 ;;;###autoload
 (defun tags-search (regexp &optional file-list-form)
@@ -2077,6 +2060,65 @@ for \\[find-tag] (which see)."
       (completion-in-region (car comp-data) (cadr comp-data)
 			    (nth 2 comp-data)
 			    (plist-get (nthcdr 3 comp-data) :predicate)))))
+
+\f
+;;; Xref backed
+
+(defclass etags--xref-backend (xref-backend-class) ())
+
+(defvar etags--xref-backend-var (make-instance 'etags--xref-backend))
+
+;;;###autoload
+(defun etags-xref-backend-function () etags--xref-backend-var)
+
+;; Stop searching if we find more than xref-limit matches, as the xref
+;; infrastracture is not designed to handle very long lists.
+;; Switching to some kind of lazy list might be better, but hopefully
+;; we hit the limit rarely.
+(defconst etags--xref-limit 1000)
+
+(defmethod xref-lookup-definitions ((_ etags--xref-backend) id)
+  ;; This emulates the behaviour of `find-tag-in-order' but instead of
+  ;; returning one match at a time all matches are returned as list.
+  ;; NOTE: find-tag-tag-order is typically a buffer-local variable.
+  (let* ((xrefs '())
+	 (first-time t)
+	 (regexp? (consp id))
+	 (pattern (if regexp? (cadr id) id))
+	 (search-fun (if regexp? #'re-search-forward #'search-forward))
+	 (marks (make-hash-table :test 'equal)))
+    (save-excursion
+      (while (visit-tags-table-buffer (not first-time))
+	(setq first-time nil)
+	(dolist (order-fun (cond (regexp? find-tag-regexp-tag-order)
+				 (t find-tag-tag-order)))
+	  (goto-char (point-min))
+	  (while (and (funcall search-fun pattern nil t)
+		      (< (hash-table-count marks) etags--xref-limit))
+	    (when (funcall order-fun pattern)
+	      (beginning-of-line)
+	      (cl-destructuring-bind (hint line &rest pos) (etags-snarf-tag)
+		(unless (eq hint t) ; hint==t if we are in a filename line
+		  (let* ((file (file-of-tag))
+			 (mark-key (cons file line)))
+		    (unless (gethash mark-key marks)
+		      (let ((loc (xref-make-file-location
+				  (expand-file-name file) line 0)))
+			(push (xref-make hint loc) xrefs)
+			(puthash mark-key t marks)))))))))))
+    (nreverse xrefs)))
+
+;; If the text in the minibuffer starts with " it's interpreted as a
+;; regexp.  This is an example for a non-trivial identifier type.
+(defmethod xref-read-identifier-from-minibuffer ((b etags--xref-backend)
+						 prompt id)
+  (let ((string (completing-read prompt (tags-lazy-completion-table) nil nil
+				 (if id (xref-identifier-to-string b id)))))
+    (cond ((string-match "^\"" string)
+	   `(rx ,(read string)))
+	  (t
+	   string))))
+
 \f
 (provide 'etags)
 
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
new file mode 100644
index 0000000..fd8d87b
--- /dev/null
+++ b/lisp/progmodes/xref.el
@@ -0,0 +1,487 @@
+;; xref.el --- Cross referencing commands              -*-lexical-binding:t-*-
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a somewhat generic infrastructure for cross
+;; referencing commands, in particular "find-definition".  Some part of
+;; the functionality must be implemented in a language dependent way
+;; and that's done by defining a "backend".  The generic code finds
+;; the backend by calling the function stored in the variable
+;; `xref-backend-function'.  A language specific mode usually makes
+;; `xref-backend-function' buffer local before storing into it.
+;;
+;; A backend is an instance of the EIEIO class `xref-backend-class'.
+;; Various generic functions (in the EIEIO sense of the word) are
+;; defined on xref-backend-class.  A language specific mode usually
+;; creates a subclasses of xref-backend-class and provides specialized
+;; methods for the generic functions.  See the `etags--xref-backend'
+;; and `find-function--xref-backend' classes for examples.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'eieio)
+(require 'ring)
+
+\f
+;;; Locations
+
+(defclass xref-location () ()
+  :documentation "A location represents a position in a file or buffer.")
+
+;; If a backend decides to subclass xref-location it can provide
+;; methods for some of the following functions:
+(defgeneric xref-location-buffer (location)
+  "Return the buffer for LOCATION.")
+
+(defgeneric xref-location-position (location)
+  "Return the position in LOCATIONs buffer.")
+
+(defgeneric xref-location= (location1 location2)
+  "Return t if two locations are equal.")
+
+(defmethod xref-location= ((l1 xref-location) l2)
+  (equal l1 l2))
+
+;;;; Commonly needed location classes are defined here:
+
+;; FIXME: might be useful to have an optional "hint" i.e. a string to
+;; search for in case the line number is sightly out of date.
+(defclass xref-file-location (xref-location)
+  ((file :type string :initarg :file)
+   (line :type fixnum :initarg :line)
+   (column :type fixnum :initarg :column))
+  :documentation "A file location is a file/line/column triple.
+Line numbers start from 1 and columns from 0.")
+
+(defun xref-make-file-location (file line column)
+  "Create and return a new xref-file-location."
+  (make-instance 'xref-file-location :file file :line line :column column))
+
+(defmethod xref-location-buffer ((l xref-file-location))
+  (with-slots (file) l
+    (or (get-file-buffer file)
+	(let ((find-file-suppress-same-file-warnings t))
+	  (find-file-noselect file)))))
+
+(defmethod xref-location-position ((l xref-file-location))
+  (with-slots (line column) l
+    (with-current-buffer (xref-location-buffer l)
+      (save-restriction
+	(widen)
+	(save-excursion
+	  (goto-char (point-min))
+	  (beginning-of-line line)
+	  (move-to-column column)
+	  (point))))))
+
+(defclass xref-buffer-location (xref-location)
+  ((buffer :type buffer :initarg :buffer :reader xref-location-buffer)
+   (position :type fixnum :initarg :position :reader xref-location-position)))
+
+(defun xref-make-buffer-location (buffer position)
+  "Create and return a new xref-buffer-location."
+  (make-instance 'xref-buffer-location :buffer buffer :position position))
+
+(defclass xref-bogus-location (xref-location)
+  ((message :type string :initarg :message
+	    :reader xref-bogus-location-message))
+  :documentation "Bogus locations are sometimes useful to
+indicate errors, e.g. when we know that a function exists but the
+actual location is not known.")
+
+(defun xref-make-bogus-location (message)
+  "Create and return a new xref-bogus-location."
+  (make-instance 'xref-bogus-location :message message))
+
+(defmethod xref-location-buffer ((l xref-bogus-location))
+  (with-slots (message) l
+    (error "%s" message)))
+
+(defmethod xref-location-position ((l xref-bogus-location))
+  (with-slots (message) l
+    (error "%s" message)))
+
+\f
+;;; cross reference
+
+(defclass xref--xref ()
+  ((description :type string :initarg :description
+		:reader xref--xref-description)
+   (location :type xref-location :initarg :location
+	     :reader xref--xref-location))
+  :comment "An xref is used to display and locate constructs like
+variables or functions.")
+
+(defun xref-make (description location)
+  "Create and return an new xref.
+DESCRIPTION is a short string to describe the xref.
+LOCATION is an `xref-location'."
+  (make-instance 'xref--xref :description description :location location))
+
+\f
+;;; Backend
+
+;; Ugly name because defclass stores the class object in the symbol.
+(defclass xref-backend-class () ()
+  :documentation "Abstract superclass for backends.")
+
+;; For now, make the etags backend the default.
+(defvar xref-backend-function #'etags-xref-backend-function
+  "Function called to find the current xref-backend.
+The function is called with no arguments and should return
+a subclass of `xref-backend-class'.")
+
+(defun xref--backend ()
+  (funcall xref-backend-function))
+
+;;;; Backend interface functions
+
+(defgeneric xref-lookup-definitions (backend identifier)
+  "Find definitions of IDENTIFIER.
+The result is a list of `xref--xref' objects.
+If no definition can be found, return nil.")
+
+(defgeneric xref-lookup-references (backend identifier)
+  "Find references of IDENTIFIER.
+The result is a list of `xref--xref' objects.
+If no reference can be found, return nil.")
+
+;; An identifier is backend specific.  By default it's a string but it
+;; can be any type, expect nil.
+(defgeneric xref-identifier-at-point (backend)
+  "Search and return the identfier near point.
+If no identifier can be found, return nil.")
+
+(defgeneric xref-read-identifier-from-minibuffer (backend prompt init)
+  "Read an identifier from the minibuffer.
+PROMPT is a string used for prompting.
+INIT is either an identifier or nil.")
+
+(defgeneric xref-identifier-to-string (backend identifier)
+  "Return a string representing IDENTIFIER.")
+
+;; default implementation for identifiers
+(defmethod xref-identifier-at-point (_backend)
+  (let ((thing (thing-at-point 'symbol)))
+    (and thing (substring-no-properties thing))))
+
+(defmethod xref-read-identifier-from-minibuffer (backend prompt id)
+  (read-from-minibuffer prompt
+			(if id (xref-identifier-to-string backend id))))
+
+(defmethod xref-identifier-to-string (_backend identifier)
+  (with-output-to-string (princ identifier)))
+
+\f
+;;; misc utilities
+(defun xref--alistify (list key test)
+  "Partition the elements of LIST into an alist.
+KEY extracts the key from an element and TEST is used to compare
+keys."
+  (let ((alist '()))
+    (dolist (e list)
+      (let* ((k (funcall key e))
+	     (probe (cl-assoc k alist :test test)))
+	(if probe
+	    (setcdr probe (cons e (cdr probe)))
+          (push (cons k (list e)) alist))))
+    ;; Put them back in order.
+    (cl-loop for (key . value) in (reverse alist)
+             collect (cons key (reverse value)))))
+
+(defun xref--insert-propertized (props &rest strings)
+  "Insert STRINGS with text properties PROPS."
+  (let ((start (point)))
+    (apply #'insert strings)
+    (add-text-properties start (point) props)))
+
+(defun xref--search-property (property &optional backward)
+    "Search the next text range where text property PROPERTY is non-nil.
+Return the value of PROPERTY.  If BACKWARD is non-nil, search
+backward."
+  (let ((next (if backward
+		  #'previous-single-char-property-change
+		#'next-single-char-property-change))
+        (start (point))
+        (value nil))
+    (while (progn
+             (goto-char (funcall next (point) property))
+             (not (or (setq value (get-text-property (point) property))
+                      (eobp)
+                      (bobp)))))
+    (cond (value)
+	  (t (goto-char start) nil))))
+
+\f
+;;; Marker stack  (M-. pushes, M-, pops)
+
+(defconst xref--marker-ring-length 16)
+
+(defvar xref--marker-ring (make-ring xref--marker-ring-length)
+  "Ring of markers to implement the marker stack.")
+
+(defun xref-push-marker-stack ()
+  "Add point to the marker stack."
+  (ring-insert xref--marker-ring (point-marker)))
+
+;;;###autoload
+(defun xref-pop-marker-stack ()
+  "Pop back to where \\[xref-find-definitions] was last invoked."
+  (interactive)
+  (let ((ring xref--marker-ring))
+    (when (ring-empty-p ring)
+      (error "Marker stack is empty"))
+    (let ((marker (ring-remove ring 0)))
+      (switch-to-buffer (or (marker-buffer marker)
+			    (error "The marked buffer has been deleted")))
+      (goto-char (marker-position marker))
+      (set-marker marker nil nil))))
+
+;; etags.el needs this
+(defun xref-clear-marker-stack ()
+  "Discard all markers from the marker stack."
+  (let ((ring xref--marker-ring))
+    (while (not (ring-empty-p ring))
+      (let ((marker (ring-remove ring)))
+	(set-marker marker nil nil)))))
+
+\f
+(defun xref--goto-location (location)
+  "Set buffer and point according to xref-location LOCATION."
+  (set-buffer (xref-location-buffer location))
+  (let ((pos (xref-location-position location)))
+    (cond ((and (<= (point-min) pos) (<= pos (point-max))))
+	  (widen-automatically (widen))
+	  (t (error "Location is outside accessible part of buffer")))
+    (goto-char pos)))
+
+(defun xref--pop-to-location (location &optional window)
+  "Goto xref-location LOCATION and display the buffer.
+WINDOW controls how the buffer is displayed:
+  nil      -- switch-to-buffer
+  'window  -- pop-to-buffer (other window)
+  'frame   -- pop-to-buffer (other frame)"
+  (xref--goto-location location)
+  (cl-ecase window
+    ((nil)  (switch-to-buffer (current-buffer)))
+    (window (pop-to-buffer (current-buffer) t))
+    (frame  (let ((pop-up-frames t)) (pop-to-buffer (current-buffer) t)))))
+
+\f
+;;; XREF buffer (part of the UI)
+
+;; The xref buffer is used to display a set of xrefs.
+
+(defun xref--display-position (pos other-window recenter-arg)
+  ;; show the location, but don't hijack focus.
+  (with-selected-window (display-buffer (current-buffer) other-window)
+    (goto-char pos)
+    (recenter recenter-arg)))
+
+(defgeneric xref--show-location (location))
+(defmethod xref--show-location ((l xref-bogus-location))
+  (with-slots (message) l
+    (message "%s" message)))
+
+(defmethod xref--show-location (location)
+  (xref--goto-location location)
+  (xref--display-position (point) t 1))
+
+(defun xref--next-line (backward)
+  (let ((loc (xref--search-property 'xref-location backward)))
+    (when loc
+      (xref--show-location loc))))
+
+(defun xref-next-line ()
+  "Move to the next xref and display its source in the other window."
+  (interactive)
+  (xref--next-line nil))
+
+(defun xref-prev-line ()
+  "Move to the previous xref and display its source in the other window."
+  (interactive)
+  (xref--next-line t))
+
+(defun xref--location-at-point ()
+  (or (get-text-property (point) 'xref-location)
+      (error "No reference at point")))
+
+(defun xref-goto-xref ()
+  "Jump to the xref at point and close the xref buffer."
+  (interactive)
+  (xref--show-location (xref--location-at-point))
+  (quit-window))
+
+(define-derived-mode xref--xref-buffer-mode fundamental-mode "XREF"
+  "Mode for displaying cross refenences."
+  (setq buffer-read-only t))
+
+(let ((map xref--xref-buffer-mode-map))
+  (define-key map (kbd "q") #'quit-window)
+  (define-key map [remap next-line] #'xref-next-line)
+  (define-key map [remap previous-line] #'xref-prev-line)
+  (define-key map (kbd "RET") #'xref-goto-xref)
+
+  ;; suggested by Johan Claesson "to further reduce finger movement":
+  (define-key map (kbd ".") #'xref-next-line)
+  (define-key map (kbd ",") #'xref-prev-line))
+
+(defun xref--buffer-name () "*xref*")
+
+(defun xref--insert-xrefs (xref-alist)
+  "Insert XREF-ALIST in the current-buffer.
+XREF-ALIST is of the form ((GROUP . (XREF ...)) ...).  Where
+GROUP is a string for decoration purposes and XREF is an
+`xref--xref' object."
+  (cl-loop for ((group . xrefs) . more1) on xref-alist do
+           (xref--insert-propertized '(face bold) group "\n")
+           (cl-loop for (xref . more2) on xrefs do
+		    (insert "  ")
+		    (with-slots (description location) xref
+		      (xref--insert-propertized
+		       (list 'xref-location location
+			     'face 'font-lock-keyword-face)
+		       description))
+		    (when (or more1 more2)
+		      (insert "\n")))))
+
+(defgeneric xref-location-group (location)
+  "Return a string used to group a set of locations.
+This is typically the filename.")
+
+(defmethod xref-location-group ((_ xref-bogus-location)) "(No location)")
+(defmethod xref-location-group ((l xref-file-location))
+  (with-slots (file) l
+    file))
+(defmethod xref-location-group ((l xref-buffer-location))
+  (with-slots (buffer) l
+    (or (buffer-file-name buffer)
+	(format "(buffer %s)" (buffer-name buffer)))))
+
+(defun xref--analyze (xrefs)
+  "Find common filenames in XREFS.
+Return an alist of the form ((FILENAME . (XREF ...)) ...)."
+  (xref--alistify xrefs
+		  (lambda (x)
+		    (xref-location-group (xref--xref-location x)))
+		  #'equal))
+
+(defun xref--show-xref-buffer (xrefs)
+  (let ((xref-alist (xref--analyze xrefs)))
+    (with-current-buffer (get-buffer-create (xref--buffer-name))
+      (let ((inhibit-read-only t))
+	(erase-buffer)
+	(xref--insert-xrefs xref-alist)
+	(xref--xref-buffer-mode)
+	(pop-to-buffer (current-buffer))
+	(goto-char (point-min))
+	(current-buffer)))))
+
+\f
+;; This part of the UI seems fairly uncontroversial: it reads the
+;; identifier and deals with the single definition case.
+;;
+;; The controversial multiple definitions case is handed of to
+;; xref-show-xrefs-function.
+
+(defun xref--unique-location (xrefs)
+  "If it exists, return the single location in the list XREFS.
+If there are multiple or no locations in XREFS return nil."
+  (and xrefs
+       (let ((loc (xref--xref-location (car xrefs))))
+	 (and (cl-every (lambda (x)
+			  (xref-location= (xref--xref-location x) loc))
+			(cdr xrefs))
+	      loc))))
+
+(defvar xref-show-xrefs-function 'xref--show-xref-buffer
+  "Function to display a list of xrefs.")
+
+(defun xref--show-xrefs (id kind xrefs window)
+  (let ((1loc (xref--unique-location xrefs)))
+    (cond ((null xrefs)
+	   (error "No known %s for: %s"
+		  kind (xref-identifier-to-string (xref--backend) id)))
+	  (1loc
+	   (xref-push-marker-stack)
+	   (xref--pop-to-location 1loc window))
+	  (t
+	   (xref-push-marker-stack)
+	   (funcall xref-show-xrefs-function xrefs)))))
+
+(defun xref--read-identifier (prompt)
+  "Return the identifier at point or read it from the minibuffer."
+  (let* ((backend (xref--backend))
+	 (id (xref-identifier-at-point backend)))
+    (cond ((or current-prefix-arg (not id))
+	   (xref-read-identifier-from-minibuffer backend prompt id))
+	  (t id))))
+
+\f
+;;; Commands
+
+(defun xref--find-definitions (id window)
+  (xref--show-xrefs id "definitions"
+		    (xref-lookup-definitions (xref--backend) id)
+		    window))
+
+;;;###autoload
+(defun xref-find-definitions (identifier)
+  "Find the definition of the identifier at point.
+With prefix argument, prompt for the identifier."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier nil))
+
+;;;###autoload
+(defun xref-find-definitions-other-window (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'window))
+
+;;;###autoload
+(defun xref-find-definitions-other-frame (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'frame))
+
+;;;###autoload
+(defun xref-find-references (identifier)
+  "Find references for the identifier at point.
+With prefix argument, prompt for the identifier."
+  (interactive (list (xref--read-identifier "Find references of: ")))
+  (xref--show-xrefs identifier "references"
+		    (xref-lookup-references (xref--backend) identifier)
+		    nil))
+
+\f
+;;; Key bindings
+
+;;;###autoload
+(progn
+  (define-key esc-map "." #'xref-find-definitions)
+  (define-key esc-map "," #'xref-pop-marker-stack)
+  (define-key ctl-x-4-map "." #'xref-find-definitions-other-window)
+  (define-key ctl-x-5-map "." #'xref-find-definitions-other-frame))
+
+\f
+(provide 'xref)
+
+;;; xref.el ends here
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-09  8:40                                       ` Helmut Eller
@ 2014-12-09 14:03                                         ` Dmitry Gutov
  2014-12-09 14:47                                           ` Helmut Eller
                                                             ` (2 more replies)
  0 siblings, 3 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-09 14:03 UTC (permalink / raw)
  To: Helmut Eller; +Cc: Stefan Monnier, emacs-devel

Helmut Eller <eller.helmut@gmail.com> writes:

> On Mon, Dec 08 2014, Stefan Monnier wrote:
>
>> Alright, let's go ahead with this.  Can you send a "latest and greatest"
>> version of your code so I can install it into master?
>
> Here we go:

Would anyone mind if I try my hand at porting this code to the "plain
functions" approach?

The functionality is already nice, but the standard Emacs APIs probably
shouldn't force users to get familiar with EIEIO (even if it's not that
hard).

For the location value, using cl-defstruct seems to be
appropriate, but using plain functions and vars for everything else
should be simpler.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-09 14:03                                         ` Dmitry Gutov
@ 2014-12-09 14:47                                           ` Helmut Eller
  2014-12-11  4:06                                             ` Dmitry Gutov
  2014-12-10  9:11                                           ` Stephen Leake
  2014-12-10 14:10                                           ` Stefan Monnier
  2 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-09 14:47 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: Stefan Monnier, emacs-devel

On Tue, Dec 09 2014, Dmitry Gutov wrote:

> Would anyone mind if I try my hand at porting this code to the "plain
> functions" approach?

I don't mind.  Rewrites welcome!

> For the location value, using cl-defstruct seems to be
> appropriate, but using plain functions and vars for everything else
> should be simpler.

Actually, the location stuff was the primary reason I started using
EIEIO.  In SLIME we have quite a zoo of location variants (not based on
EIEIO), e.g. file+byteoffset, file+charoffsets, search-strings, and
something that we call "source-paths".  A source-path describes a path
in the SEXP tree; sounds weird but works surprisingly well for Lisp
syntax.

Just want to say that locations should be very flexible.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-09 14:03                                         ` Dmitry Gutov
  2014-12-09 14:47                                           ` Helmut Eller
@ 2014-12-10  9:11                                           ` Stephen Leake
  2014-12-10 13:02                                             ` Dmitry Gutov
  2014-12-10 14:10                                           ` Stefan Monnier
  2 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-12-10  9:11 UTC (permalink / raw)
  To: emacs-devel

Dmitry Gutov <dgutov@yandex.ru> writes:

> Helmut Eller <eller.helmut@gmail.com> writes:
>
>> On Mon, Dec 08 2014, Stefan Monnier wrote:
>>
>>> Alright, let's go ahead with this.  Can you send a "latest and greatest"
>>> version of your code so I can install it into master?
>>
>> Here we go:
>
> Would anyone mind if I try my hand at porting this code to the "plain
> functions" approach?

I would; the use of EIEIO was the primary attraction of this version for
me.

> The functionality is already nice, but the standard Emacs APIs probably
> shouldn't force users to get familiar with EIEIO 

This package does not force anyone to know EIEIO; the package API is all
plain functions.

But people can use the classes if they want to; I intend to in ada-mode.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-10  9:11                                           ` Stephen Leake
@ 2014-12-10 13:02                                             ` Dmitry Gutov
  2014-12-10 17:00                                               ` Stephen Leake
  0 siblings, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-10 13:02 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

Stephen Leake <stephen_leake@stephe-leake.org> writes:

> This package does not force anyone to know EIEIO; the package API is all
> plain functions.

The API mandates that `xref-backend-function' returns an instance of a
class derived from xref-backend-class.  How would you implement the
find-func backend without changing xref?

> But people can use the classes if they want to; I intend to in ada-mode.

I don't see why this can't happen with a plain-function implementation.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-09 14:03                                         ` Dmitry Gutov
  2014-12-09 14:47                                           ` Helmut Eller
  2014-12-10  9:11                                           ` Stephen Leake
@ 2014-12-10 14:10                                           ` Stefan Monnier
  2014-12-11  4:08                                             ` Dmitry Gutov
  2 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-12-10 14:10 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: Helmut Eller, emacs-devel

> Would anyone mind if I try my hand at porting this code to the "plain
> functions" approach?

Go ahead, but try to minimize the changes w.r.t xref.el so it's easier
to compare the two.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-10 13:02                                             ` Dmitry Gutov
@ 2014-12-10 17:00                                               ` Stephen Leake
  2014-12-10 19:06                                                 ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-12-10 17:00 UTC (permalink / raw)
  To: emacs-devel

Dmitry Gutov <dgutov@yandex.ru> writes:

> Stephen Leake <stephen_leake@stephe-leake.org> writes:
>
>> This package does not force anyone to know EIEIO; the package API is all
>> plain functions.
>
> The API mandates that `xref-backend-function' returns an instance of a
> class derived from xref-backend-class.  

Ah, right; I forgot about that.

On the other hand, the required code is three lines:

(defclass xref-ada-backend (xref-backend-class) ())
(defvar xref--ada-backend (make-instance 'xref-ada-backend))
(defun xref-ada-backend-function () xref--ada-backend)

xref could provide a macro that does this.

Everything else in a major-mode backend can be non-EIEIO.

>> But people can use the classes if they want to; I intend to in ada-mode.
>
> I don't see why this can't happen with a plain-function implementation.

Perhaps I've misunderstood what you are proposing.

I have the impression you are proposing to entirely delete EIEIO from xref.

What are you proposing?

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-10 17:00                                               ` Stephen Leake
@ 2014-12-10 19:06                                                 ` Stefan Monnier
  2014-12-12  1:03                                                   ` Stephen Leake
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-12-10 19:06 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

> On the other hand, the required code is three lines:

> (defclass xref-ada-backend (xref-backend-class) ())
> (defvar xref--ada-backend (make-instance 'xref-ada-backend))
> (defun xref-ada-backend-function () xref--ada-backend)

You also need to define the corresponding methods.  IOW, all the visible
part of the API relies on EIEIO.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-09 14:47                                           ` Helmut Eller
@ 2014-12-11  4:06                                             ` Dmitry Gutov
  2014-12-11  8:09                                               ` Helmut Eller
  2014-12-11 15:07                                               ` Stefan Monnier
  0 siblings, 2 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-11  4:06 UTC (permalink / raw)
  To: Helmut Eller; +Cc: Stefan Monnier, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 2981 bytes --]

Helmut Eller <eller.helmut@gmail.com> writes:

> I don't mind.  Rewrites welcome!

Ok, thanks. The modified proposal attached.

To be honest, now I can better see the appeal of using the classes here.
For instance, in the modified API a backend has to return `:none' in its
identifier-at-point implementation if not found, because otherwise the
default implementation will be called (and it's hard to implement the
overriding otherwise).

Although we could use a plist (or alist) instead of a
function-with-multiple-calling-conventions.

Suppose we end up using your implementation, do you think there could be
a reasonable case where a xref-backend-function creates a non-trivial
instance of its backend class, with instance vars, etc? Because
otherwise it'll remain an odd wart. Then the variable can just hold the
class symbol, and instantiation can be performed on-demain, or even each
time it's used: it's cheap anyway.

> Actually, the location stuff was the primary reason I started using
> EIEIO.  In SLIME we have quite a zoo of location variants (not based on
> EIEIO), e.g. file+byteoffset, file+charoffsets, search-strings, and
> something that we call "source-paths".  A source-path describes a path
> in the SEXP tree; sounds weird but works surprisingly well for Lisp
> syntax.
>
> Just want to say that locations should be very flexible.

Right, I can see how EIEIO can be useful in this case, so this is the
part I mostly left untouched, for now. However, it could be simplified:

* I'm pretty sure the default implementation of `xref-location=' will
  cover 99% of all cases.

* There's no real need for the above and `xref--unique-location'. We can
  just require the implementations to return lists without duplicates. I
  would think that most would do that even without being asked.

* What is `xref--show-location' for? Why do we want to allow certain
  types to override this behavior? I think `xref--show-xref-buffer' is
  also suspect at this stage of implementation.

`xref-location-group' is quite neat, though. But depending on whether we
really want to make this lighter-weight, the class hierarchy here can be
replaced with some ad-hoc polymorphism (so we could say that a location
can either be a (buffer position) (file line column), or, say, a closure
for the most general case).

-

I kept `etags--xref-read-identifier', but I don't particularly like it:
in other places, when we want to use a regexp, that's usually a
different command, or a prefix argument, etc. Inputting quotes to make
the contents work as a regexp is... odd.

-

The "identifier at point" and "read identifier" makes me wonder how it
would work with more complex languages, where we delegate a lot of logic
to an external server process. Will a two-request workflow become usual?
First ask the server what's at point, and then send it back with a
specific question. I guess some existing packages would be hard to adapt
to that.

-

We should probably port Jorgen's tests.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Modified xref proposal --]
[-- Type: text/x-diff, Size: 26546 bytes --]

diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index ba70f90..d2b4b21 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -227,10 +227,12 @@ Blank lines separate paragraphs.  Semicolons start comments.
 
 \\{emacs-lisp-mode-map}"
   :group 'lisp
+  (defvar xref-backend)
   (lisp-mode-variables nil nil 'elisp)
   (setq imenu-case-fold-search nil)
   (setq-local eldoc-documentation-function
               #'elisp-eldoc-documentation-function)
+  (setq-local xref-backend #'elisp-xref-backend)
   (add-hook 'completion-at-point-functions
             #'elisp-completion-at-point nil 'local))
 
@@ -548,6 +550,38 @@ It can be quoted, or be inside a quoted form."
 (define-obsolete-function-alias
   'lisp-completion-at-point 'elisp-completion-at-point "25.1")
 
+;;; Xref backend
+
+(declare-function xref-make-buffer-location "xref" (buffer position))
+(declare-function xref-make-bogus-location "xref" (message))
+(declare-function xref-make "xref" (description location))
+
+(defun elisp-xref-backend (action &optional id &rest args)
+  (pcase action
+    (`definitions (elisp--xref-lookup-definitions id))
+    (`read-identifier (elisp--xref-read-identifier id (car args)))))
+
+(defun elisp--find-xref (symbol type)
+  (let ((loc (condition-case err
+                 (let ((loc (save-excursion
+                              (find-definition-noselect symbol type))))
+                   (xref-make-buffer-location (car loc) (or (cdr loc) 1)))
+               (error
+                (xref-make-bogus-location (error-message-string err)))))
+        (desc (format "(%s %s)" (or type 'defun) symbol)))
+    (xref-make desc loc)))
+
+;; FIXME: unify with `elisp--company-location'.
+(defun elisp--xref-lookup-definitions (id)
+  (let ((sym (intern-soft id)))
+    (when sym
+      (let ((fun (if (fboundp sym) (elisp--find-xref sym nil)))
+            (var (if (boundp sym) (elisp--find-xref sym 'defvar))))
+        (remove nil (list fun var))))))
+
+(defun elisp--xref-read-identifier (id prompt)
+  (completing-read prompt obarray nil nil id))
+
 ;;; Elisp Interaction mode
 
 (defvar lisp-interaction-mode-map
diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el
index b89b4cf..2a8a20d 100644
--- a/lisp/progmodes/etags.el
+++ b/lisp/progmodes/etags.el
@@ -28,6 +28,7 @@
 
 (require 'ring)
 (require 'button)
+(require 'xref)
 
 ;;;###autoload
 (defvar tags-file-name nil
@@ -182,8 +183,8 @@ Example value:
 		       (sexp :tag "Tags to search")))
   :version "21.1")
 
-(defvar find-tag-marker-ring (make-ring find-tag-marker-ring-length)
-  "Ring of markers which are locations from which \\[find-tag] was invoked.")
+(define-obsolete-variable-alias 'find-tag-marker-ring 'xref--marker-ring
+  "25.1")
 
 (defvar default-tags-table-function nil
   "If non-nil, a function to choose a default tags file for a buffer.
@@ -716,12 +717,10 @@ Returns t if it visits a tags table, or nil if there are no more in the list."
     (while (< i find-tag-marker-ring-length)
       (if (aref (cddr tags-location-ring) i)
 	  (set-marker (aref (cddr tags-location-ring) i) nil))
-      (if (aref (cddr find-tag-marker-ring) i)
-	  (set-marker (aref (cddr find-tag-marker-ring) i) nil))
       (setq i (1+ i))))
+  (xref-clear-marker-stack)
   (setq tags-file-name nil
 	tags-location-ring (make-ring find-tag-marker-ring-length)
-	find-tag-marker-ring (make-ring find-tag-marker-ring-length)
 	tags-table-list nil
 	tags-table-computed-list nil
 	tags-table-computed-list-for nil
@@ -898,7 +897,7 @@ See documentation of variable `tags-file-name'."
 	      ;; Run the user's hook.  Do we really want to do this for pop?
 	      (run-hooks 'local-find-tag-hook))))
       ;; Record whence we came.
-      (ring-insert find-tag-marker-ring (point-marker))
+      (xref-push-marker-stack)
       (if (and next-p last-tag)
 	  ;; Find the same table we last used.
 	  (visit-tags-table-buffer 'same)
@@ -954,7 +953,6 @@ See documentation of variable `tags-file-name'."
 	(switch-to-buffer buf)
       (error (pop-to-buffer buf)))
     (goto-char pos)))
-;;;###autoload (define-key esc-map "." 'find-tag)
 
 ;;;###autoload
 (defun find-tag-other-window (tagname &optional next-p regexp-p)
@@ -995,7 +993,6 @@ See documentation of variable `tags-file-name'."
 			;; the window's point from the buffer.
 			(set-window-point (selected-window) tagpoint))
 		      window-point)))
-;;;###autoload (define-key ctl-x-4-map "." 'find-tag-other-window)
 
 ;;;###autoload
 (defun find-tag-other-frame (tagname &optional next-p)
@@ -1020,7 +1017,6 @@ See documentation of variable `tags-file-name'."
   (interactive (find-tag-interactive "Find tag other frame: "))
   (let ((pop-up-frames t))
     (find-tag-other-window tagname next-p)))
-;;;###autoload (define-key ctl-x-5-map "." 'find-tag-other-frame)
 
 ;;;###autoload
 (defun find-tag-regexp (regexp &optional next-p other-window)
@@ -1049,20 +1045,8 @@ See documentation of variable `tags-file-name'."
 ;;;###autoload (define-key esc-map "*" 'pop-tag-mark)
 
 ;;;###autoload
-(defun pop-tag-mark ()
-  "Pop back to where \\[find-tag] was last invoked.
+(defalias 'pop-tag-mark 'xref-pop-marker-stack)
 
-This is distinct from invoking \\[find-tag] with a negative argument
-since that pops a stack of markers at which tags were found, not from
-where they were found."
-  (interactive)
-  (if (ring-empty-p find-tag-marker-ring)
-      (error "No previous locations for find-tag invocation"))
-  (let ((marker (ring-remove find-tag-marker-ring 0)))
-    (switch-to-buffer (or (marker-buffer marker)
-                          (error "The marked buffer has been deleted")))
-    (goto-char (marker-position marker))
-    (set-marker marker nil nil)))
 \f
 (defvar tag-lines-already-matched nil
   "Matches remembered between calls.") ; Doc string: calls to what?
@@ -1859,7 +1843,6 @@ nil, we exit; otherwise we scan the next file."
     (and messaged
 	 (null tags-loop-operate)
 	 (message "Scanning file %s...found" buffer-file-name))))
-;;;###autoload (define-key esc-map "," 'tags-loop-continue)
 
 ;;;###autoload
 (defun tags-search (regexp &optional file-list-form)
@@ -2077,6 +2060,63 @@ for \\[find-tag] (which see)."
       (completion-in-region (car comp-data) (cadr comp-data)
 			    (nth 2 comp-data)
 			    (plist-get (nthcdr 3 comp-data) :predicate)))))
+
+\f
+;;; Xref backend
+
+;; Stop searching if we find more than xref-limit matches, as the xref
+;; infrastracture is not designed to handle very long lists.
+;; Switching to some kind of lazy list might be better, but hopefully
+;; we hit the limit rarely.
+(defconst etags--xref-limit 1000)
+
+;;;###autoload
+(defun etags-xref-backend (action &optional id &rest args)
+  (pcase action
+    (`definitions (etags--xref-lookup-definitions id))
+    (`read-identifier (etags--xref-read-identifier id (car args)))))
+
+(defun etags--xref-lookup-definitions (id)
+  ;; This emulates the behaviour of `find-tag-in-order' but instead of
+  ;; returning one match at a time all matches are returned as list.
+  ;; NOTE: find-tag-tag-order is typically a buffer-local variable.
+  (let* ((xrefs '())
+         (first-time t)
+         (regexp? (consp id))
+         (pattern (if regexp? (cadr id) id))
+         (search-fun (if regexp? #'re-search-forward #'search-forward))
+         (marks (make-hash-table :test 'equal)))
+    (save-excursion
+      (while (visit-tags-table-buffer (not first-time))
+        (setq first-time nil)
+        (dolist (order-fun (cond (regexp? find-tag-regexp-tag-order)
+                                 (t find-tag-tag-order)))
+          (goto-char (point-min))
+          (while (and (funcall search-fun pattern nil t)
+                      (< (hash-table-count marks) etags--xref-limit))
+            (when (funcall order-fun pattern)
+              (beginning-of-line)
+              (cl-destructuring-bind (hint line &rest pos) (etags-snarf-tag)
+                (unless (eq hint t) ; hint==t if we are in a filename line
+                  (let* ((file (file-of-tag))
+                         (mark-key (cons file line)))
+                    (unless (gethash mark-key marks)
+                      (let ((loc (xref-make-file-location
+                                  (expand-file-name file) line 0)))
+                        (push (xref-make hint loc) xrefs)
+                        (puthash mark-key t marks)))))))))))
+    (nreverse xrefs)))
+
+;; If the text in the minibuffer starts with " it's interpreted as a
+;; regexp.  This is an example for a non-trivial identifier type.
+(defun etags--xref-read-identifier (id prompt)
+  (let ((string (completing-read prompt (tags-lazy-completion-table)
+                                 nil nil id)))
+    (cond ((string-match "\\`\"" string)
+           `(rx ,(read string)))
+          (t
+           string))))
+
 \f
 (provide 'etags)
 
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
new file mode 100644
index 0000000..f8c372e
--- /dev/null
+++ b/lisp/progmodes/xref.el
@@ -0,0 +1,486 @@
+;; xref.el --- Cross referencing commands              -*-lexical-binding:t-*-
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+
+
+;; This file provides a somewhat generic infrastructure for cross
+;; referencing commands, in particular "find-definition".  Some part
+;; of the functionality must be implemented in a language dependent
+;; way and that's done by defining a "backend".  It's a function,
+;; stored in the variable `xref-backend'.  A language-specific mode
+;; usually makes that variable buffer-local before storing into it.
+;;
+;; For `xref-backend' calling conventions, see its description.  In
+;; some cases, it has to operate with "xref" and "location" instances.
+;; One would usually call `make-xref' and `xref-make-file-location' or
+;; `xref-make-buffer-location' to create them.
+;;
+;; Another core notion is an identifier. Its representation is
+;; backend-specific.  By default it's a string but it can be any type.
+;; Two of the actions in `xref-backend' convert it to a string and back.
+;;
+;; See the functions `etags-xref-backend' and `elisp-xref-backend'
+;; for full examples.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'eieio)
+(require 'ring)
+
+\f
+;;; Locations
+
+(defclass xref-location () ()
+  :documentation "A location represents a position in a file or buffer.")
+
+;; If a backend decides to subclass xref-location it can provide
+;; methods for some of the following functions:
+(defgeneric xref-location-buffer (location)
+  "Return the buffer for LOCATION.")
+
+(defgeneric xref-location-position (location)
+  "Return the position in LOCATIONs buffer.")
+
+(defgeneric xref-location= (location1 location2)
+  "Return t if two locations are equal.")
+
+(defmethod xref-location= ((l1 xref-location) l2)
+  (equal l1 l2))
+
+;;;; Commonly needed location classes are defined here:
+
+;; FIXME: might be useful to have an optional "hint" i.e. a string to
+;; search for in case the line number is sightly out of date.
+(defclass xref-file-location (xref-location)
+  ((file :type string :initarg :file)
+   (line :type fixnum :initarg :line)
+   (column :type fixnum :initarg :column))
+  :documentation "A file location is a file/line/column triple.
+Line numbers start from 1 and columns from 0.")
+
+(defun xref-make-file-location (file line column)
+  "Create and return a new xref-file-location."
+  (make-instance 'xref-file-location :file file :line line :column column))
+
+(defmethod xref-location-buffer ((l xref-file-location))
+  (with-slots (file) l
+    (or (get-file-buffer file)
+        (let ((find-file-suppress-same-file-warnings t))
+          (find-file-noselect file)))))
+
+(defmethod xref-location-position ((l xref-file-location))
+  (with-slots (line column) l
+    (with-current-buffer (xref-location-buffer l)
+      (save-restriction
+        (widen)
+        (save-excursion
+          (goto-char (point-min))
+          (beginning-of-line line)
+          (move-to-column column)
+          (point))))))
+
+(defclass xref-buffer-location (xref-location)
+  ((buffer :type buffer :initarg :buffer :reader xref-location-buffer)
+   (position :type fixnum :initarg :position :reader xref-location-position)))
+
+(defun xref-make-buffer-location (buffer position)
+  "Create and return a new xref-buffer-location."
+  (make-instance 'xref-buffer-location :buffer buffer :position position))
+
+(defclass xref-bogus-location (xref-location)
+  ((message :type string :initarg :message
+            :reader xref-bogus-location-message))
+  :documentation "Bogus locations are sometimes useful to
+indicate errors, e.g. when we know that a function exists but the
+actual location is not known.")
+
+(defun xref-make-bogus-location (message)
+  "Create and return a new xref-bogus-location."
+  (make-instance 'xref-bogus-location :message message))
+
+(defmethod xref-location-buffer ((l xref-bogus-location))
+  (with-slots (message) l
+    (error "%s" message)))
+
+(defmethod xref-location-position ((l xref-bogus-location))
+  (with-slots (message) l
+    (error "%s" message)))
+
+\f
+;;; cross reference
+
+(defclass xref--xref ()
+  ((description :type string :initarg :description
+                :reader xref--xref-description)
+   (location :type xref-location :initarg :location
+             :reader xref--xref-location))
+  :comment "An xref is used to display and locate constructs like
+variables or functions.")
+
+(defun xref-make (description location)
+  "Create and return a new xref.
+DESCRIPTION is a short string to describe the xref.
+LOCATION is an `xref-location'."
+  (make-instance 'xref--xref :description description :location location))
+
+\f
+;;; Backend
+
+;; For now, make the etags backend the default.
+(defvar xref-backend #'etags-xref-backend
+  "The current xref-backend.
+This function can be called with different sets of arguments, as
+described below.
+
+Required:
+
+ (definitions IDENTIFIER): Find definitions of IDENTIFIER.  The
+result must be a list of `xref--xref' objects.  If no definition
+can be found, return nil.
+
+ (references IDENTIFIER): Find references of IDENTIFIER.  The
+result must be a list of `xref--xref' objects.  If no reference
+can be found, return nil.
+
+Optional:
+
+ (read-identifier INIT PROMPT): Read an identifier from the
+minibuffer.  PROMPT is a string used for prompting.  INIT is
+either an identifier to use as the initial value or nil.
+
+ (identifier-at-point): Search and return the identfier near
+point.  If no identifier can be found, return `:none'.
+
+ (identifier-to-string IDENTIFIER): Return a string representing
+IDENTIFIER.")
+
+;;;; Backend interface functions
+
+(defun xref--call-backend (action &rest args)
+  (apply xref-backend action args))
+
+(defun xref--identifier-at-point ()
+  (let ((res (or (xref--call-backend 'identifier-at-point)
+                 (let ((thing (thing-at-point 'symbol)))
+                   (and thing (substring-no-properties thing))))))
+    (unless (eq res :none) res)))
+
+(defun xref--identifier-to-string (identifier)
+  (or (xref--call-backend 'identifier-to-string identifier)
+      (with-output-to-string (princ identifier))))
+
+\f
+;;; misc utilities
+(defun xref--alistify (list key test)
+  "Partition the elements of LIST into an alist.
+KEY extracts the key from an element and TEST is used to compare
+keys."
+  (let ((alist '()))
+    (dolist (e list)
+      (let* ((k (funcall key e))
+             (probe (cl-assoc k alist :test test)))
+        (if probe
+            (setcdr probe (cons e (cdr probe)))
+          (push (cons k (list e)) alist))))
+    ;; Put them back in order.
+    (cl-loop for (key . value) in (reverse alist)
+             collect (cons key (reverse value)))))
+
+(defun xref--insert-propertized (props &rest strings)
+  "Insert STRINGS with text properties PROPS."
+  (let ((start (point)))
+    (apply #'insert strings)
+    (add-text-properties start (point) props)))
+
+(defun xref--search-property (property &optional backward)
+    "Search the next text range where text property PROPERTY is non-nil.
+Return the value of PROPERTY.  If BACKWARD is non-nil, search
+backward."
+  (let ((next (if backward
+                  #'previous-single-char-property-change
+                #'next-single-char-property-change))
+        (start (point))
+        (value nil))
+    (while (progn
+             (goto-char (funcall next (point) property))
+             (not (or (setq value (get-text-property (point) property))
+                      (eobp)
+                      (bobp)))))
+    (cond (value)
+          (t (goto-char start) nil))))
+
+\f
+;;; Marker stack  (M-. pushes, M-, pops)
+
+(defconst xref--marker-ring-length 16)
+
+(defvar xref--marker-ring (make-ring xref--marker-ring-length)
+  "Ring of markers to implement the marker stack.")
+
+(defun xref-push-marker-stack ()
+  "Add point to the marker stack."
+  (ring-insert xref--marker-ring (point-marker)))
+
+;;;###autoload
+(defun xref-pop-marker-stack ()
+  "Pop back to where \\[xref-find-definitions] was last invoked."
+  (interactive)
+  (let ((ring xref--marker-ring))
+    (when (ring-empty-p ring)
+      (error "Marker stack is empty"))
+    (let ((marker (ring-remove ring 0)))
+      (switch-to-buffer (or (marker-buffer marker)
+                            (error "The marked buffer has been deleted")))
+      (goto-char (marker-position marker))
+      (set-marker marker nil nil))))
+
+;; etags.el needs this
+(defun xref-clear-marker-stack ()
+  "Discard all markers from the marker stack."
+  (let ((ring xref--marker-ring))
+    (while (not (ring-empty-p ring))
+      (let ((marker (ring-remove ring)))
+        (set-marker marker nil nil)))))
+
+\f
+(defun xref--goto-location (location)
+  "Set buffer and point according to xref-location LOCATION."
+  (set-buffer (xref-location-buffer location))
+  (let ((pos (xref-location-position location)))
+    (cond ((and (<= (point-min) pos) (<= pos (point-max))))
+          (widen-automatically (widen))
+          (t (error "Location is outside accessible part of buffer")))
+    (goto-char pos)))
+
+(defun xref--pop-to-location (location &optional window)
+  "Goto xref-location LOCATION and display the buffer.
+WINDOW controls how the buffer is displayed:
+  nil      -- switch-to-buffer
+  'window  -- pop-to-buffer (other window)
+  'frame   -- pop-to-buffer (other frame)"
+  (xref--goto-location location)
+  (cl-ecase window
+    ((nil)  (switch-to-buffer (current-buffer)))
+    (window (pop-to-buffer (current-buffer) t))
+    (frame  (let ((pop-up-frames t)) (pop-to-buffer (current-buffer) t)))))
+
+\f
+;;; XREF buffer (part of the UI)
+
+;; The xref buffer is used to display a set of xrefs.
+
+(defun xref--display-position (pos other-window recenter-arg)
+  ;; show the location, but don't hijack focus.
+  (with-selected-window (display-buffer (current-buffer) other-window)
+    (goto-char pos)
+    (recenter recenter-arg)))
+
+(defgeneric xref--show-location (location))
+(defmethod xref--show-location ((l xref-bogus-location))
+  (with-slots (message) l
+    (message "%s" message)))
+
+(defmethod xref--show-location (location)
+  (xref--goto-location location)
+  (xref--display-position (point) t 1))
+
+(defun xref--next-line (backward)
+  (let ((loc (xref--search-property 'xref-location backward)))
+    (when loc
+      (xref--show-location loc))))
+
+(defun xref-next-line ()
+  "Move to the next xref and display its source in the other window."
+  (interactive)
+  (xref--next-line nil))
+
+(defun xref-prev-line ()
+  "Move to the previous xref and display its source in the other window."
+  (interactive)
+  (xref--next-line t))
+
+(defun xref--location-at-point ()
+  (or (get-text-property (point) 'xref-location)
+      (error "No reference at point")))
+
+(defun xref-goto-xref ()
+  "Jump to the xref at point and close the xref buffer."
+  (interactive)
+  (xref--show-location (xref--location-at-point))
+  (quit-window))
+
+(define-derived-mode xref--xref-buffer-mode fundamental-mode "XREF"
+  "Mode for displaying cross refenences."
+  (setq buffer-read-only t))
+
+(let ((map xref--xref-buffer-mode-map))
+  (define-key map (kbd "q") #'quit-window)
+  (define-key map [remap next-line] #'xref-next-line)
+  (define-key map [remap previous-line] #'xref-prev-line)
+  (define-key map (kbd "RET") #'xref-goto-xref)
+
+  ;; suggested by Johan Claesson "to further reduce finger movement":
+  (define-key map (kbd ".") #'xref-next-line)
+  (define-key map (kbd ",") #'xref-prev-line))
+
+(defun xref--buffer-name () "*xref*")
+
+(defun xref--insert-xrefs (xref-alist)
+  "Insert XREF-ALIST in the current-buffer.
+XREF-ALIST is of the form ((GROUP . (XREF ...)) ...).  Where
+GROUP is a string for decoration purposes and XREF is an
+`xref--xref' object."
+  (cl-loop for ((group . xrefs) . more1) on xref-alist do
+           (xref--insert-propertized '(face bold) group "\n")
+           (cl-loop for (xref . more2) on xrefs do
+                    (insert "  ")
+                    (with-slots (description location) xref
+                      (xref--insert-propertized
+                       (list 'xref-location location
+                             'face 'font-lock-keyword-face)
+                       description))
+                    (when (or more1 more2)
+                      (insert "\n")))))
+
+(defgeneric xref-location-group (location)
+  "Return a string used to group a set of locations.
+This is typically the filename.")
+
+(defmethod xref-location-group ((_ xref-bogus-location)) "(No location)")
+(defmethod xref-location-group ((l xref-file-location))
+  (with-slots (file) l
+    file))
+(defmethod xref-location-group ((l xref-buffer-location))
+  (with-slots (buffer) l
+    (or (buffer-file-name buffer)
+        (format "(buffer %s)" (buffer-name buffer)))))
+
+(defun xref--analyze (xrefs)
+  "Find common filenames in XREFS.
+Return an alist of the form ((FILENAME . (XREF ...)) ...)."
+  (xref--alistify xrefs
+                  (lambda (x)
+                    (xref-location-group (xref--xref-location x)))
+                  #'equal))
+
+(defun xref--show-xref-buffer (xrefs)
+  (let ((xref-alist (xref--analyze xrefs)))
+    (with-current-buffer (get-buffer-create (xref--buffer-name))
+      (let ((inhibit-read-only t))
+        (erase-buffer)
+        (xref--insert-xrefs xref-alist)
+        (xref--xref-buffer-mode)
+        (pop-to-buffer (current-buffer))
+        (goto-char (point-min))
+        (current-buffer)))))
+
+\f
+;; This part of the UI seems fairly uncontroversial: it reads the
+;; identifier and deals with the single definition case.
+;;
+;; The controversial multiple definitions case is handed of to
+;; xref-show-xrefs-function.
+
+(defun xref--unique-location (xrefs)
+  "If it exists, return the single location in the list XREFS.
+If there are multiple or no locations in XREFS return nil."
+  (and xrefs
+       (let ((loc (xref--xref-location (car xrefs))))
+         (and (cl-every (lambda (x)
+                          (xref-location= (xref--xref-location x) loc))
+                        (cdr xrefs))
+              loc))))
+
+(defvar xref-show-xrefs-function 'xref--show-xref-buffer
+  "Function to display a list of xrefs.")
+
+(defun xref--show-xrefs (id kind xrefs window)
+  (let ((1loc (xref--unique-location xrefs)))
+    (cond ((null xrefs)
+           (error "No known %s for: %s"
+                  kind (xref--identifier-to-string id)))
+          (1loc
+           (xref-push-marker-stack)
+           (xref--pop-to-location 1loc window))
+          (t
+           (xref-push-marker-stack)
+           (funcall xref-show-xrefs-function xrefs)))))
+
+(defun xref--read-identifier (prompt)
+  "Return the identifier at point or read it from the minibuffer."
+  (let ((id (xref--identifier-at-point)))
+    (cond ((or current-prefix-arg (not id))
+           (or (xref--call-backend 'read-identifier id prompt)
+               (read-from-minibuffer prompt
+                                     (if id (xref--identifier-to-string id)))))
+          (t id))))
+
+\f
+;;; Commands
+
+(defun xref--find-definitions (id window)
+  (xref--show-xrefs id "definitions"
+                    (xref--call-backend 'definitions id)
+                    window))
+
+;;;###autoload
+(defun xref-find-definitions (identifier)
+  "Find the definition of the identifier at point.
+With prefix argument, prompt for the identifier."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier nil))
+
+;;;###autoload
+(defun xref-find-definitions-other-window (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'window))
+
+;;;###autoload
+(defun xref-find-definitions-other-frame (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'frame))
+
+;;;###autoload
+(defun xref-find-references (identifier)
+  "Find references for the identifier at point.
+With prefix argument, prompt for the identifier."
+  (interactive (list (xref--read-identifier "Find references of: ")))
+  (xref--show-xrefs identifier "references"
+                    (xref--call-backend 'references identifier)
+                    nil))
+
+\f
+;;; Key bindings
+
+;;;###autoload
+(progn
+  (define-key esc-map "." #'xref-find-definitions)
+  (define-key esc-map "," #'xref-pop-marker-stack)
+  (define-key ctl-x-4-map "." #'xref-find-definitions-other-window)
+  (define-key ctl-x-5-map "." #'xref-find-definitions-other-frame))
+
+\f
+(provide 'xref)
+
+;;; xref.el ends here

^ permalink raw reply related	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-10 14:10                                           ` Stefan Monnier
@ 2014-12-11  4:08                                             ` Dmitry Gutov
  0 siblings, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-11  4:08 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Helmut Eller, emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> Go ahead, but try to minimize the changes w.r.t xref.el so it's easier
> to compare the two.

Sure. I actually changed less than declared: the main change is,
elisp-xref-backend lives in elisp-mode.el. And it's a plain function, of
course.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11  4:06                                             ` Dmitry Gutov
@ 2014-12-11  8:09                                               ` Helmut Eller
  2014-12-11 11:12                                                 ` Helmut Eller
  2014-12-11 22:52                                                 ` Dmitry Gutov
  2014-12-11 15:07                                               ` Stefan Monnier
  1 sibling, 2 replies; 172+ messages in thread
From: Helmut Eller @ 2014-12-11  8:09 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: Stefan Monnier, emacs-devel

On Thu, Dec 11 2014, Dmitry Gutov wrote:

> Ok, thanks. The modified proposal attached.

Looks pleasantly non-verbose.

> To be honest, now I can better see the appeal of using the classes here.
> For instance, in the modified API a backend has to return `:none' in its
> identifier-at-point implementation if not found, because otherwise the
> default implementation will be called (and it's hard to implement the
> overriding otherwise).

Maybe you could switch the roles around: return :not-implemented when a
backend has no implementation and make nil an ordinary return value.

> Suppose we end up using your implementation, do you think there could be
> a reasonable case where a xref-backend-function creates a non-trivial
> instance of its backend class, with instance vars, etc?

A cache or a socket connection to an external indexer could reasonably
be stored in a slot of a backend object.  But that is as far as my
imagination goes.

> otherwise it'll remain an odd wart. Then the variable can just hold the
> class symbol, and instantiation can be performed on-demain, or even each
> time it's used: it's cheap anyway.

Good idea!  Hadn't thought of that.

>> Just want to say that locations should be very flexible.
>
> Right, I can see how EIEIO can be useful in this case, so this is the
> part I mostly left untouched, for now.

Which kinda defeats the argument that API users shouldn't have to know
EIEIO.

> However, it could be simplified:
>
> * I'm pretty sure the default implementation of `xref-location=' will
>   cover 99% of all cases.
>
> * There's no real need for the above and `xref--unique-location'. We can
>   just require the implementations to return lists without duplicates. I
>   would think that most would do that even without being asked.

You're probably right.

> * What is `xref--show-location' for? Why do we want to allow certain
>   types to override this behavior?

xref-bogus-locations had to be handled somehow and using a GF is just as
good as a if/typecase statement.

>   I think `xref--show-xref-buffer' is
>   also suspect at this stage of implementation.

I'm not sure what you mean.

What I can see is that xref--show-xrefs should be have a cleaner arglist
and be part of the API, because there will be language specific commands
that would like to reuse the UI.

> `xref-location-group' is quite neat, though. But depending on whether we
> really want to make this lighter-weight, the class hierarchy here can be
> replaced with some ad-hoc polymorphism (so we could say that a location
> can either be a (buffer position) (file line column), or, say, a closure
> for the most general case).

Using closures as objects is always possible but rarely desirable.

> I kept `etags--xref-read-identifier', but I don't particularly like it:
> in other places, when we want to use a regexp, that's usually a
> different command, or a prefix argument, etc. Inputting quotes to make
> the contents work as a regexp is... odd.

ert-run-tests-interactively uses quotes to distinguish regexps from
symbols.  But I agree: a generalized apropos might be better than
forcing regexps into find-definition.

> The "identifier at point" and "read identifier" makes me wonder how it
> would work with more complex languages, where we delegate a lot of logic
> to an external server process. Will a two-request workflow become usual?
> First ask the server what's at point, and then send it back with a
> specific question. I guess some existing packages would be hard to adapt
> to that.

It seems to me that this situation is similar to a completion-at-point
command that asks an external process for possible completions.
Something that existing packages often do.  Despite that, nobody can
foresee all problems; instead we fix them as they come along.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11  8:09                                               ` Helmut Eller
@ 2014-12-11 11:12                                                 ` Helmut Eller
  2014-12-11 18:36                                                   ` Helmut Eller
  2014-12-11 22:52                                                 ` Dmitry Gutov
  1 sibling, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-11 11:12 UTC (permalink / raw)
  To: emacs-devel

On Thu, Dec 11 2014, Helmut Eller wrote:

>> otherwise it'll remain an odd wart. Then the variable can just hold the
>> class symbol, and instantiation can be performed on-demain, or even each
>> time it's used: it's cheap anyway.
>
> Good idea!  Hadn't thought of that.

Too bad that classes can't be autoloaded.

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11  4:06                                             ` Dmitry Gutov
  2014-12-11  8:09                                               ` Helmut Eller
@ 2014-12-11 15:07                                               ` Stefan Monnier
  2014-12-11 18:43                                                 ` Helmut Eller
                                                                   ` (2 more replies)
  1 sibling, 3 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-12-11 15:07 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: Helmut Eller, emacs-devel

> To be honest, now I can better see the appeal of using the classes here.
> For instance, in the modified API a backend has to return `:none' in its
> identifier-at-point implementation if not found, because otherwise the
> default implementation will be called (and it's hard to implement the
> overriding otherwise).

You don't need :none.
You can do:

   (add-function :around (local xref-backend) #'elisp-xref-backend)

and then in elisp-xref-backend you do:

   (defun elisp-xref-backend (orig action ...)
     (pcase action
       ...
       (_ (apply orig args))))

> +;; For now, make the etags backend the default.
> +(defvar xref-backend #'etags-xref-backend

This should be called `xref-backend-function', to follow usual practice.

> +  "The current xref-backend.
> +This function can be called with different sets of arguments, as
> +described below.
> +
> +Required:
> +
> + (definitions IDENTIFIER): Find definitions of IDENTIFIER.  The
> +result must be a list of `xref--xref' objects.  If no definition
> +can be found, return nil.

Rename xref--xref to xref-xref, since it's clearly part of the API.

> + (references IDENTIFIER): Find references of IDENTIFIER.  The
> +result must be a list of `xref--xref' objects.  If no reference
> +can be found, return nil.
> +
> +Optional:
> +
> + (read-identifier INIT PROMPT): Read an identifier from the
> +minibuffer.  PROMPT is a string used for prompting.  INIT is
> +either an identifier to use as the initial value or nil.
> +
> + (identifier-at-point): Search and return the identfier near
> +point.  If no identifier can be found, return `:none'.
> +
> + (identifier-to-string IDENTIFIER): Return a string representing
> +IDENTIFIER.")

A few comments:
- As discussed earlier, `read-identifier' is probably a bad idea.
  Replace it with a `completion-table' method, so the completing-read is
  not in the backend but in xref.el.
- identifier-at-point should document more clearly that the return value
  may be something else than a string.  AFAIK the only interesting
  non-string non-nil case is to return a special value (e.g. a marker
  or just `t' to stand to (point)) which stands for "let the
  find-definitions code figure out the identifier at that place".
- If we restrict identifier to "a string or nil or t", then we can get
  rid of identifier-to-string.
- The first two methods use sufficiently similar (actual, identical)
  calling conventions, so it's perfectly fine to make them share
  a single backend function, but adding the other methods makes it
  scream "I want to use OO", just like completion-tables do.
  IOW, I think if we don't want to use EIEIO for the `xref-backend' objects,
  then we need to split `xref-backend' into several variables:
  - xref-find-function (can be used both for the `definitions' and for
    the `references').
  - xref-identifier-at-point-function.
  - xref-identifier-completion-table.


-- Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 11:12                                                 ` Helmut Eller
@ 2014-12-11 18:36                                                   ` Helmut Eller
  2014-12-11 19:21                                                     ` David Engster
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-11 18:36 UTC (permalink / raw)
  To: emacs-devel; +Cc: Stefan Monnier

> Too bad that classes can't be autoloaded.

I added this little hack to autoload classes:

  (defvar xref-backend 'etags-xref-backend
    "The class symbol used to create backend instances.
  It should be a subclass of `xref-backend-class'.
  The front-end calls `make-instance' with this symbol as argument.")
  
  (defun xref--maybe-autoload-class (symbol)
    (unless (class-p symbol)
      (let ((f (symbol-function symbol)))
        (when (autoloadp f)
  	(autoload-do-load f symbol)))))
  
  (defun xref--backend ()
    (let ((symbol xref-backend))
      (xref--maybe-autoload-class symbol)
      (make-instance symbol)))

And an autoloadable class is then defined as:

  ;;;###autoload (autoload 'etags-xref-backend "etags")
  (defclass etags-xref-backend (xref-backend-class eieio-singleton) ())

This works because defclass creates a constructor function
with the same name as the classname.

Would this be acceptable, from a style point of view?

Helmut




^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 15:07                                               ` Stefan Monnier
@ 2014-12-11 18:43                                                 ` Helmut Eller
  2014-12-11 20:11                                                   ` Stefan Monnier
  2014-12-12  1:29                                                 ` Stephen Leake
  2014-12-12  5:05                                                 ` Dmitry Gutov
  2 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-11 18:43 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel, Dmitry Gutov

On Thu, Dec 11 2014, Stefan Monnier wrote:

> - If we restrict identifier to "a string or nil or t", then we can get
>   rid of identifier-to-string.

Doesn't look like a great idea to me.  identifier-to-string is used to
create error messages like: "No known definitions for: fooo".  How would
the message look like for the t case?

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 18:36                                                   ` Helmut Eller
@ 2014-12-11 19:21                                                     ` David Engster
  2014-12-11 19:36                                                       ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: David Engster @ 2014-12-11 19:21 UTC (permalink / raw)
  To: Helmut Eller; +Cc: Stefan Monnier, emacs-devel

Helmut Eller writes:
>> Too bad that classes can't be autoloaded.

They can. CEDET does that all the time.

>   ;;;###autoload (autoload 'etags-xref-backend "etags")
>   (defclass etags-xref-backend (xref-backend-class eieio-singleton) ())

Uhm, did you try to simply putting ";;;###autoload" before a class
definition? It should work just fine (through
`eieio-defclass-autoload').

However, there *is* a problem with autoloading classes, namely that
EIEIO should not be loaded at Emacs startup. This is why this feature is
currently restricted to the CEDET subsystem which is only loaded when
you activate Semantic or EDE.

-David



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 19:21                                                     ` David Engster
@ 2014-12-11 19:36                                                       ` Helmut Eller
  2014-12-11 21:53                                                         ` David Engster
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-11 19:36 UTC (permalink / raw)
  To: David Engster; +Cc: Stefan Monnier, emacs-devel

On Thu, Dec 11 2014, David Engster wrote:

> Helmut Eller writes:
>>> Too bad that classes can't be autoloaded.
>
> They can. CEDET does that all the time.

Ah, that's great!

>>   ;;;###autoload (autoload 'etags-xref-backend "etags")
>>   (defclass etags-xref-backend (xref-backend-class eieio-singleton) ())
>
> Uhm, did you try to simply putting ";;;###autoload" before a class
> definition? It should work just fine (through
> `eieio-defclass-autoload').

Yes, I tried but got this error:

  ...
  Loading loaddefs.el (source)...
  Attempt to autoload eieio-defclass-autoload while preparing to dump
  make[1]: *** [emacs] Error 1

> However, there *is* a problem with autoloading classes, namely that
> EIEIO should not be loaded at Emacs startup. This is why this feature is
> currently restricted to the CEDET subsystem which is only loaded when
> you activate Semantic or EDE.

So what's the recommended way to do it for a file in lisp/progmodes/ ?

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 18:43                                                 ` Helmut Eller
@ 2014-12-11 20:11                                                   ` Stefan Monnier
  2014-12-11 20:31                                                     ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-12-11 20:11 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel, Dmitry Gutov

>> - If we restrict identifier to "a string or nil or t", then we can get
>> rid of identifier-to-string.
> Doesn't look like a great idea to me.  identifier-to-string is used to
> create error messages like: "No known definitions for: fooo".  How would
> the message look like for the t case?

The message could look like "No known definition for ident at point".

The use case for `t' is when the backend doesn't even know how to name
the identifier at point.  It just passes the whole file along with the
position of point to some external program which returns the possible
definitions.  So there really might really be no good string
representation, regardless of our API (maybe the external program could
give us some kind of textual representation name for it, but then again
maybe it doesn't support this functionality and maybe this description
would be too long/complex).


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 20:11                                                   ` Stefan Monnier
@ 2014-12-11 20:31                                                     ` Helmut Eller
  2014-12-11 21:33                                                       ` Stefan Monnier
  2014-12-15 17:21                                                       ` Dmitry Gutov
  0 siblings, 2 replies; 172+ messages in thread
From: Helmut Eller @ 2014-12-11 20:31 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel, Dmitry Gutov

On Thu, Dec 11 2014, Stefan Monnier wrote:

>>> - If we restrict identifier to "a string or nil or t", then we can get
>>> rid of identifier-to-string.
>> Doesn't look like a great idea to me.  identifier-to-string is used to
>> create error messages like: "No known definitions for: fooo".  How would
>> the message look like for the t case?
>
> The message could look like "No known definition for ident at point".
>
> The use case for `t' is when the backend doesn't even know how to name
> the identifier at point.  It just passes the whole file along with the
> position of point to some external program which returns the possible
> definitions.  So there really might really be no good string
> representation, regardless of our API (maybe the external program could
> give us some kind of textual representation name for it, but then again
> maybe it doesn't support this functionality and maybe this description
> would be too long/complex).

A identifier representation like 

 (list (thing-at-point 'line) (current-buffer) (point))

doesn't cost anything and would go a long way to give a better error
message.  If thing-at-point returns nil or an empty line then
identifier-at-point should just return nil.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 20:31                                                     ` Helmut Eller
@ 2014-12-11 21:33                                                       ` Stefan Monnier
  2014-12-15 17:21                                                       ` Dmitry Gutov
  1 sibling, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-12-11 21:33 UTC (permalink / raw)
  To: Helmut Eller; +Cc: Dmitry Gutov, emacs-devel

> A identifier representation like 
>  (list (thing-at-point 'line) (current-buffer) (point))

If identifier-at-point returns t you can use the above as
an approximation if you need to generate a message for it, but it won't
be much more helpful to the user.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 19:36                                                       ` Helmut Eller
@ 2014-12-11 21:53                                                         ` David Engster
  2014-12-11 22:04                                                           ` David Engster
  0 siblings, 1 reply; 172+ messages in thread
From: David Engster @ 2014-12-11 21:53 UTC (permalink / raw)
  To: Helmut Eller; +Cc: Stefan Monnier, emacs-devel

Helmut Eller writes:
> On Thu, Dec 11 2014, David Engster wrote:
>
>> Helmut Eller writes:
>>>> Too bad that classes can't be autoloaded.
>>
>> They can. CEDET does that all the time.
>
> Ah, that's great!
>
>>>   ;;;###autoload (autoload 'etags-xref-backend "etags")
>>>   (defclass etags-xref-backend (xref-backend-class eieio-singleton) ())
>>
>> Uhm, did you try to simply putting ";;;###autoload" before a class
>> definition? It should work just fine (through
>> `eieio-defclass-autoload').
>
> Yes, I tried but got this error:
>
>   ...
>   Loading loaddefs.el (source)...
>   Attempt to autoload eieio-defclass-autoload while preparing to dump
>   make[1]: *** [emacs] Error 1

EIEIO is not available at startup. You have to explicitly 'require' it
before autoloading of classes can work.

>> However, there *is* a problem with autoloading classes, namely that
>> EIEIO should not be loaded at Emacs startup. This is why this feature is
>> currently restricted to the CEDET subsystem which is only loaded when
>> you activate Semantic or EDE.
>
> So what's the recommended way to do it for a file in lisp/progmodes/ ?

The only way is to separate everything that needs EIEIO into a separate
file with its own autoloads. This usually does not make sense for a
small package, though. So really, the best way is to have one central
autoload for your package (usually a major/minor mode).

-David



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 21:53                                                         ` David Engster
@ 2014-12-11 22:04                                                           ` David Engster
  2014-12-12  7:26                                                             ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: David Engster @ 2014-12-11 22:04 UTC (permalink / raw)
  To: Helmut Eller; +Cc: Stefan Monnier, emacs-devel

David Engster writes:
> The only way is to separate everything that needs EIEIO into a separate
> file with its own autoloads.

I just took a quick look at xref and understand a bit better what you're
trying to do. It's not about loading xref but the backends.

Your hack should work for your special case. If you look into what
eieio-defclass-autoload does, you'll see that the general case is more
complicated, because you might need to set up superclasses as well.

If your hack is acceptable is up to Stefan, of course...

-David



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11  8:09                                               ` Helmut Eller
  2014-12-11 11:12                                                 ` Helmut Eller
@ 2014-12-11 22:52                                                 ` Dmitry Gutov
  2014-12-11 23:55                                                   ` Stefan Monnier
  1 sibling, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-11 22:52 UTC (permalink / raw)
  To: Helmut Eller; +Cc: Stefan Monnier, emacs-devel

On 12/11/2014 10:09 AM, Helmut Eller wrote:

> Looks pleasantly non-verbose.

Thanks, that was one of the goals.

> Maybe you could switch the roles around: return :not-implemented when a
> backend has no implementation and make nil an ordinary return value.

That's an option. But if we require that for every action, it'll 
increase verbosity. We only care about the distinction between nil and 
"nothing" in `identifier-at-point', so far, so that would be suboptimal too.

If we have a separate identifier-at-point-function, like Stefan wrote in 
the other message, this problem largely disappears. Kind obvious, in 
hindsight.

>> otherwise it'll remain an odd wart. Then the variable can just hold the
>> class symbol, and instantiation can be performed on-demain, or even each
>> time it's used: it's cheap anyway.
>
> Good idea!  Hadn't thought of that.
> ...
 > Too bad that classes can't be autoloaded.

Right. I don't have a better suggestion here, sorry.

>> Right, I can see how EIEIO can be useful in this case, so this is the
>> part I mostly left untouched, for now.
>
> Which kinda defeats the argument that API users shouldn't have to know
> EIEIO.

Not necessarily. The user-facing part of the API is plain functions now, 
and they can also use the xref-make-*-location function without really 
caring what they return. It's only when a user wants to add new location 
types they will really need to use the EIEIO API.

But of course, it would have to be loaded at runtime.

>> * What is `xref--show-location' for? Why do we want to allow certain
>>    types to override this behavior?
>
> xref-bogus-locations had to be handled somehow and using a GF is just as
> good as a if/typecase statement.

I think that gives it too much responsibility. What do we want from a 
location? Chiefly, a buffer-position pair.

* We don't need separate location-buffer and location-position 
accessors. Let's just have a method that returns a cons.

* Bogus locations will return nil and show a message. Or maybe signal an 
error, if you like. We can define a special kind of error, 
`xref--show-location' (a plain function) will catch it and display the 
error message, if it's so inclined. Another option is a special return 
value, like (:error . "Boo hoo").

Together with xref-location-group, this leaves just two methods. And 
without that one, it could be implemented as something like a future, I 
guess.

>>    I think `xref--show-xref-buffer' is
>>    also suspect at this stage of implementation.
>
> I'm not sure what you mean.

I mean it's fine as a possibility, but we don't have any other 
implementations, and thus don't really know what API they'd require.

If it were up to me, I'd remove it, to maybe bring back later, after the 
presently discussed feature is settled, and Stephen has experimented 
with a compilation-mode implementation.

> What I can see is that xref--show-xrefs should be have a cleaner arglist
> and be part of the API, because there will be language specific commands
> that would like to reuse the UI.

Ok, maybe. It should provide some definite benefit, though, over just 
doing (funcall xref-show-xrefs-function xrefs).

> Using closures as objects is always possible but rarely desirable.

Instead of a (usually opaque) closure, it could be a struct with a 
function reference (usually symbol) and an arguments list, which would 
be passed to it together with some changing argument. That would be 
quite transparent.

But anyway, I don't mind using EIEIO either.

> ert-run-tests-interactively uses quotes to distinguish regexps from
> symbols.  But I agree: a generalized apropos might be better than
> forcing regexps into find-definition.

I guess that would just mean another action for the 
xref-backend-function, in terms of the API?

> It seems to me that this situation is similar to a completion-at-point
> command that asks an external process for possible completions.
> Something that existing packages often do.  Despite that, nobody can
> foresee all problems; instead we fix them as they come along.

Similar, yes. With completion-at-point, the backend sees the current 
buffer and point, but also the completion table can determine where in 
the buffer the current "prefix" begins, which is usually important for 
external tools.

If the identifier-at-point is just a string (or some value that can be 
derived from a string), that would be limiting.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 22:52                                                 ` Dmitry Gutov
@ 2014-12-11 23:55                                                   ` Stefan Monnier
  2014-12-11 23:59                                                     ` Dmitry Gutov
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-12-11 23:55 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: Helmut Eller, emacs-devel

> * We don't need separate location-buffer and location-position
> accessors. Let's just have a method that returns a cons.

Why not a marker?


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 23:55                                                   ` Stefan Monnier
@ 2014-12-11 23:59                                                     ` Dmitry Gutov
  0 siblings, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-11 23:59 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Helmut Eller, emacs-devel

On 12/12/2014 01:55 AM, Stefan Monnier wrote:
>> * We don't need separate location-buffer and location-position
>> accessors. Let's just have a method that returns a cons.
>
> Why not a marker?

Sure, sounds good.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-10 19:06                                                 ` Stefan Monnier
@ 2014-12-12  1:03                                                   ` Stephen Leake
  0 siblings, 0 replies; 172+ messages in thread
From: Stephen Leake @ 2014-12-12  1:03 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@IRO.UMontreal.CA> writes:

>> On the other hand, the required code is three lines:
>
>> (defclass xref-ada-backend (xref-backend-class) ())
>> (defvar xref--ada-backend (make-instance 'xref-ada-backend))
>> (defun xref-ada-backend-function () xref--ada-backend)
>
> You also need to define the corresponding methods.  IOW, all the visible
> part of the API relies on EIEIO.

Yes.

Sigh, I seem to be particularly dense about this.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 15:07                                               ` Stefan Monnier
  2014-12-11 18:43                                                 ` Helmut Eller
@ 2014-12-12  1:29                                                 ` Stephen Leake
  2014-12-12  3:05                                                   ` Stefan Monnier
  2014-12-12  5:05                                                 ` Dmitry Gutov
  2 siblings, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-12-12  1:29 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:


> - identifier-at-point should document more clearly that the return value
>   may be something else than a string.  AFAIK the only interesting
>   non-string non-nil case is to return a special value (e.g. a marker
>   or just `t' to stand to (point)) which stands for "let the
>   find-definitions code figure out the identifier at that place".

In ada-mode, identifier is a class derived from xref-file-location:

(defclass xref-ada-identifier (xref-file-location)
  ((id :type string :initarg :identifier)))

This is because identifiers in Ada are overloaded, and are often just
the last part of the fully qualified namespace name. The backend uses
the cross-reference information output by the compiler; the compiler
does the name resolution.

If identifier is restricted to just the string, ada-mode needs a
guarantee that xref-find-definitions will be called with current-buffer
the same as the buffer containing the string, and point in the same
place, so the backend can determine the file, line, column. That seems
kludgy.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-12  1:29                                                 ` Stephen Leake
@ 2014-12-12  3:05                                                   ` Stefan Monnier
  2014-12-12 11:15                                                     ` Stephen Leake
  2014-12-13  9:56                                                     ` Dmitry Gutov
  0 siblings, 2 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-12-12  3:05 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

> If identifier is restricted to just the string, ada-mode needs a
> guarantee that xref-find-definitions will be called with current-buffer
> the same as the buffer containing the string, and point in the same
> place, so the backend can determine the file, line, column.

That's the idea behind using the value t as an "identifier at point".
It seems like a reasonable constraint on the use of
xref-find-definitions.  But if you can think of a case where it might be
impractical to call xref-find-definitions from the right place, we could
use a marker instead of the value t.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 15:07                                               ` Stefan Monnier
  2014-12-11 18:43                                                 ` Helmut Eller
  2014-12-12  1:29                                                 ` Stephen Leake
@ 2014-12-12  5:05                                                 ` Dmitry Gutov
  2 siblings, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-12  5:05 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Helmut Eller, emacs-devel

On 12/11/2014 05:07 PM, Stefan Monnier wrote:

>     (add-function :around (local xref-backend) #'elisp-xref-backend)

Okay, thanks for the reminder about nadvice.

I guess with this approach to function inheritance, there would be a 
different, bare-bones default value of xref-find-function, because I 
don't think I want to delegate to etags-xref-find from elisp-xref-find 
(and all other backends).

Anyway, having a separate xref-identifier-at-point-function should solve 
this problem in a different way.

> This should be called `xref-backend-function', to follow usual practice.

Right, thanks.

> Rename xref--xref to xref-xref, since it's clearly part of the API.

Or we can keep the class name private, and document that the function 
should return a list of xref values, using `xref-make'.

> A few comments:
> - As discussed earlier, `read-identifier' is probably a bad idea.
>    Replace it with a `completion-table' method, so the completing-read is
>    not in the backend but in xref.el.

Ok, apparently I missed some things in the older thread.

> - identifier-at-point should document more clearly that the return value
>    may be something else than a string.  AFAIK the only interesting
>    non-string non-nil case is to return a special value (e.g. a marker
>    or just `t' to stand to (point)) which stands for "let the
>    find-definitions code figure out the identifier at that place".
> - If we restrict identifier to "a string or nil or t", then we can get
>    rid of identifier-to-string.

All right, let's go with this, until a better idea comes up.

But note that AFAIK 99% of the time the identifier at point and 
prefix+suffix used by completion-at-point will be one and the same. 
Maybe it will make sense to unify these: for example, by naming the 
function identifier-bounds, and requiring it to return the beginning and 
the end of the identifier.

This pair of numbers (or maybe markers) would be useful to both 
find-definition functions that expect a string (just call 
buffer-substring beforehand), as well as those that need a buffer position.

>    a single backend function, but adding the other methods makes it
>    scream "I want to use OO", just like completion-tables do.

I wasn't aware they're considered a bad design already. :) That API is 
complicated, but not just because a function has many calling conventions.

>    then we need to split `xref-backend' into several variables:
>    - xref-find-function (can be used both for the `definitions' and for
>      the `references').
>    - xref-identifier-at-point-function.
>    - xref-identifier-completion-table.

Ok. (I should send the updated patch tonight).



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 22:04                                                           ` David Engster
@ 2014-12-12  7:26                                                             ` Helmut Eller
  0 siblings, 0 replies; 172+ messages in thread
From: Helmut Eller @ 2014-12-12  7:26 UTC (permalink / raw)
  To: David Engster; +Cc: emacs-devel

On Thu, Dec 11 2014, David Engster wrote:

> David Engster writes:
>> The only way is to separate everything that needs EIEIO into a separate
>> file with its own autoloads.
>
> I just took a quick look at xref and understand a bit better what you're
> trying to do. It's not about loading xref but the backends.
>
> Your hack should work for your special case. If you look into what
> eieio-defclass-autoload does, you'll see that the general case is more
> complicated, because you might need to set up superclasses as well.
>
> If your hack is acceptable is up to Stefan, of course...

Thanks for the answers.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-12  3:05                                                   ` Stefan Monnier
@ 2014-12-12 11:15                                                     ` Stephen Leake
  2014-12-12 13:58                                                       ` Stefan Monnier
  2014-12-13  9:56                                                     ` Dmitry Gutov
  1 sibling, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-12-12 11:15 UTC (permalink / raw)
  To: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> If identifier is restricted to just the string, ada-mode needs a
>> guarantee that xref-find-definitions will be called with current-buffer
>> the same as the buffer containing the string, and point in the same
>> place, so the backend can determine the file, line, column.
>
> That's the idea behind using the value t as an "identifier at point".
> It seems like a reasonable constraint on the use of
> xref-find-definitions.  But if you can think of a case where it might be
> impractical to call xref-find-definitions from the right place, we could
> use a marker instead of the value t.

I could live with either `t' or a marker; we can make it more
complicated later if some mode shows up with a real need. A marker is
clearer than just `t', but someone has to be responsible for deleting
the marker.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-12 11:15                                                     ` Stephen Leake
@ 2014-12-12 13:58                                                       ` Stefan Monnier
  0 siblings, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-12-12 13:58 UTC (permalink / raw)
  To: Stephen Leake; +Cc: emacs-devel

> I could live with either `t' or a marker; we can make it more
> complicated later if some mode shows up with a real need. A marker is
> clearer than just `t', but someone has to be responsible for deleting
> the marker.

No, markers are automatically "deleted" by the GC (contrary to overlays).


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-12  3:05                                                   ` Stefan Monnier
  2014-12-12 11:15                                                     ` Stephen Leake
@ 2014-12-13  9:56                                                     ` Dmitry Gutov
  1 sibling, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-13  9:56 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Stephen Leake, Helmut Eller, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1301 bytes --]

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> That's the idea behind using the value t as an "identifier at point".
> It seems like a reasonable constraint on the use of
> xref-find-definitions.

Here's another example why we could reconsider using simply t or marker
as a return value there:

- Suppose, for the current backend, xref-identifier-at-point-function
  never returns strings, as described.

- User calls `xref-find-definitions' with a prefix argument, this
  triggers completing read, but we can't use anything as the initial
  input.

For a popular OOP language, the identifier completion table might
contain entries like "package.Class#method", and if the point is on
"foo.method", and if completion doesn't require prefix matching, having
"method" as the initial input would save the user some typing.

Anyway, updated patch attached. Aside from what was already discussed,
the changes are:

- Added `xref-find-apropos' and its implementation in the Etags backend,
  as a replacement for reading and jumping to a "regexp identifier".

- `xref-identifier-completion-table' ->
  `xref-identifier-table-function'. Apparently, it should be a function
  returning a completion table, for the same reason as why
  `tags-lazy-completion-table' exists. Please correct me if I'm wrong.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Plain function API with EIEIO locations --]
[-- Type: text/x-diff, Size: 27999 bytes --]

diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index ba70f90..b051605 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -227,10 +227,14 @@ Blank lines separate paragraphs.  Semicolons start comments.
 
 \\{emacs-lisp-mode-map}"
   :group 'lisp
+  (defvar xref-find-function)
+  (defvar xref-identifier-table-function)
   (lisp-mode-variables nil nil 'elisp)
   (setq imenu-case-fold-search nil)
   (setq-local eldoc-documentation-function
               #'elisp-eldoc-documentation-function)
+  (setq-local xref-find-function #'elisp-xref-find)
+  (setq-local xref-identifier-table-function #'elisp--xref-identifier-table)
   (add-hook 'completion-at-point-functions
             #'elisp-completion-at-point nil 'local))
 
@@ -426,6 +430,15 @@ It can be quoted, or be inside a quoted form."
             0))
      ((facep sym) (find-definition-noselect sym 'defface)))))
 
+(defvar elisp--identifier-completion-table
+  (apply-partially #'completion-table-with-predicate
+                   obarray
+                   (lambda (sym)
+                     (or (boundp sym)
+                         (fboundp sym)
+                         (symbol-plist sym)))
+                   'strict))
+
 (defun elisp-completion-at-point ()
   "Function used for `completion-at-point-functions' in `emacs-lisp-mode'."
   (with-syntax-table emacs-lisp-mode-syntax-table
@@ -466,13 +479,8 @@ It can be quoted, or be inside a quoted form."
                            :company-docsig #'elisp--company-doc-string
                            :company-location #'elisp--company-location))
                     ((elisp--form-quoted-p beg)
-                     (list nil obarray
-                           ;; Don't include all symbols
-                           ;; (bug#16646).
-                           :predicate (lambda (sym)
-                                        (or (boundp sym)
-                                            (fboundp sym)
-                                            (symbol-plist sym)))
+                     ;; Don't include all symbols (bug#16646).
+                     (list nil elisp--identifier-completion-table
                            :annotation-function
                            (lambda (str) (if (fboundp (intern-soft str)) " <f>"))
                            :company-doc-buffer #'elisp--company-doc-buffer
@@ -548,6 +556,34 @@ It can be quoted, or be inside a quoted form."
 (define-obsolete-function-alias
   'lisp-completion-at-point 'elisp-completion-at-point "25.1")
 
+;;; Xref backend
+
+(declare-function xref-make-buffer-location "xref" (buffer position))
+(declare-function xref-make-bogus-location "xref" (message))
+(declare-function xref-make "xref" (description location))
+
+;; FIXME: unify with `elisp--company-location'.
+(defun elisp-xref-find (action id)
+  (when (eq action 'definitions)
+    (let ((sym (intern-soft id)))
+      (when sym
+        (let ((fun (if (fboundp sym) (elisp--xref-find-type sym nil)))
+              (var (if (boundp sym) (elisp--xref-find-type sym 'defvar))))
+          (remove nil (list fun var)))))))
+
+(defun elisp--xref-find-type (symbol type)
+  (let ((loc (condition-case err
+                 (let ((loc (save-excursion
+                              (find-definition-noselect symbol type))))
+                   (xref-make-buffer-location (car loc) (or (cdr loc) 1)))
+               (error
+                (xref-make-bogus-location (error-message-string err)))))
+        (desc (format "(%s %s)" (or type 'defun) symbol)))
+    (xref-make desc loc)))
+
+(defun elisp--xref-identifier-table ()
+  elisp--identifier-completion-table)
+
 ;;; Elisp Interaction mode
 
 (defvar lisp-interaction-mode-map
diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el
index b89b4cf..37c14a9 100644
--- a/lisp/progmodes/etags.el
+++ b/lisp/progmodes/etags.el
@@ -28,6 +28,7 @@
 
 (require 'ring)
 (require 'button)
+(require 'xref)
 
 ;;;###autoload
 (defvar tags-file-name nil
@@ -182,8 +183,8 @@ Example value:
 		       (sexp :tag "Tags to search")))
   :version "21.1")
 
-(defvar find-tag-marker-ring (make-ring find-tag-marker-ring-length)
-  "Ring of markers which are locations from which \\[find-tag] was invoked.")
+(define-obsolete-variable-alias 'find-tag-marker-ring 'xref--marker-ring
+  "25.1")
 
 (defvar default-tags-table-function nil
   "If non-nil, a function to choose a default tags file for a buffer.
@@ -716,12 +717,10 @@ Returns t if it visits a tags table, or nil if there are no more in the list."
     (while (< i find-tag-marker-ring-length)
       (if (aref (cddr tags-location-ring) i)
 	  (set-marker (aref (cddr tags-location-ring) i) nil))
-      (if (aref (cddr find-tag-marker-ring) i)
-	  (set-marker (aref (cddr find-tag-marker-ring) i) nil))
       (setq i (1+ i))))
+  (xref-clear-marker-stack)
   (setq tags-file-name nil
 	tags-location-ring (make-ring find-tag-marker-ring-length)
-	find-tag-marker-ring (make-ring find-tag-marker-ring-length)
 	tags-table-list nil
 	tags-table-computed-list nil
 	tags-table-computed-list-for nil
@@ -780,6 +779,7 @@ tags table and its (recursively) included tags tables."
 	(quit (message "Tags completion table construction aborted.")
 	      (setq tags-completion-table nil)))))
 
+;;;###autoload
 (defun tags-lazy-completion-table ()
   (let ((buf (current-buffer)))
     (lambda (string pred action)
@@ -898,7 +898,7 @@ See documentation of variable `tags-file-name'."
 	      ;; Run the user's hook.  Do we really want to do this for pop?
 	      (run-hooks 'local-find-tag-hook))))
       ;; Record whence we came.
-      (ring-insert find-tag-marker-ring (point-marker))
+      (xref-push-marker-stack)
       (if (and next-p last-tag)
 	  ;; Find the same table we last used.
 	  (visit-tags-table-buffer 'same)
@@ -954,7 +954,6 @@ See documentation of variable `tags-file-name'."
 	(switch-to-buffer buf)
       (error (pop-to-buffer buf)))
     (goto-char pos)))
-;;;###autoload (define-key esc-map "." 'find-tag)
 
 ;;;###autoload
 (defun find-tag-other-window (tagname &optional next-p regexp-p)
@@ -995,7 +994,6 @@ See documentation of variable `tags-file-name'."
 			;; the window's point from the buffer.
 			(set-window-point (selected-window) tagpoint))
 		      window-point)))
-;;;###autoload (define-key ctl-x-4-map "." 'find-tag-other-window)
 
 ;;;###autoload
 (defun find-tag-other-frame (tagname &optional next-p)
@@ -1020,7 +1018,6 @@ See documentation of variable `tags-file-name'."
   (interactive (find-tag-interactive "Find tag other frame: "))
   (let ((pop-up-frames t))
     (find-tag-other-window tagname next-p)))
-;;;###autoload (define-key ctl-x-5-map "." 'find-tag-other-frame)
 
 ;;;###autoload
 (defun find-tag-regexp (regexp &optional next-p other-window)
@@ -1049,20 +1046,8 @@ See documentation of variable `tags-file-name'."
 ;;;###autoload (define-key esc-map "*" 'pop-tag-mark)
 
 ;;;###autoload
-(defun pop-tag-mark ()
-  "Pop back to where \\[find-tag] was last invoked.
+(defalias 'pop-tag-mark 'xref-pop-marker-stack)
 
-This is distinct from invoking \\[find-tag] with a negative argument
-since that pops a stack of markers at which tags were found, not from
-where they were found."
-  (interactive)
-  (if (ring-empty-p find-tag-marker-ring)
-      (error "No previous locations for find-tag invocation"))
-  (let ((marker (ring-remove find-tag-marker-ring 0)))
-    (switch-to-buffer (or (marker-buffer marker)
-                          (error "The marked buffer has been deleted")))
-    (goto-char (marker-position marker))
-    (set-marker marker nil nil)))
 \f
 (defvar tag-lines-already-matched nil
   "Matches remembered between calls.") ; Doc string: calls to what?
@@ -1859,7 +1844,6 @@ nil, we exit; otherwise we scan the next file."
     (and messaged
 	 (null tags-loop-operate)
 	 (message "Scanning file %s...found" buffer-file-name))))
-;;;###autoload (define-key esc-map "," 'tags-loop-continue)
 
 ;;;###autoload
 (defun tags-search (regexp &optional file-list-form)
@@ -2077,6 +2061,54 @@ for \\[find-tag] (which see)."
       (completion-in-region (car comp-data) (cadr comp-data)
 			    (nth 2 comp-data)
 			    (plist-get (nthcdr 3 comp-data) :predicate)))))
+
+\f
+;;; Xref backend
+
+;; Stop searching if we find more than xref-limit matches, as the xref
+;; infrastracture is not designed to handle very long lists.
+;; Switching to some kind of lazy list might be better, but hopefully
+;; we hit the limit rarely.
+(defconst etags--xref-limit 1000)
+
+;;;###autoload
+(defun etags-xref-find (action id)
+  (pcase action
+    (`definitions (etags--xref-find-definitions id))
+    (`apropos (etags--xref-find-definitions id t))))
+
+(defun etags--xref-find-definitions (pattern &optional regexp?)
+  ;; This emulates the behaviour of `find-tag-in-order' but instead of
+  ;; returning one match at a time all matches are returned as list.
+  ;; NOTE: find-tag-tag-order is typically a buffer-local variable.
+  (let* ((xrefs '())
+         (first-time t)
+         (search-fun (if regexp? #'re-search-forward #'search-forward))
+         (marks (make-hash-table :test 'equal))
+         (case-fold-search (if (memq tags-case-fold-search '(nil t))
+                               tags-case-fold-search
+                             case-fold-search)))
+    (save-excursion
+      (while (visit-tags-table-buffer (not first-time))
+        (setq first-time nil)
+        (dolist (order-fun (cond (regexp? find-tag-regexp-tag-order)
+                                 (t find-tag-tag-order)))
+          (goto-char (point-min))
+          (while (and (funcall search-fun pattern nil t)
+                      (< (hash-table-count marks) etags--xref-limit))
+            (when (funcall order-fun pattern)
+              (beginning-of-line)
+              (cl-destructuring-bind (hint line &rest pos) (etags-snarf-tag)
+                (unless (eq hint t) ; hint==t if we are in a filename line
+                  (let* ((file (file-of-tag))
+                         (mark-key (cons file line)))
+                    (unless (gethash mark-key marks)
+                      (let ((loc (xref-make-file-location
+                                  (expand-file-name file) line 0)))
+                        (push (xref-make hint loc) xrefs)
+                        (puthash mark-key t marks)))))))))))
+    (nreverse xrefs)))
+
 \f
 (provide 'etags)
 
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
new file mode 100644
index 0000000..780c586
--- /dev/null
+++ b/lisp/progmodes/xref.el
@@ -0,0 +1,477 @@
+;; xref.el --- Cross-referencing commands              -*-lexical-binding:t-*-
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a somewhat generic infrastructure for cross
+;; referencing commands, in particular "find-definition".
+;;
+;; Some part of the functionality must be implemented in a language
+;; dependent way and that's done by defining `xref-find-function',
+;; `xref-identifier-at-point-function' and
+;; `xref-identifier-table-function', which see.  A language-specific
+;; mode usually makes these variables buffer-local first.
+;;
+;; For `xref-find-function' calling conventions, see its description.
+;; It has to operate with "xref" and "location" values.
+;;
+;; One would usually call `make-xref' and `xref-make-file-location',
+;; `xref-make-buffer-location' or `xref-make-bogus-location' to create
+;; them.
+;;
+;; For each identifier, we consider that either it has a precise
+;; string representation that's easy to find out (in which case we
+;; operate with a string value), or we use the value t if a background
+;; process is expected to determine it using the buffer contents and
+;; the current position.
+;;
+;; See the functions `etags-xref-find' and `elisp-xref-find' for full
+;; examples.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'eieio)
+(require 'ring)
+
+\f
+;;; Locations
+
+(defclass xref-location () ()
+  :documentation "A location represents a position in a file or buffer.")
+
+;; If a backend decides to subclass xref-location it can provide
+;; methods for some of the following functions:
+(defgeneric xref-location-marker (location)
+  "Return the marker for LOCATION.")
+
+(defgeneric xref-location-group (location)
+  "Return a string used to group a set of locations.
+This is typically the filename.")
+
+;;;; Commonly needed location classes are defined here:
+
+;; FIXME: might be useful to have an optional "hint" i.e. a string to
+;; search for in case the line number is sightly out of date.
+(defclass xref-file-location (xref-location)
+  ((file :type string :initarg :file)
+   (line :type fixnum :initarg :line)
+   (column :type fixnum :initarg :column))
+  :documentation "A file location is a file/line/column triple.
+Line numbers start from 1 and columns from 0.")
+
+(defun xref-make-file-location (file line column)
+  "Create and return a new xref-file-location."
+  (make-instance 'xref-file-location :file file :line line :column column))
+
+(defmethod xref-location-marker ((l xref-file-location))
+  (with-slots (file line column) l
+    (with-current-buffer
+        (or (get-file-buffer file)
+            (let ((find-file-suppress-same-file-warnings t))
+              (find-file-noselect file)))
+      (save-restriction
+        (widen)
+        (save-excursion
+          (goto-char (point-min))
+          (beginning-of-line line)
+          (move-to-column column)
+          (point-marker))))))
+
+(defmethod xref-location-group ((l xref-file-location))
+  (oref l :file))
+
+(defclass xref-buffer-location (xref-location)
+  ((buffer :type buffer :initarg :buffer)
+   (position :type fixnum :initarg :position)))
+
+(defun xref-make-buffer-location (buffer position)
+  "Create and return a new xref-buffer-location."
+  (make-instance 'xref-buffer-location :buffer buffer :position position))
+
+(defmethod xref-location-marker ((l xref-buffer-location))
+  (with-slots (buffer position) l
+    (let ((m (make-marker)))
+      (move-marker m position buffer))))
+
+(defmethod xref-location-group ((l xref-buffer-location))
+  (with-slots (buffer) l
+    (or (buffer-file-name buffer)
+        (format "(buffer %s)" (buffer-name buffer)))))
+
+(defclass xref-bogus-location (xref-location)
+  ((message :type string :initarg :message
+            :reader xref-bogus-location-message))
+  :documentation "Bogus locations are sometimes useful to
+indicate errors, e.g. when we know that a function exists but the
+actual location is not known.")
+
+(defun xref-make-bogus-location (message)
+  "Create and return a new xref-bogus-location."
+  (make-instance 'xref-bogus-location :message message))
+
+(defmethod xref-location-marker ((l xref-bogus-location))
+  (user-error "%s" (oref l :message)))
+
+(defmethod xref-location-group ((_ xref-bogus-location)) "(No location)")
+
+\f
+;;; Cross-reference
+
+(defclass xref--xref ()
+  ((description :type string :initarg :description
+                :reader xref--xref-description)
+   (location :type xref-location :initarg :location
+             :reader xref--xref-location))
+  :comment "An xref is used to display and locate constructs like
+variables or functions.")
+
+(defun xref-make (description location)
+  "Create and return a new xref.
+DESCRIPTION is a short string to describe the xref.
+LOCATION is an `xref-location'."
+  (make-instance 'xref--xref :description description :location location))
+
+\f
+;;; API
+
+;; For now, make the etags backend the default.
+(defvar xref-find-function #'etags-xref-find
+  "Function to look for cross-references.
+It can be called in several ways:
+
+ (definitions IDENTIFIER): Find definitions of IDENTIFIER.  The
+result must be a list of xref objects.  If no definitions can be
+found, return nil.
+
+ (references IDENTIFIER): Find references of IDENTIFIER.  The
+result must be a list of xref objects.  If no references can be
+found, return nil.
+
+ (apropos PATTERN): Find all symbols that match PATTERN.  PATTERN
+is a regexp.
+
+IDENTIFIER can be any non-nil value returned by
+`xref-identifier-at-point-function', or any value in
+`xref-identifier-table-function'.
+
+To create an xref object, call `xref-make'.")
+
+(defvar xref-identifier-at-point-function #'xref-default-identifier-at-point
+  "Function to get the relevant identifier at point.
+
+The return value must be a string, t or nil.  nil means no
+identifier at point found.  t means that there is an identifier
+at point, but its string representation is difficult to obtain.")
+
+(defvar xref-identifier-table-function #'tags-lazy-completion-table
+  "Function that returns the completion table for identifiers.")
+
+(defun xref-default-identifier-at-point ()
+  (let ((thing (thing-at-point 'symbol)))
+    (and thing (substring-no-properties thing))))
+
+\f
+;;; misc utilities
+(defun xref--alistify (list key test)
+  "Partition the elements of LIST into an alist.
+KEY extracts the key from an element and TEST is used to compare
+keys."
+  (let ((alist '()))
+    (dolist (e list)
+      (let* ((k (funcall key e))
+             (probe (cl-assoc k alist :test test)))
+        (if probe
+            (setcdr probe (cons e (cdr probe)))
+          (push (cons k (list e)) alist))))
+    ;; Put them back in order.
+    (cl-loop for (key . value) in (reverse alist)
+             collect (cons key (reverse value)))))
+
+(defun xref--insert-propertized (props &rest strings)
+  "Insert STRINGS with text properties PROPS."
+  (let ((start (point)))
+    (apply #'insert strings)
+    (add-text-properties start (point) props)))
+
+(defun xref--search-property (property &optional backward)
+    "Search the next text range where text property PROPERTY is non-nil.
+Return the value of PROPERTY.  If BACKWARD is non-nil, search
+backward."
+  (let ((next (if backward
+                  #'previous-single-char-property-change
+                #'next-single-char-property-change))
+        (start (point))
+        (value nil))
+    (while (progn
+             (goto-char (funcall next (point) property))
+             (not (or (setq value (get-text-property (point) property))
+                      (eobp)
+                      (bobp)))))
+    (cond (value)
+          (t (goto-char start) nil))))
+
+\f
+;;; Marker stack  (M-. pushes, M-, pops)
+
+(defconst xref--marker-ring-length 16)
+
+(defvar xref--marker-ring (make-ring xref--marker-ring-length)
+  "Ring of markers to implement the marker stack.")
+
+(defun xref-push-marker-stack ()
+  "Add point to the marker stack."
+  (ring-insert xref--marker-ring (point-marker)))
+
+;;;###autoload
+(defun xref-pop-marker-stack ()
+  "Pop back to where \\[xref-find-definitions] was last invoked."
+  (interactive)
+  (let ((ring xref--marker-ring))
+    (when (ring-empty-p ring)
+      (error "Marker stack is empty"))
+    (let ((marker (ring-remove ring 0)))
+      (switch-to-buffer (or (marker-buffer marker)
+                            (error "The marked buffer has been deleted")))
+      (goto-char (marker-position marker))
+      (set-marker marker nil nil))))
+
+;; etags.el needs this
+(defun xref-clear-marker-stack ()
+  "Discard all markers from the marker stack."
+  (let ((ring xref--marker-ring))
+    (while (not (ring-empty-p ring))
+      (let ((marker (ring-remove ring)))
+        (set-marker marker nil nil)))))
+
+\f
+(defun xref--goto-location (location)
+  "Set buffer and point according to xref-location LOCATION."
+  (let ((marker (xref-location-marker location)))
+    (set-buffer (marker-buffer marker))
+    (cond ((and (<= (point-min) marker) (<= marker (point-max))))
+          (widen-automatically (widen))
+          (t (error "Location is outside accessible part of buffer")))
+    (goto-char marker)))
+
+(defun xref--pop-to-location (location &optional window)
+  "Goto xref-location LOCATION and display the buffer.
+WINDOW controls how the buffer is displayed:
+  nil      -- switch-to-buffer
+  'window  -- pop-to-buffer (other window)
+  'frame   -- pop-to-buffer (other frame)"
+  (xref--goto-location location)
+  (cl-ecase window
+    ((nil)  (switch-to-buffer (current-buffer)))
+    (window (pop-to-buffer (current-buffer) t))
+    (frame  (let ((pop-up-frames t)) (pop-to-buffer (current-buffer) t)))))
+
+\f
+;;; XREF buffer (part of the UI)
+
+;; The xref buffer is used to display a set of xrefs.
+
+(defun xref--display-position (pos other-window recenter-arg)
+  ;; show the location, but don't hijack focus.
+  (with-selected-window (display-buffer (current-buffer) other-window)
+    (goto-char pos)
+    (recenter recenter-arg)))
+
+(defun xref--show-location (location)
+  (condition-case err
+      (progn
+        (xref--goto-location location)
+        (xref--display-position (point) t 1))
+    (user-error (message (error-message-string err)))))
+
+(defun xref--next-line (backward)
+  (let ((loc (xref--search-property 'xref-location backward)))
+    (when loc
+      (xref--show-location loc))))
+
+(defun xref-next-line ()
+  "Move to the next xref and display its source in the other window."
+  (interactive)
+  (xref--next-line nil))
+
+(defun xref-prev-line ()
+  "Move to the previous xref and display its source in the other window."
+  (interactive)
+  (xref--next-line t))
+
+(defun xref--location-at-point ()
+  (or (get-text-property (point) 'xref-location)
+      (error "No reference at point")))
+
+(defun xref-goto-xref ()
+  "Jump to the xref at point and close the xref buffer."
+  (interactive)
+  (xref--show-location (xref--location-at-point))
+  (quit-window))
+
+(define-derived-mode xref--xref-buffer-mode fundamental-mode "XREF"
+  "Mode for displaying cross-refenences."
+  (setq buffer-read-only t))
+
+(let ((map xref--xref-buffer-mode-map))
+  (define-key map (kbd "q") #'quit-window)
+  (define-key map [remap next-line] #'xref-next-line)
+  (define-key map [remap previous-line] #'xref-prev-line)
+  (define-key map (kbd "RET") #'xref-goto-xref)
+
+  ;; suggested by Johan Claesson "to further reduce finger movement":
+  (define-key map (kbd ".") #'xref-next-line)
+  (define-key map (kbd ",") #'xref-prev-line))
+
+(defun xref--buffer-name () "*xref*")
+
+(defun xref--insert-xrefs (xref-alist)
+  "Insert XREF-ALIST in the current-buffer.
+XREF-ALIST is of the form ((GROUP . (XREF ...)) ...).  Where
+GROUP is a string for decoration purposes and XREF is an
+`xref--xref' object."
+  (cl-loop for ((group . xrefs) . more1) on xref-alist do
+           (xref--insert-propertized '(face bold) group "\n")
+           (cl-loop for (xref . more2) on xrefs do
+                    (insert "  ")
+                    (with-slots (description location) xref
+                      (xref--insert-propertized
+                       (list 'xref-location location
+                             'face 'font-lock-keyword-face)
+                       description))
+                    (when (or more1 more2)
+                      (insert "\n")))))
+
+(defun xref--analyze (xrefs)
+  "Find common filenames in XREFS.
+Return an alist of the form ((FILENAME . (XREF ...)) ...)."
+  (xref--alistify xrefs
+                  (lambda (x)
+                    (xref-location-group (xref--xref-location x)))
+                  #'equal))
+
+(defun xref--show-xref-buffer (xrefs)
+  (let ((xref-alist (xref--analyze xrefs)))
+    (with-current-buffer (get-buffer-create (xref--buffer-name))
+      (let ((inhibit-read-only t))
+        (erase-buffer)
+        (xref--insert-xrefs xref-alist)
+        (xref--xref-buffer-mode)
+        (pop-to-buffer (current-buffer))
+        (goto-char (point-min))
+        (current-buffer)))))
+
+\f
+;; This part of the UI seems fairly uncontroversial: it reads the
+;; identifier and deals with the single definition case.
+;;
+;; The controversial multiple definitions case is handed off to
+;; xref-show-xrefs-function.
+
+(defvar xref-show-xrefs-function 'xref--show-xref-buffer
+  "Function to display a list of xrefs.")
+
+(defun xref--show-xrefs (id kind xrefs window)
+  (cond
+   ((null xrefs)
+    (if (eq id t)
+        (error "No known %s for the identifier at point" kind)
+      (error "No known %s for: %s" kind id)))
+   ((not (cdr xrefs))
+    (xref-push-marker-stack)
+    (xref--pop-to-location (xref--xref-location (car xrefs)) window))
+   (t
+    (xref-push-marker-stack)
+    (funcall xref-show-xrefs-function xrefs))))
+
+(defun xref--read-identifier (prompt)
+  "Return the identifier at point or read it from the minibuffer."
+  (let ((id (funcall xref-identifier-at-point-function)))
+    (cond ((or current-prefix-arg (not id))
+           (completing-read prompt (funcall xref-identifier-table-function)
+                            nil t (unless (eq id t) id)))
+          (t id))))
+
+\f
+;;; Commands
+
+(defun xref--find-definitions (id window)
+  (xref--show-xrefs id "definitions"
+                    (funcall xref-find-function 'definitions id)
+                    window))
+
+;;;###autoload
+(defun xref-find-definitions (identifier)
+  "Find the definition of the identifier at point.
+With prefix argument, prompt for the identifier."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier nil))
+
+;;;###autoload
+(defun xref-find-definitions-other-window (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'window))
+
+;;;###autoload
+(defun xref-find-definitions-other-frame (identifier)
+  "Like `xref-find-definitions' but switch to the other window."
+  (interactive (list (xref--read-identifier "Find definitions of: ")))
+  (xref--find-definitions identifier 'frame))
+
+;;;###autoload
+(defun xref-find-references (identifier)
+  "Find references to the identifier at point.
+With prefix argument, prompt for the identifier."
+  (interactive (list (xref--read-identifier "Find references of: ")))
+  (xref--show-xrefs identifier "references"
+                    (funcall xref-find-function 'references identifier)
+                    nil))
+
+;;;###autoload
+(defun xref-find-apropos (pattern)
+  "Find all meaningful symbols that match PATTERN.
+The argument has the same meaning as in `apropos'."
+  (interactive (list (read-from-minibuffer
+                      "Search for pattern (word list or regexp): ")))
+  (require 'apropos)
+  (xref--show-xrefs pattern "apropos"
+                    (funcall xref-find-function 'apropos
+                             (apropos-parse-pattern
+                              (if (string-equal (regexp-quote pattern) pattern)
+                                  ;; Split into words
+                                  (or (split-string pattern "[ \t]+" t)
+                                      (user-error "No word list given"))
+                                pattern)))
+                    nil))
+
+\f
+;;; Key bindings
+
+;;;###autoload
+(progn
+  (define-key esc-map "." #'xref-find-definitions)
+  (define-key esc-map "," #'xref-pop-marker-stack)
+  (define-key ctl-x-4-map "." #'xref-find-definitions-other-window)
+  (define-key ctl-x-5-map "." #'xref-find-definitions-other-frame))
+
+\f
+(provide 'xref)
+
+;;; xref.el ends here

^ permalink raw reply related	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-11 20:31                                                     ` Helmut Eller
  2014-12-11 21:33                                                       ` Stefan Monnier
@ 2014-12-15 17:21                                                       ` Dmitry Gutov
  2014-12-15 21:13                                                         ` Stefan Monnier
  2014-12-15 21:57                                                         ` Helmut Eller
  1 sibling, 2 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-15 17:21 UTC (permalink / raw)
  To: Helmut Eller; +Cc: Stefan Monnier, emacs-devel

Helmut Eller <eller.helmut@gmail.com> writes:

> A identifier representation like 
>
>  (list (thing-at-point 'line) (current-buffer) (point))
>
> doesn't cost anything and would go a long way to give a better error
> message.  If thing-at-point returns nil or an empty line then
> identifier-at-point should just return nil.

Bearing in mind the example I mentioned in the previous email, maybe we
can add an optional argument to the xref-identifier-at-point-function.

When it's t, the function would return an approximate string (basically
symbol at point, but maybe with minor syntax adjustments), and that
value would be used both in the error message (when we can't find
anything), and as initial input for identifier completion, from
xref-identifier-completion-table.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-15 17:21                                                       ` Dmitry Gutov
@ 2014-12-15 21:13                                                         ` Stefan Monnier
  2014-12-15 21:24                                                           ` Dmitry Gutov
  2014-12-15 21:57                                                         ` Helmut Eller
  1 sibling, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-12-15 21:13 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: Helmut Eller, emacs-devel

> When it's t, the function would return an approximate string (basically
> symbol at point, but maybe with minor syntax adjustments), and that
> value would be used both in the error message (when we can't find
> anything), and as initial input for identifier completion, from
> xref-identifier-completion-table.

I don't care about which values are really allowed as identifiers.
I just the docstring to say clearly that non-strings can be used.
And I'd like to get rid of the "identifier->string" method because
I think it's over-engineering (it's only used to give
very-slightly-prettier error messages).


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-15 21:13                                                         ` Stefan Monnier
@ 2014-12-15 21:24                                                           ` Dmitry Gutov
  0 siblings, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-15 21:24 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Helmut Eller, emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> When it's t, the function would return an approximate string (basically
>> symbol at point, but maybe with minor syntax adjustments), and that
>> value would be used both in the error message (when we can't find
>> anything), and as initial input for identifier completion, from
>> xref-identifier-completion-table.
>
> I don't care about which values are really allowed as identifiers.
> I just the docstring to say clearly that non-strings can be used.
> And I'd like to get rid of the "identifier->string" method because
> I think it's over-engineering (it's only used to give
> very-slightly-prettier error messages).

The message you're responding to assumes familiarity with the patch,
which, I think, already addresses both your points (and identifiers can
only be strings, nil or t, like you suggested before).



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-15 17:21                                                       ` Dmitry Gutov
  2014-12-15 21:13                                                         ` Stefan Monnier
@ 2014-12-15 21:57                                                         ` Helmut Eller
  2014-12-15 22:06                                                           ` Dmitry Gutov
  1 sibling, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-15 21:57 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: emacs-devel

On Mon, Dec 15 2014, Dmitry Gutov wrote:

> Helmut Eller <eller.helmut@gmail.com> writes:
>
>> A identifier representation like 
>>
>>  (list (thing-at-point 'line) (current-buffer) (point))
>>
>> doesn't cost anything and would go a long way to give a better error
>> message.  If thing-at-point returns nil or an empty line then
>> identifier-at-point should just return nil.
>
> Bearing in mind the example I mentioned in the previous email, maybe we
> can add an optional argument to the xref-identifier-at-point-function.

You could also further restrict the identifier type: allow only strings.
Tell users that they can put text properties on the string if they need
more structured data.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-15 21:57                                                         ` Helmut Eller
@ 2014-12-15 22:06                                                           ` Dmitry Gutov
  2014-12-15 22:17                                                             ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-15 22:06 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On 12/15/2014 11:57 PM, Helmut Eller wrote:

> You could also further restrict the identifier type: allow only strings.

Already did. :) Have you looked at the patch?

> Tell users that they can put text properties on the string if they need
> more structured data.

Right, that will also be a nice escape hatch. Do you think it obviates 
my proposal of returning precise or imprecise identifiers, depending on 
the argument?



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-15 22:06                                                           ` Dmitry Gutov
@ 2014-12-15 22:17                                                             ` Helmut Eller
  2014-12-15 22:26                                                               ` Dmitry Gutov
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-15 22:17 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: emacs-devel

On Tue, Dec 16 2014, Dmitry Gutov wrote:

> On 12/15/2014 11:57 PM, Helmut Eller wrote:
>
>> You could also further restrict the identifier type: allow only strings.
>
> Already did. :) Have you looked at the patch?

Apparently I missed that.  The last patch I looked at had a number of
(eq id t) tests.

>> Tell users that they can put text properties on the string if they need
>> more structured data.
>
> Right, that will also be a nice escape hatch. Do you think it obviates
> my proposal of returning precise or imprecise identifiers, depending
> on the argument?

There's still the problem of how to put text properties on the result of
completing-read.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-15 22:17                                                             ` Helmut Eller
@ 2014-12-15 22:26                                                               ` Dmitry Gutov
  2014-12-15 22:41                                                                 ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-15 22:26 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On 12/16/2014 12:17 AM, Helmut Eller wrote:

> Apparently I missed that.  The last patch I looked at had a number of
> (eq id t) tests.

Ok, sorry. I misunderstood. So, only strings? That's not bad, the string 
can be that "imprecise" value, and a property will signal that the 
backend will have to find out what the identifier is, maybe using the 
value of that property (likely marker), or the current value of point.

> There's still the problem of how to put text properties on the result of
> completing-read.

Do we really need to? I think the identifier-completion-table can manage 
to contain only unambiguous entries.

Even if we could carry text properties through `completing-read', the 
user won't see them when choosing, so all strings in the table will have 
to be different anyway.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-15 22:26                                                               ` Dmitry Gutov
@ 2014-12-15 22:41                                                                 ` Helmut Eller
  2014-12-15 22:54                                                                   ` Dmitry Gutov
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-15 22:41 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: emacs-devel

On Tue, Dec 16 2014, Dmitry Gutov wrote:

> Ok, sorry. I misunderstood. So, only strings? That's not bad, the
> string can be that "imprecise" value, and a property will signal that
> the backend will have to find out what the identifier is, maybe using
> the value of that property (likely marker), or the current value of
> point.

Yes, that's the idea.

>> There's still the problem of how to put text properties on the result of
>> completing-read.
>
> Do we really need to? I think the identifier-completion-table can
> manage to contain only unambiguous entries.
>
> Even if we could carry text properties through `completing-read', the
> user won't see them when choosing, so all strings in the table will
> have to be different anyway.

What I'm saying is that my original proposal of having a
read-identifier-form-minibuffer make sense as it allows backends to add
text properties more easily than a completion table.  And calling
completing-read isn't difficult for the backends if they already have
the completion table.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-15 22:41                                                                 ` Helmut Eller
@ 2014-12-15 22:54                                                                   ` Dmitry Gutov
  2014-12-15 23:03                                                                     ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-15 22:54 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On 12/16/2014 12:41 AM, Helmut Eller wrote:
>> Even if we could carry text properties through `completing-read', the
>> user won't see them when choosing, so all strings in the table will
>> have to be different anyway.
>
> What I'm saying is that my original proposal of having a
> read-identifier-form-minibuffer make sense as it allows backends to add
> text properties more easily than a completion table.

And I'm asking, why do we need text properties there?

For example, for a hypothetical Ruby backend, the completion table will 
return fully qualified identifiers, like String#gsub, 
ActiveRecord::Base.first, or Hash#[]. A string like this can be send to 
the external process, and it'll return the source location, docstring, 
etc, without any additional data.

 > And calling
> completing-read isn't difficult for the backends if they already have
> the completion table.

That's a lot of responsibility we don't necessarily have to give them. 
And even though the method is called `read-identifier-from-minibuffer', 
what's stopping them from using any other possible interface to get the 
input from the user?

Rather than allow that, I'd rather we improve the input interface in 
centralized fashion, and ask the backends only for possible values.

So when we can read input in the middle of the frame in a nice popup 
(any day now, I'm sure), all backends will benefit.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-15 22:54                                                                   ` Dmitry Gutov
@ 2014-12-15 23:03                                                                     ` Helmut Eller
       [not found]                                                                       ` <54901FEB.1090704@yandex.ru>
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-15 23:03 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: emacs-devel

On Tue, Dec 16 2014, Dmitry Gutov wrote:

> On 12/16/2014 12:41 AM, Helmut Eller wrote:
>>> Even if we could carry text properties through `completing-read', the
>>> user won't see them when choosing, so all strings in the table will
>>> have to be different anyway.
>>
>> What I'm saying is that my original proposal of having a
>> read-identifier-form-minibuffer make sense as it allows backends to add
>> text properties more easily than a completion table.
>
> And I'm asking, why do we need text properties there?

Because it feels right.

>> And calling
>> completing-read isn't difficult for the backends if they already have
>> the completion table.
>
> That's a lot of responsibility we don't necessarily have to give
> them. And even though the method is called
> `read-identifier-from-minibuffer', what's stopping them from using any
> other possible interface to get the input from the user?
>
> Rather than allow that, I'd rather we improve the input interface in
> centralized fashion, and ask the backends only for possible values.

>
> So when we can read input in the middle of the frame in a nice popup
> (any day now, I'm sure), all backends will benefit.

Feel free to disagree.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
       [not found]                                                                             ` <m2y4q75ntx.fsf@gmail.com>
@ 2014-12-16 21:40                                                                               ` Dmitry Gutov
  2014-12-17  7:25                                                                                 ` Helmut Eller
  0 siblings, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-16 21:40 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On 12/16/2014 10:42 PM, Helmut Eller wrote:
> Not everything needs to be entered by the user.  The namespace part
> could comes from the buffer (inferred)

But what if they want to jump to a symbol defined in a different 
package/namespace?

I would imagine that jumping to a symbol in the current package would be 
relatively infrequent (in one-file-per-class languages), or at least not 
as important (in other languages, where Imenu probably covers that).

Admittedly, if the language requires all used external symbols (classes, 
or functions) to be declared at the top of the file, for all symbols 
mentioned in the current file "jump to definition" could just be based 
on the local name (maybe imported alias) and the name of the current 
unit of computation (package or class, like in Java), but that discounts 
all symbols that aren't referenced in the current file.

I think allowing to jump to an arbitrary symbol (not just the one near 
point, or even in the same package) is the main goal for the identifier 
completion table.

 > and the user shouldn't be forced to type fully qualified names.

Ideally, right. We want to offer the user defaults for both the package 
part (current), and the symbol name (symbol at point), both of which the 
user could override conveniently.

But maybe offering current.package/symbol-at-point as one default value, 
in one completing-read invocation, would be good enough?

The benefit of a backend being able to decide to call `completing-read' 
twice is being able to cleanly offer these two defaults, separately.

The drawback: personally, I can't really choose which part to prompt for 
first. The best result probably depends on whether the user is looking 
for a symbol (and isn't sure about the package), or knows the package 
and wants to explore the symbols it it.

If we're completing both a the same time, the user can choose which part 
to edit, with completion. I'm not sure, though, if the backend would be 
able to actually filter completions for the first part (package) based 
on the text of the second part (symbol name) thus far entered. But 
that's an implementation detail.

To recount, here are arguments in favor of having identifier completion 
table as a part of the backend API, from the older discussion:

- Partial-completion style will let you complete "js.fo" to anything 
that matches "js*.fo*".

- It means the UI can use it for various kinds of completion (e.g. for
completion-at-point-function).

- ...the general rule that the backend should not do UI tasks and 
prompting is definitely a UI operation.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-16 21:40                                                                               ` Dmitry Gutov
@ 2014-12-17  7:25                                                                                 ` Helmut Eller
  2014-12-19  8:00                                                                                   ` Dmitry Gutov
  0 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-17  7:25 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: emacs-devel

On Tue, Dec 16 2014, Dmitry Gutov wrote:

2> On 12/16/2014 10:42 PM, Helmut Eller wrote:
>> Not everything needs to be entered by the user.  The namespace part
>> could comes from the buffer (inferred)
>
> But what if they want to jump to a symbol defined in a different
> package/namespace?

Then there's no other choice than to use a qualified name.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-17  7:25                                                                                 ` Helmut Eller
@ 2014-12-19  8:00                                                                                   ` Dmitry Gutov
  2014-12-19  8:49                                                                                     ` Helmut Eller
                                                                                                       ` (2 more replies)
  0 siblings, 3 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-19  8:00 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel, Stephen Leake, Stefan Monnier, Jorgen Schaefer

Helmut Eller <eller.helmut@gmail.com> writes:

>> But what if they want to jump to a symbol defined in a different
>> package/namespace?
>
> Then there's no other choice than to use a qualified name.

Okay.

Anyway, the identifier table can just as well include local names (and
aliases), all of which `xref-find-function' should be able to resolve
with no problem because it's called in the same buffer.

And strings in completion tables can include properties, too.

I've pushed the patch with some updates to
http://git.savannah.gnu.org/cgit/emacs.git/log/?h=scratch/xref, please
everyone take a look.

If there are no further objections, I'll install it in a few days.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-19  8:00                                                                                   ` Dmitry Gutov
@ 2014-12-19  8:49                                                                                     ` Helmut Eller
  2014-12-19 14:34                                                                                       ` Dmitry Gutov
  2014-12-19  8:56                                                                                     ` Helmut Eller
  2014-12-25 20:25                                                                                     ` Dmitry Gutov
  2 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-19  8:49 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: emacs-devel

On Fri, Dec 19 2014, Dmitry Gutov wrote:

> I've pushed the patch with some updates to
> http://git.savannah.gnu.org/cgit/emacs.git/log/?h=scratch/xref, please
> everyone take a look.

xref-goto-xref should be rewritten as:

(defun xref-goto-xref ()
  "Jump to the xref at point and close the xref buffer."
  (interactive)
  (let ((loc (xref--location-at-point)))
    (quit-window)
    (xref--pop-to-location loc)))

as the old version could quit the wrong window.

I would also add this to make the next-error command work:

(defun xref--next-error-function (n reset?)
  (when reset?
    (goto-char (point-min)))
  (let ((backward (< n 0))
	(n (abs n))
	(loc nil))
    (dotimes (_ n)
      (setq loc (xref--search-property 'xref-location backward)))
    (cond (loc
	   (xref--display-position (point) t 0)
	   (xref--pop-to-location loc))
	  (t
	   (error "No %s xref" (if backward "previous" "next"))))))

(define-derived-mode xref--xref-buffer-mode fundamental-mode "XREF"
  "Mode for displaying cross refenences."
  (setq buffer-read-only t)
  (setq next-error-function #'xref--next-error-function)
  (setq next-error-last-buffer (current-buffer)))

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-19  8:00                                                                                   ` Dmitry Gutov
  2014-12-19  8:49                                                                                     ` Helmut Eller
@ 2014-12-19  8:56                                                                                     ` Helmut Eller
  2014-12-19 13:36                                                                                       ` Dmitry Gutov
  2014-12-25 20:25                                                                                     ` Dmitry Gutov
  2 siblings, 1 reply; 172+ messages in thread
From: Helmut Eller @ 2014-12-19  8:56 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: emacs-devel

On Fri, Dec 19 2014, Dmitry Gutov wrote:

> I've pushed the patch with some updates to
> http://git.savannah.gnu.org/cgit/emacs.git/log/?h=scratch/xref, please
> everyone take a look.

in elisp-mode.el I would use

(eval-when-compile
  (require 'xref))

instead of declare-function.

Helmut



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-19  8:56                                                                                     ` Helmut Eller
@ 2014-12-19 13:36                                                                                       ` Dmitry Gutov
  0 siblings, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-19 13:36 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On 12/19/2014 10:56 AM, Helmut Eller wrote:

> (eval-when-compile
>    (require 'xref))
>
> instead of declare-function.

If I do that, the byte-compiler still complains that these functions 
"might not be defined at runtime".



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-19  8:49                                                                                     ` Helmut Eller
@ 2014-12-19 14:34                                                                                       ` Dmitry Gutov
  0 siblings, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-19 14:34 UTC (permalink / raw)
  To: Helmut Eller; +Cc: emacs-devel

On 12/19/2014 10:49 AM, Helmut Eller wrote:

 > xref-goto-xref should be rewritten as:

Ok, thanks.

> (define-derived-mode xref--xref-buffer-mode fundamental-mode "XREF"
>    "Mode for displaying cross refenences."
>    (setq buffer-read-only t)
>    (setq next-error-function #'xref--next-error-function)
>    (setq next-error-last-buffer (current-buffer)))

This looks dubious to me. What if I already have a compilation buffer 
open, and I'm using `xref-find-definitions' to follow the implementation 
of some code near one of the errors?

This seems like it'll break the error chain I'm currently following.

Also, when I'm using xref-find-definitions (as opposed to the other 
xref-find- functions), I'm very unlikely to want to visit all results. 
It's usually just one that I'm after.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-19  8:00                                                                                   ` Dmitry Gutov
  2014-12-19  8:49                                                                                     ` Helmut Eller
  2014-12-19  8:56                                                                                     ` Helmut Eller
@ 2014-12-25 20:25                                                                                     ` Dmitry Gutov
  2014-12-26  3:50                                                                                       ` Stefan Monnier
  2014-12-27 19:01                                                                                       ` Stephen Leake
  2 siblings, 2 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-25 20:25 UTC (permalink / raw)
  To: Dmitry Gutov, Helmut Eller
  Cc: emacs-devel, Stephen Leake, Stefan Monnier, Jorgen Schaefer

On 12/19/2014 10:00 AM, Dmitry Gutov wrote:

> If there are no further objections, I'll install it in a few days.

Installed, with minor tweaks.

I'll let others decide if the now-unbound interactive functions from 
etags.el should be obsoleted, removed or kept as-is for older users.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-25 20:25                                                                                     ` Dmitry Gutov
@ 2014-12-26  3:50                                                                                       ` Stefan Monnier
  2014-12-28 22:21                                                                                         ` Dmitry Gutov
  2014-12-27 19:01                                                                                       ` Stephen Leake
  1 sibling, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-12-26  3:50 UTC (permalink / raw)
  To: Dmitry Gutov
  Cc: Stephen Leake, emacs-devel, Jorgen Schaefer, Helmut Eller,
	Dmitry Gutov

> I'll let others decide if the now-unbound interactive functions from
> etags.el should be obsoleted, removed or kept as-is for older users.

Obsoleting them sounds right,


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-25 20:25                                                                                     ` Dmitry Gutov
  2014-12-26  3:50                                                                                       ` Stefan Monnier
@ 2014-12-27 19:01                                                                                       ` Stephen Leake
  2014-12-27 21:22                                                                                         ` Stephen Leake
  1 sibling, 1 reply; 172+ messages in thread
From: Stephen Leake @ 2014-12-27 19:01 UTC (permalink / raw)
  To: emacs-devel

Dmitry Gutov <raaahh@gmail.com> writes:

> On 12/19/2014 10:00 AM, Dmitry Gutov wrote:
>
>> If there are no further objections, I'll install it in a few days.
>
> Installed, with minor tweaks.
>
> I'll let others decide if the now-unbound interactive functions from
> etags.el should be obsoleted, removed or kept as-is for older users.

Something's weird.

Starting from emacs -Q, in *scratch*, eval:

(require 'xref)
(emacs-lisp-mode)
xref-find-function

This gives etags-xref-find; it should give elisp-xref-find.

The only work-around I've found is to recompile emacs-lisp-mode in the
running emacs.

Somehow, the dumped definition of emacs-lisp-mode doesn't match the
source in elisp-mode.el (which sets xref-find-function to
elisp-xref-find).

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-27 19:01                                                                                       ` Stephen Leake
@ 2014-12-27 21:22                                                                                         ` Stephen Leake
  0 siblings, 0 replies; 172+ messages in thread
From: Stephen Leake @ 2014-12-27 21:22 UTC (permalink / raw)
  To: emacs-devel

Stephen Leake <stephen_leake@stephe-leake.org> writes:

> Dmitry Gutov <raaahh@gmail.com> writes:
>
>> On 12/19/2014 10:00 AM, Dmitry Gutov wrote:
>>
>>> If there are no further objections, I'll install it in a few days.
>>
>> Installed, with minor tweaks.
>>
>> I'll let others decide if the now-unbound interactive functions from
>> etags.el should be obsoleted, removed or kept as-is for older users.
>
> Something's weird.
>
> Starting from emacs -Q, in *scratch*, eval:
>
> (require 'xref)
> (emacs-lisp-mode)
> xref-find-function
>
> This gives etags-xref-find; it should give elisp-xref-find.
>
> The only work-around I've found is to recompile emacs-lisp-mode in the
> running emacs.
>
> Somehow, the dumped definition of emacs-lisp-mode doesn't match the
> source in elisp-mode.el (which sets xref-find-function to
> elisp-xref-find).

Never mind; I forgot to recompile in that build dir.

-- 
-- Stephe



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-26  3:50                                                                                       ` Stefan Monnier
@ 2014-12-28 22:21                                                                                         ` Dmitry Gutov
  2014-12-29  0:24                                                                                           ` Stefan Monnier
  0 siblings, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-28 22:21 UTC (permalink / raw)
  To: Stefan Monnier, Dmitry Gutov
  Cc: Stephen Leake, emacs-devel, Helmut Eller, Jorgen Schaefer

On 12/26/2014 05:50 AM, Stefan Monnier wrote:

> Obsoleting them sounds right,

How about this? Maybe install this early and then collect complaints if 
the new system is not up to par.


diff --git a/etc/NEWS b/etc/NEWS
index 14933aa..306e840 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -453,6 +453,11 @@ easier binding, which is now unoccupied (`M-,').
  alias for a private variable.  `xref-push-marker-stack' and
  `xref-pop-marker-stack' should be used to mutate it instead.

+** etags
+As a result of the above, these commands are now obsolete:
+`find-tag-other-window', `find-tag-other-frame', `find-tag-regexp',
+`tags-apropos' and `tags-loop-continue'.
+
  ** Obsolete packages

  ---
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 8f33641..9eedc0a 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -379,29 +379,18 @@
        menu-bar-separator)

      (bindings--define-key menu [apropos-tags]
-      '(menu-item "Tags Apropos..." tags-apropos
+      '(menu-item "Find Apropos..." xref-find-apropos
                    :help "Find function/variables whose names match 
regexp"))
-    (bindings--define-key menu [next-tag-otherw]
-      '(menu-item "Next Tag in Other Window"
-                  menu-bar-next-tag-other-window
-                  :enable (and (boundp 'tags-location-ring)
-                               (not (ring-empty-p tags-location-ring)))
-                  :help "Find next function/variable matching last tag 
name in another window"))
-
-    (bindings--define-key menu [next-tag]
-      '(menu-item "Find Next Tag"
-                  menu-bar-next-tag
-                  :enable (and (boundp 'tags-location-ring)
-                               (not (ring-empty-p tags-location-ring)))
-                  :help "Find next function/variable matching last tag 
name"))
-    (bindings--define-key menu [find-tag-otherw]
-      '(menu-item "Find Tag in Other Window..." find-tag-other-window
-                  :help "Find function/variable definition in another 
window"))
-    (bindings--define-key menu [find-tag]
-      '(menu-item "Find Tag..." find-tag
-                  :help "Find definition of function or variable"))
-
-    (bindings--define-key menu [separator-tags]
+
+    (bindings--define-key menu [xref-find-otherw]
+      '(menu-item "Find Definition in Other Window..."
+                  xref-find-definitions-other-window
+                  :help "Find function/variable definitions in another 
window"))
+    (bindings--define-key menu [xref-find-def]
+      '(menu-item "Find Definition..." xref-find-definitions
+                  :help "Find definitions of function or variable"))
+
+    (bindings--define-key menu [separator-xref]
        menu-bar-separator)

      (bindings--define-key menu [end-of-buf]
diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el
index 0be9979..f9e037b 100644
--- a/lisp/progmodes/etags.el
+++ b/lisp/progmodes/etags.el
@@ -995,6 +995,9 @@ See documentation of variable `tags-file-name'."
  			(set-window-point (selected-window) tagpoint))
  		      window-point)))

+(make-obsolete 'find-tag-other-window
+               'xref-find-definitions-other-window "25.1")
+
  ;;;###autoload
  (defun find-tag-other-frame (tagname &optional next-p)
    "Find tag (in current tags table) whose name contains TAGNAME.
@@ -1019,6 +1022,9 @@ See documentation of variable `tags-file-name'."
    (let ((pop-up-frames t))
      (find-tag-other-window tagname next-p)))

+(make-obsolete 'find-tag-other-frame
+               'xref-find-definitions-other-frame "25.1")
+
  ;;;###autoload
  (defun find-tag-regexp (regexp &optional next-p other-window)
    "Find tag (in current tags table) whose name matches REGEXP.
@@ -1042,6 +1048,8 @@ See documentation of variable `tags-file-name'."
    (funcall (if other-window 'find-tag-other-window 'find-tag)
  	   regexp next-p t))

+(make-obsolete 'find-tag-regexp 'xref-find-apropos "25.1")
+
  ;;;###autoload
  (defalias 'pop-tag-mark 'xref-pop-marker-stack)

@@ -1842,6 +1850,10 @@ nil, we exit; otherwise we scan the next file."
  	 (null tags-loop-operate)
  	 (message "Scanning file %s...found" buffer-file-name))))

+(make-obsolete 'tags-loop-continue
+               "use `xref-find-definitions' interface instead."
+               "25.1")
+
  ;;;###autoload
  (defun tags-search (regexp &optional file-list-form)
    "Search through all files listed in tags table for match for REGEXP.
@@ -1946,6 +1958,9 @@ directory specification."
      ;; apropos-mode is derived from fundamental-mode and it kills
      ;; all local variables.
      (setq buffer-read-only t)))
+
+(make-obsolete 'tags-apropos 'xref-find-apropos "25.1")
+
  \f
  ;; XXX Kludge interface.





^ permalink raw reply related	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-28 22:21                                                                                         ` Dmitry Gutov
@ 2014-12-29  0:24                                                                                           ` Stefan Monnier
  2014-12-29  0:38                                                                                             ` Dmitry Gutov
  2014-12-29  1:54                                                                                             ` Dmitry Gutov
  0 siblings, 2 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-12-29  0:24 UTC (permalink / raw)
  To: Dmitry Gutov
  Cc: Stephen Leake, emacs-devel, Helmut Eller, Dmitry Gutov,
	Jorgen Schaefer

> How about this? Maybe install this early and then collect complaints if the
> new system is not up to par.

Please use (declare (obsolete ...)) instead.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-29  0:24                                                                                           ` Stefan Monnier
@ 2014-12-29  0:38                                                                                             ` Dmitry Gutov
  2014-12-29  1:54                                                                                             ` Dmitry Gutov
  1 sibling, 0 replies; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-29  0:38 UTC (permalink / raw)
  To: Stefan Monnier
  Cc: Stephen Leake, emacs-devel, Helmut Eller, Dmitry Gutov,
	Jorgen Schaefer

On 12/29/2014 02:24 AM, Stefan Monnier wrote:

> Please use (declare (obsolete ...)) instead.

Right!

Thanks for reading, installed.



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-29  0:24                                                                                           ` Stefan Monnier
  2014-12-29  0:38                                                                                             ` Dmitry Gutov
@ 2014-12-29  1:54                                                                                             ` Dmitry Gutov
  2014-12-29 14:20                                                                                               ` Stefan Monnier
  1 sibling, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-29  1:54 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

Can we remove the commands `menu-bar-next-tag-other-window' and 
`menu-bar-next-tag'?



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-29  1:54                                                                                             ` Dmitry Gutov
@ 2014-12-29 14:20                                                                                               ` Stefan Monnier
  2014-12-29 16:17                                                                                                 ` Eli Zaretskii
  0 siblings, 1 reply; 172+ messages in thread
From: Stefan Monnier @ 2014-12-29 14:20 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: emacs-devel

> Can we remove the commands `menu-bar-next-tag-other-window' and
> `menu-bar-next-tag'?

Yes, go ahead.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-29 14:20                                                                                               ` Stefan Monnier
@ 2014-12-29 16:17                                                                                                 ` Eli Zaretskii
  2014-12-29 17:27                                                                                                   ` Dmitry Gutov
  2014-12-29 18:56                                                                                                   ` Stefan Monnier
  0 siblings, 2 replies; 172+ messages in thread
From: Eli Zaretskii @ 2014-12-29 16:17 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel, dgutov

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Date: Mon, 29 Dec 2014 09:20:30 -0500
> Cc: emacs-devel <emacs-devel@gnu.org>
> 
> > Can we remove the commands `menu-bar-next-tag-other-window' and
> > `menu-bar-next-tag'?
> 
> Yes, go ahead.

Why???



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-29 16:17                                                                                                 ` Eli Zaretskii
@ 2014-12-29 17:27                                                                                                   ` Dmitry Gutov
  2014-12-29 17:37                                                                                                     ` Eli Zaretskii
  2014-12-29 18:56                                                                                                   ` Stefan Monnier
  1 sibling, 1 reply; 172+ messages in thread
From: Dmitry Gutov @ 2014-12-29 17:27 UTC (permalink / raw)
  To: Eli Zaretskii, Stefan Monnier; +Cc: emacs-devel

On 12/29/2014 06:17 PM, Eli Zaretskii wrote:

>>> Can we remove the commands `menu-bar-next-tag-other-window' and
>>> `menu-bar-next-tag'?
>>
>> Yes, go ahead.
>
> Why???

Because the menu elements had been removed, and these seem like they 
only should be used in the menu (even though the names look public).



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-29 17:27                                                                                                   ` Dmitry Gutov
@ 2014-12-29 17:37                                                                                                     ` Eli Zaretskii
  0 siblings, 0 replies; 172+ messages in thread
From: Eli Zaretskii @ 2014-12-29 17:37 UTC (permalink / raw)
  To: Dmitry Gutov; +Cc: monnier, emacs-devel

> Date: Mon, 29 Dec 2014 19:27:10 +0200
> From: Dmitry Gutov <dgutov@yandex.ru>
> CC: emacs-devel@gnu.org
> 
> On 12/29/2014 06:17 PM, Eli Zaretskii wrote:
> 
> >>> Can we remove the commands `menu-bar-next-tag-other-window' and
> >>> `menu-bar-next-tag'?
> >>
> >> Yes, go ahead.
> >
> > Why???
> 
> Because the menu elements had been removed, and these seem like they 
> only should be used in the menu (even though the names look public).

Sorry, I don't follow: which menu elements were removed?



^ permalink raw reply	[flat|nested] 172+ messages in thread

* Re: Generalizing find-definition
  2014-12-29 16:17                                                                                                 ` Eli Zaretskii
  2014-12-29 17:27                                                                                                   ` Dmitry Gutov
@ 2014-12-29 18:56                                                                                                   ` Stefan Monnier
  1 sibling, 0 replies; 172+ messages in thread
From: Stefan Monnier @ 2014-12-29 18:56 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel, dgutov

>> > Can we remove the commands `menu-bar-next-tag-other-window' and
>> > `menu-bar-next-tag'?
>> Yes, go ahead.
> Why???

They're not used any more.


        Stefan



^ permalink raw reply	[flat|nested] 172+ messages in thread

end of thread, other threads:[~2014-12-29 18:56 UTC | newest]

Thread overview: 172+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-11-02 14:15 Generalizing find-definition Jorgen Schaefer
2014-11-02 15:34 ` Stefan Monnier
2014-11-02 16:29   ` Jorgen Schaefer
2014-11-02 18:14     ` Helmut Eller
2014-11-02 18:35       ` Jorgen Schaefer
2014-11-02 19:51         ` Helmut Eller
2014-11-02 20:17           ` Jorgen Schaefer
2014-11-03  2:22     ` Stefan Monnier
2014-11-03  7:03       ` Helmut Eller
2014-11-03  7:44       ` Jorgen Schaefer
2014-11-03 14:17         ` Stephen Leake
2014-11-03 14:30         ` Stefan Monnier
2014-11-03 18:28           ` Jorgen Schaefer
2014-11-03 20:09             ` Stefan Monnier
2014-11-03 20:55               ` Jorgen Schaefer
2014-11-03 22:38                 ` Stefan Monnier
2014-11-04 14:52                   ` Stephen Leake
2014-11-04 18:12                     ` Stefan Monnier
2014-11-04 23:13                       ` Stephen Leake
2014-11-05  2:00                         ` Stefan Monnier
2014-11-06 15:33                   ` Dmitry Gutov
2014-11-06 19:40                     ` Stephen Leake
2014-11-07  2:57                       ` Yuri Khan
2014-11-07 20:56                       ` Dmitry Gutov
2014-11-03 22:39                 ` Stefan Monnier
2014-11-04 14:58                   ` Stephen Leake
2014-11-03 23:46                 ` Stephen J. Turnbull
2014-11-04  7:58                   ` Jorgen Schaefer
2014-11-04  2:52                 ` Yuri Khan
2014-11-04  7:41                   ` Jorgen Schaefer
2014-11-06 15:22           ` Dmitry Gutov
2014-11-06 16:51             ` Stefan Monnier
2014-11-06 17:00               ` Helmut Eller
2014-11-06 17:08                 ` Multiple next-error sources Jorgen Schaefer
2014-11-06 23:15                   ` Stefan Monnier
2014-11-07  9:49                     ` Jorgen Schaefer
2014-11-07 14:59                       ` Stefan Monnier
2014-11-07 15:24                         ` Daniel Colascione
2014-11-07 15:55                           ` Stefan Monnier
2014-11-07 16:08                             ` Daniel Colascione
2014-11-07 18:17                               ` Stefan Monnier
2014-11-07 18:22                                 ` Daniel Colascione
2014-11-07 19:06                                   ` Stefan Monnier
2014-11-07 15:41                         ` Jorgen Schaefer
2014-11-07 16:03                           ` Stefan Monnier
2014-11-07 16:55                         ` Alan Mackenzie
2014-11-07 17:10                           ` Daniel Colascione
2014-11-07 17:40                             ` Alan Mackenzie
2014-11-08  8:55                               ` Dmitry Gutov
2014-11-07 18:08                           ` Stefan Monnier
2014-11-07 18:21                             ` Alan Mackenzie
2014-11-07 18:48                               ` Stefan Monnier
2014-11-07 19:51                                 ` Alan Mackenzie
2014-11-03 14:46       ` Generalizing find-definition Stephen Leake
2014-11-03 16:42         ` Stefan Monnier
2014-11-04 15:39           ` Stephen Leake
2014-11-04 18:14             ` Stefan Monnier
2014-11-17 20:10   ` Jorgen Schaefer
2014-11-18  8:07     ` Stephen Leake
2014-11-18 11:24       ` Helmut Eller
2014-11-18 12:48         ` Dmitry Gutov
2014-11-18 12:03       ` Helmut Eller
2014-11-19 14:27       ` Stefan Monnier
2014-11-19 14:51         ` Ivan Shmakov
2014-11-19 22:31           ` Stefan Monnier
2014-11-20  0:15         ` Stephen Leake
2014-11-20  4:18           ` Stefan Monnier
2014-11-18 16:31     ` Stefan Monnier
2014-11-20  0:21       ` Stephen Leake
2014-11-20  4:19         ` Stefan Monnier
2014-11-20 20:21           ` Jorgen Schaefer
2014-11-20 13:44     ` Helmut Eller
2014-11-20 20:28       ` Jorgen Schaefer
2014-11-20 20:42         ` Helmut Eller
2014-11-20 23:27         ` Stefan Monnier
2014-11-20 23:42           ` Jorgen Schaefer
2014-11-21  3:05             ` Stefan Monnier
2014-11-21  8:24             ` martin rudalics
2014-11-30 13:29               ` Stefan Monnier
2014-11-23 13:44       ` Johan Claesson
2014-12-01 17:31         ` Helmut Eller
2014-12-04  3:13           ` Stephen Leake
2014-12-04  8:07             ` Stephen Leake
2014-12-04 12:45               ` Helmut Eller
2014-12-04  9:11             ` Helmut Eller
2014-12-04 16:19               ` Stephen Leake
2014-12-04 16:49                 ` Helmut Eller
2014-12-05  9:43                   ` Stephen Leake
2014-12-05 13:25                     ` Helmut Eller
2014-12-05 17:41                       ` Stephen Leake
2014-12-06  8:55                         ` Helmut Eller
2014-12-06 18:19                           ` Stephen Leake
2014-12-06 18:38                           ` Drew Adams
2014-12-07 16:52                             ` Stephen Leake
2014-12-06 22:57                           ` Stefan Monnier
2014-12-07  9:55                             ` Helmut Eller
2014-12-08 14:33                               ` Stefan Monnier
2014-12-08 19:58                                 ` Helmut Eller
2014-12-08 21:38                                   ` Stefan Monnier
2014-12-08 21:58                                     ` Jorgen Schaefer
2014-12-09  2:33                                       ` Stefan Monnier
2014-12-09  2:34                                     ` Stefan Monnier
2014-12-09  8:40                                       ` Helmut Eller
2014-12-09 14:03                                         ` Dmitry Gutov
2014-12-09 14:47                                           ` Helmut Eller
2014-12-11  4:06                                             ` Dmitry Gutov
2014-12-11  8:09                                               ` Helmut Eller
2014-12-11 11:12                                                 ` Helmut Eller
2014-12-11 18:36                                                   ` Helmut Eller
2014-12-11 19:21                                                     ` David Engster
2014-12-11 19:36                                                       ` Helmut Eller
2014-12-11 21:53                                                         ` David Engster
2014-12-11 22:04                                                           ` David Engster
2014-12-12  7:26                                                             ` Helmut Eller
2014-12-11 22:52                                                 ` Dmitry Gutov
2014-12-11 23:55                                                   ` Stefan Monnier
2014-12-11 23:59                                                     ` Dmitry Gutov
2014-12-11 15:07                                               ` Stefan Monnier
2014-12-11 18:43                                                 ` Helmut Eller
2014-12-11 20:11                                                   ` Stefan Monnier
2014-12-11 20:31                                                     ` Helmut Eller
2014-12-11 21:33                                                       ` Stefan Monnier
2014-12-15 17:21                                                       ` Dmitry Gutov
2014-12-15 21:13                                                         ` Stefan Monnier
2014-12-15 21:24                                                           ` Dmitry Gutov
2014-12-15 21:57                                                         ` Helmut Eller
2014-12-15 22:06                                                           ` Dmitry Gutov
2014-12-15 22:17                                                             ` Helmut Eller
2014-12-15 22:26                                                               ` Dmitry Gutov
2014-12-15 22:41                                                                 ` Helmut Eller
2014-12-15 22:54                                                                   ` Dmitry Gutov
2014-12-15 23:03                                                                     ` Helmut Eller
     [not found]                                                                       ` <54901FEB.1090704@yandex.ru>
     [not found]                                                                         ` <m2k31ric89.fsf@gmail.com>
     [not found]                                                                           ` <5490962D.7010105@yandex.ru>
     [not found]                                                                             ` <m2y4q75ntx.fsf@gmail.com>
2014-12-16 21:40                                                                               ` Dmitry Gutov
2014-12-17  7:25                                                                                 ` Helmut Eller
2014-12-19  8:00                                                                                   ` Dmitry Gutov
2014-12-19  8:49                                                                                     ` Helmut Eller
2014-12-19 14:34                                                                                       ` Dmitry Gutov
2014-12-19  8:56                                                                                     ` Helmut Eller
2014-12-19 13:36                                                                                       ` Dmitry Gutov
2014-12-25 20:25                                                                                     ` Dmitry Gutov
2014-12-26  3:50                                                                                       ` Stefan Monnier
2014-12-28 22:21                                                                                         ` Dmitry Gutov
2014-12-29  0:24                                                                                           ` Stefan Monnier
2014-12-29  0:38                                                                                             ` Dmitry Gutov
2014-12-29  1:54                                                                                             ` Dmitry Gutov
2014-12-29 14:20                                                                                               ` Stefan Monnier
2014-12-29 16:17                                                                                                 ` Eli Zaretskii
2014-12-29 17:27                                                                                                   ` Dmitry Gutov
2014-12-29 17:37                                                                                                     ` Eli Zaretskii
2014-12-29 18:56                                                                                                   ` Stefan Monnier
2014-12-27 19:01                                                                                       ` Stephen Leake
2014-12-27 21:22                                                                                         ` Stephen Leake
2014-12-12  1:29                                                 ` Stephen Leake
2014-12-12  3:05                                                   ` Stefan Monnier
2014-12-12 11:15                                                     ` Stephen Leake
2014-12-12 13:58                                                       ` Stefan Monnier
2014-12-13  9:56                                                     ` Dmitry Gutov
2014-12-12  5:05                                                 ` Dmitry Gutov
2014-12-10  9:11                                           ` Stephen Leake
2014-12-10 13:02                                             ` Dmitry Gutov
2014-12-10 17:00                                               ` Stephen Leake
2014-12-10 19:06                                                 ` Stefan Monnier
2014-12-12  1:03                                                   ` Stephen Leake
2014-12-10 14:10                                           ` Stefan Monnier
2014-12-11  4:08                                             ` Dmitry Gutov
2014-12-08 22:36                                 ` Stephen Leake
2014-11-02 22:26 ` Stephen Leake
2014-11-03  7:31   ` Jorgen Schaefer
2014-11-03  8:13     ` Helmut Eller
2014-11-03 13:49       ` Stephen Leake
2014-11-03 17:58       ` Jorgen Schaefer
2014-11-04 15:54         ` Stephen Leake

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).