I’m not sure I completely understand what you need, but I’ll try to explain Snail’s search for completions.
Snail tries to do (what I expect to be) The Right Thing when editing source file buffers. With regard to modules, the rules are:
Everything in that module is visible to the completion system.
Modules referenced inside that module with import or using are also visible, following Julia’s usual rules.
If there is no module in a source file, Snail assumes the module is Main, same as the REPL.
When you reference a module Alpha in another module Beta, it only completes Alpha’s exported names. The assumption is that the exported names are part of the module’s interface, and unexported names are internal, and therefore aren’t “supposed” to be used from other modules.
How do you structure the modules of your project? If you have multiple files, do they each house distinct modules? Each distinct module is supposed to have an explicit list of import and using statements. If OTOH MyPackage just uses the default Main module for everything, then I expect completion to work for anything that has been loaded into the REPL environment, whether with julia-snail-send-buffer-file or another mechanism.
Usually I split one module over multiple files (and this is common also for the majority of Julia packages that I have seen out there) by doing something like this:
module MyPackage
using OtherPackage1
using OtherPackage2
include("file_1.jl")
...
include("file_n.jl")
end
So if I understand your answer (and from what I could gather while playing with julia-snail), currently julia-snail will not know that file_1.jl is part of a module MyPackage , am I correct?
I think it will be very valuable if snail could recognize that files which are added to a module via include statements are part of the module (at least those added in the file where the module is declared). But I am not sure how hard would this be to implement.
This is so great, I’m very excited! My biggest gripe with programming in Julia is that the emacs tooling is little limited. Looking forward to getting this going on my system when I can.
Exactly. Snail looks at file_1.jl, parses the tree around the cursor, finds no containing module, and assumes it’s in Main.
(Still, note that completion of code defined in file_1.jl will work fine in the file which defines MyPackage, at least after it’s been loaded in the REPL.)
Yes. I don’t do this myself so didn’t think of it, but it makes sense to structure code like this. Since this is common idiom, supporting it is important, especially since so much of Snail relies on knowing the current module (not just completion, but also xref and C-c C-c).
What do you think of using an Emacs-level buffer-local variable to tell Snail about which module a file is in? You’d put this at the top of e.g. file_1.jl:
Then if the parser thinks the current module is Main, it will use the value of julia-snail-current-file-module as the current module instead. The file will still be able to define internal modules and they will be considered nested under julia-snail-current-file-module.
This won’t work in the case when file_1.jl is included in multiple different modules to provide mixin behavior, but IMO that’s a pretty pathological corner case and (I hope) considered bad Julia style.
While this can work for private projects I guess it is not ideal for packages which are meant to be published or projects where you collaborate with other people who don’t use Emacs. If this is very easy to implement I think it is better to have this option than not have it.
Is there maybe a possibility to store this information in a separate file (which can be excluded from version control), maybe as some alist? Or will this have a bad effect on performance?
If so is it possible to automatically generate such a file/alist when sending a module buffer to the REPL by parsing the include statements?
I never seen such a thing in packages and can’t think of a scenario where it will make sense to do so, so I would also consider this a pathological case.
Yes, manually specified file modules can be stored in a file, but I don’t love the idea of checking it for changes all the time or trying to figure out how to invalidate a cache.
Upon reflection, it seems quite possible to just have the parser store includes that it finds in-memory and on the fly. It shouldn’t affect performance much. It also has the advantage of working if the root module including a particular file changes (the change will be picked up on a re-parse of the new root module). I think that only leaves the corner case of a file going from being an include-only part of a module to something independent or a root module of its own.
I have to give some thought to how to correctly implement this feature, but it does seem like something I can add.
Initialization problems reported upthread have been resolved. (Async process handshakes are hard, who knew… )
The feature @orialb suggested, which tracks Julia source files include()d inside other modules, has been added. It required some rework of the code, so I hope I didn’t introduce too many bugs, but it was really important to get multi-file development working properly. Please open a ticket if you notice anything untoward.
I verified that Snail works with Julia 1.4.
Some of the window handling has been reworked and simplified, which let me simplify installation and configuration instructions.
Thanks to a contribution by user dahtah on GitHub, error buffers now have hyperlinks to error locations.
Wonderful, glad to hear it! I want to add two more features (remote ssh-based REPL and image display, for which people have added PRs but need some polish) and get a version 1.0.0-final out the door. Just difficult to find time to work on Snail right now.
I missed this post… No, Snail is not an LSP server and so does not work with lsp-mode and eglot. Snail provides xref and autocomplete using its own protocol, in addition to REPL integration (which LSP lacks).