Skip to content

Conversation

Konfekt
Copy link
Contributor

@Konfekt Konfekt commented Mar 20, 2025

this makes gf jump to JDK imports as well as local imports

Konfekt added 3 commits March 20, 2025 09:58
this makes gf jump to JDK imports as well as local imports
instead of clobbering search registers and manual search and open
@Konfekt Konfekt changed the title automatically set undocumented source_path with fallback automatically set source_path to fallback educated guess Mar 25, 2025
@Konfekt
Copy link
Contributor Author

Konfekt commented Mar 25, 2025

I now wonder why I couldn't find the documentation of this setting; just grepping yielded

JAVA							*ft-java-plugin*

Whenever the variable "g:ftplugin_java_source_path" is defined and its value
is a filename whose extension is either ".jar" or ".zip", e.g.: >
	let g:ftplugin_java_source_path = '/path/to/src.jar'
	let g:ftplugin_java_source_path = '/path/to/src.zip'

@zzzyxwvut
Copy link
Owner

The master implementation is capable and foolproof, for it
defers hard decisions to users. :) Its &includeexpr support
‘resolves’ simple names too with gf, and permits to open any
source file from an archive.

A dedicated plugin to be written I alluded to should attempt
to shoulder a portion of such decisions; for example, offer
automated discovery of source files. Then such automation
will need to consider that:

  • Source files will be packaged in many separate archives
    and will be placed in separate directory trees.
  • There will be transitive dependences via import module,
    i.e. a type simple name will come from either import'd
    module or from another module that is requires transitive
    specified in a module declaration or from another module's
    own requires transitive module, and so on.
  • Some non-modularised projects will have split packages and
    cyclic dependences.
  • There will be manageable type name clashes in a source
    file, where a type simple name is import'd and one or
    more type fully qualified names are not import'd.

Implementing it is a challenging task.

@zzzyxwvut zzzyxwvut mentioned this pull request Mar 25, 2025
@Konfekt
Copy link
Contributor Author

Konfekt commented Mar 26, 2025

The master implementation is capable and foolproof, for it
defers hard decisions to users... Implementing it is a challenging task.

Fully agreed, but Vim is full of compromises for making the user's life simpler.

In this case, why not use the JDK src.zip as a starting point instead of requiring manual set up and knowing about the overriden search registers?

Users will appreciate not to have their heads wrap around these options buried in the docs.

@zzzyxwvut
Copy link
Owner

Let us compare how capable both implementations are by using
this example project:

git clone https://github.com/zzzyxwvut/module-info.git
cd module-info/module_info

git switch java/24
git archive --verbose --format=zip -9 -o /tmp/module_info_sources.zip java/24 src/
./build.sh test

export JDK_24_SRC_ARCH=/path/to/src.zip
vim src/org.module.info.demo/tests/org/demo/tests/Tester.java

Now try to load the source file for any type from java.base
or java.logging or jdk.jfr (see below) with gf, after doing:

let g:ftplugin_java_source_path = $JDK_24_SRC_ARCH
doautocmd FileType
Relevant types in src.zip:
# Module java.base:
java.io.IOException
java.io.InputStream
java.io.UncheckedIOException
java.time.Duration
java.util.Arrays
java.util.Optional
java.util.ServiceLoader
java.util.function.Consumer
java.util.function.Function
java.util.stream.Collectors
java.util.stream.Stream

# Module java.logging:
java.util.logging.Logger
java.util.logging.LogManager

# Module jdk.jfr:
jdk.jfr.consumer.EventStream
jdk.jfr.consumer.RecordedStackTrace
jdk.jfr.consumer.RecordingStream

Then try to load the source file for any type from
org.module.info.demo or org.module.info.tester (see below)
with gf, after doing:

let g:ftplugin_java_source_path = '/tmp/module_info_sources.zip'
doautocmd FileType
Relevant types in module_info_sources.zip:
# Module org.module.info.demo:
org.tester.Templet.FalseAssertions
org.tester.Templet.Result

# Module org.module.info.tester:
org.demo.internal.Testable

(Because double-barrelled identifiers like Templet.Result
signify a TopLevelType.NestedType relation, there will be no
filename matches for these. A TopLevelType filename need to
be searched instead, with NestedType possibly declared in
it.)

The proposed version only discovers files from the java.base
module (whereas 60-odd modules are packaged in JDK) and it
only supports fully qualified types and single-type imports,
e.g. import java.util.Optional; and java.util.Optional;
simple names, e.g. Optional, are beyond its capabilities.

The odd thing about execpath('javac') lookups is that it is
assumed that whatever compiler version is found in $PATH is
surely the compiler version used for a loaded Java file.
Well, it's no fun, chasing a bug in one version, read other
version
sources.

@Konfekt
Copy link
Contributor Author

Konfekt commented Mar 27, 2025

The proposed version only discovers files from the java.base
module (whereas 60-odd modules are packaged in JDK) and it
only supports fully qualified types and single-type imports,
e.g. import java.util.Optional; and java.util.Optional;
simple names, e.g. Optional, are beyond its capabilities.

java.base is too basic. But could this manual search not be done programmatically?

The odd thing about execpath('javac') lookups is that it is
assumed that whatever compiler version is found in $PATH is
surely the compiler version used for a loaded Java file.

Surely not, only most likely. You'd prefer reading it from &makeprg and fall back to javac instead?

Mind you that all these assumptions are only made as best guesses if source path is unset, so sureness could never be assumed.

@zzzyxwvut
Copy link
Owner

On any developer's host system, there can be a handful of
older and newer Java projects that depend on distinct JDK
versions. Non-LTS JDK versions are phased out into oblivion
every six months, LTS JDK versions can be supported for many
years; Java projects may not follow in lockstep fashion and
adopt each new JDK release. Able, long-living projects will
set up formal bookkeeping by specifying required tools and
their versions for development and maintenance in a build
manifest: Makefile, pom.xml, etc. That is the file to parse
and query (and its parent version elsewhere) not stateless
environment variables like $JAVA_HOME, $PATH, etc. And
ftplugin/xml.vim (or its specialised derivative) can be just
the place to set &makeprg, g:ftplugin_java_source_path, etc.
It goes without saying that some other plugin will be needed
to orchestrate the loading of pom.xml etc. ahead of any Java
file for a project. Otherwise, after/ftplugin/java.vim is
better suited for ad-hoc settings and other guesswork.

@Konfekt
Copy link
Contributor Author

Konfekt commented Mar 28, 2025

Not all developers define their JDK path in their tool chain config when editing Java code in Vim.
For those, one suggestion would be to only fall back to a best guess in absence of these?

@Konfekt
Copy link
Contributor Author

Konfekt commented Mar 28, 2025

Taking smaller leaps, would you agree on keeping gf with source_path set intact for opening project local imports outsideof source_path by an additional check

	let fname = tr(v:fname, '.', '/') .. '.java'
      		let ffname = findfile(fname)
      		if filereadable(ffname)
      	    	    return ffname
...

@zzzyxwvut
Copy link
Owner

A search in &l:path directories is supported, as documented:

Otherwise, for the defined variable "g:ftplugin_java_source_path", the local
value of the 'path' option will be further modified by prefixing the value of
the variable, e.g.: >
let g:ftplugin_java_source_path = $JDK_SRC_PATH
let &l:path = g:ftplugin_java_source_path . ',' . &l:path
<
and the "gf" command can be used on a fully-qualified type to look for a file
in the "path" and to try to load it.

And for the above example, this setup should suffice to load
files at import declarations (at commit java/24~1) for
org.module.info.demo and org.module.info.tester with gf
(barring double-barrelled-identifier imports), after doing:

let g:ftplugin_java_source_path = [
	\ 'org.module.info.demo/classes',
	\ 'org.module.info.demo/tests',
	\ 'org.module.info.tester/classes',
	\ ]
    \ ->map('"/tmp/module-info/module_info/src/" . v:val')
    \ ->join(',')
doautocmd FileType

On the other hand, assigning an archive path implies that
you opt for making file selection yourself from that archive
only and NOT stipulating for a first match from &l:path.

If you want to set g:ftplugin_java_source_path just once,
then consider extracting all archived files and adding their
paths to g:ftplugin_java_source_path. But &l:path-based
searches are limited to fully-qualified non-double-barrelled
identifiers only, with pathname ties arbitrarily resolved in
favour of whatever ‘first’ match happens to be.

Tags can be a better alternative for local forks.


Not all developers define their JDK path in their tool chain config when
editing Java code in Vim.
For those, one suggestion would be to only fall back to a best guess in
absence of these?

Without looking at a manifest file and its parent files, you
can't know that. Well, you should know YOUR local projects
and may afford to go with defaults set from an after/ file.
But for a general approach to be helpful, you either look
(or better yet ask users to supply relevant settings) and
cache all the collected settings in another file for future
reference, and then decide what to use; or don't bother and
let users configure their projects without assistance. It
is better to do nothing than incur risk of unconditionally
overriding user preferences without any explicit request.

@Konfekt
Copy link
Contributor Author

Konfekt commented Mar 29, 2025

On the other hand, assigning an archive path implies that
you opt for making file selection yourself from that archive
only and NOT stipulating for a first match from &l:path.

It didn't mean it for me. Starting from a default setup, one would expect the standard libraries and all inside the project to be reachable. That's what this PR would achieve.

If you want to set g:ftplugin_java_source_path just once,
then consider extracting all archived files and adding their
paths to g:ftplugin_java_source_path.

This is beyond practical a solution for most Vim users editing Java code. Few likely care where the standard libraries reside.

But &l:path-based searches are limited to fully-qualified non-double-barrelled identifiers only

What does fully-qualified non-double-barrelled mean? I suppose that if the current work dir is that of the project, then the import statements resolve fine?

@Konfekt
Copy link
Contributor Author

Konfekt commented Mar 29, 2025

NB: I myself worked around it by LS, tags and overriding &includeexpr, this is only for a more convenient default setup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants