stags: Scala tags generator
Installation
Using Coursier:
coursier bootstrap co.pjrt:stags-cli_2.12:0.4.2 -o stags
If you want to use stags
tag generation as a library, you can add it to sbt with:
libraryDependencies += "co.pjrt" % "stags_2.12" % "0.4.2"
Using Nailgun:
You can use Coursier to create a standalone cli for starting Stags with Nailgun like this:
coursier bootstrap --standalone co.pjrt:stags-cli_2.12:0.4.2 \
-o stags_ng -f --main com.martiansoftware.nailgun.NGServer
stags_ng & // start nailgun in background
ng ng-alias stags co.pjrt.stags.cli.Main
ng stags --version
You can then create an alias for ng stags
if that's still too much typing.
Caveats and tips:
- You must call
ng ng-alias
after every restart of the nailgun server. You could create a script to do this- You could also simply make an alias in your terminal (ie:
alias stags=ng co.pjrt.stags.cli.Main
).
- You could also simply make an alias in your terminal (ie:
- If you are running multiple Nailgun instances (for example, one for
stags
and one forscalafmt
) you must run one of them in a different port.- In the above example, simply call
stags_ng $new_port
to run the stags ng instance in a different port. Then allng
calls need to have the flag--nailgun-port $new_port
in them. - You could in theory simply feed both jars (ie: stags and scalafmt) into the same ng instance but beware this could cause classpath conflicts between the two (or more) jars.
- In the above example, simply call
Usage
stags ./
This will fetch all Scala files under the current directory. The tags file will be generated in ./tags
. To place the tags file somewhere else, do:
stags ./ -o path/to/tags
Features
The two main differences between stags and a general ctags generator like Universal CTags is its ability to understand Scala code (with all its intricacies) and the ability to produce qualified tags.
Understanding Scala intricacies and static tagging them
What are static tags? Static tags are tags for "static functions". In the C world this means functions that can only be used in the file where they are defined; you could think of them as "private". Vim understand static tags and will match them first before anything else.
Static tags lend themselves nicely to private field and functions, so stags
marks private statements and fields as static, while taking care of some Scala intricacies.
If a def/val/class/ect is private
within its file, then it is static. If it is private for some large scope, then it isn't static. This means that if it is private[X]
then we check if X
is an enclosing object within the file. However, if X isn't an enclosing object in this file, then we mark it as non-static. For example
package org.example.somepackage.test
object X {
object Y {
private[X] def f = …
}
}
object K {
private[somepackage] def g = …
}
In this example, f
would be static, but g
isn't because g
might be accessed from outside the file.
Other cases that are marked as static are:
- constructor fields in classes (ie: in
class X(a: Int, b: String, c: Boolean)
,a
,b
andc
will all be static)- But non-static for the first parameter group of
case
classes (since those are accessible by default)case class X(a: Int)(b: Int)
<-a
will be non-static, butb
will be static
- Any that are marked as "private" are static
- But non-static for the first parameter group of
- the single field in an implicit class/case class
implicit class X(val x: Int)
<-x
is static- this is done because chances are that
x
will never be accessed anywhere but this file
- all implicit things (val, defs, class, etc)
- these things are rarely, if ever, accessed via their tokens
Qualified tags
A common pattern found when importing conflicting fields is to use them in a qualified form. For example:
import org.example.SomeObject
import org.example.OtherObject
SomeObject.foo(...)
OtherObject.foo(...)
In order to differentiate between the two, stags
generates tags for all fields along with an extra tag that combines their parent with the tag itself. Note that stags
never generates qualified tags for fields/methods in trait
and class
(only objects and package objects) since said fields/methods cannot be qualifiedly referenced.
Following code, by default, would produce three tags: Example
, foo
and Example.foo
:
package object test {
object Example {
def foo(...)
}
}
The depth of the qualified tags is controlled by --qualified-depth
. Setting it to three (3) would produce a third tag test.Example.foo
.
Vim support for qualified tags
Vim won't understand such a tag right off the bat. The following modification is required:
function! QualifiedTagJump() abort
let l:plain_tag = expand("<cword>")
let l:orig_keyword = &iskeyword
set iskeyword+=\.
let l:word = expand("<cword>")
let &iskeyword = l:orig_keyword
let l:splitted = split(l:word, '\.')
let l:acc = []
for wo in l:splitted
let l:acc = add(l:acc, wo)
if wo ==# l:plain_tag
break
endif
endfor
let l:combined = join(l:acc, ".")
try
execute "ta " . l:combined
catch /.*E426.*/ " Tag not found
execute "ta " . l:plain_tag
endtry
endfunction
nnoremap <silent> <C-]> :<C-u>call QualifiedTagJump()<CR>