Author Archives: Jacob Zelko

Asynchronous Workflow Using Julia Tutorial

By: Jacob Zelko

Re-posted from: https://jacobzelko.com/01082023043553-julia-async-workflow/index.html

Asynchronous Workflow Using Julia Tutorial

Date: January 7 2023

Summary: A thorough tutorial on how to use Julia for asynchronous workflows to do many things, at once!

Keywords: #blog #archive #julia #programming #literate #workflow #asynchronous #weave #browser #sync #node

Bibliography

Not Available

Table of Contents

    1. Motivation
    2. Set-Up
    3. Simple Workflow Process
      1. Blocking Workflow
      2. Asynchronous Workflow
    4. Asynchronous Workflow for Literate Programming
    5. Conclusion
  1. References
  2. References
  3. Discussion:

Motivation

I sometimes find myself wanting to execute a Julia file I have just edited. This is different than what the excellent Revise.jl package (created by Tim Holy) does in that I want to execute the full file and not just update function definitions. Moreover, I may just want Julia to do something else entirely upon updating a file or directory. This can be very helpful in analysis workflows as well as literate programming. Creating an extended post on my process for this was inspired by a Julia Discourse post I provided an answer to.

Set-Up

Here is how I recommend setting up your Julia environment to follow this tutorial (I assume you have Julia installed correctly):

  1. Create an empty directory (folder) somewhere on your computer. (NOTE: For sake of this tutorial, I will refer to it as the "test" folder).

  2. Within your favorite text editor (like Notepad, Vim, VSCode, etc.), open this directory.

  3. Open and create a file called "tmp.jmd" (NOTE: Keep the file and your editor open as we will be coming back to this file quite a lot).

  4. Open your Julia REPL within the directory and activate a temporary environment within package mode:

pkg> activate --temp
  1. Add into this temporary environment the following packages:

pkg> add FileWatching, Weave
  1. Install node onto your machine.

I suggest using the great tool, nvm, which allows easy management of node versions.

  1. Install the node package, browser-sync, which allows for syncing of webpages based on updates.

Here is the command you can use in your command line:

npm i -g browser-sync

NOTE: Steps 6 and 7 are not necessary if you are not interested in the section on literate programming

Simple Workflow Process

There are two ways to start this workflow. One blocking (i.e. you cannot use the Julia REPL while working) and another spawning an asynchronous background process to enable you to work with the same REPL. Here is gif showing these workflows in action:

Blocking Workflow

This creates a synchronous task in your Julia REPL that can then run whatever command you want while you monitor a specific file (or folder). In this case, I am making the task print a statement that says “Change Detected!” to my REPL whenever there is a change in the file called “tmp.jmd”.

using FileWatchingwhile true 
    watch_file("./tmp.jmd")
    println("Change Detected!")
end

This workflow is not optimal as this loop continues and locks your REPL from doing anything else.

Asynchronous Workflow

This creates an asynchronous task in the background of your Julia REPL that can then run whatever command you want while you monitor a specific file (or folder). In this case, I am making the task print a statement that says “Change Detected!” to my REPL whenever there is a change in the file called “tmp.jmd”.

using FileWatching@async while true 
    watch_file("./tmp.jmd")
    println("Change Detected!")
end

From here, you could then still interact with this REPL while this task is running in the background. Furthermore, you can change the line that says println("Change Detected!") to whatever command (or commands) you want to trigger.

Asynchronous Workflow for Literate Programming

This workflow allows one to work with a Julia Markdown document with Weave.jl to preview your work in real time. There a few steps to start the workflow but here is how it looks when in operation:

First, execute the following snippet in your Julia REPL to start the asynchronous process to watch for changes and tell Julia to weave the file we will modify:

using FileWatching
using Weave@async while true 
    watch_file("./tmp.jmd")
    weave("./tmp.jmd")
end

Next, run this in a separate terminal:

browser-sync start --server --start --file "./*.html"

or this command within your Julia REPL (NOTE: This will make your REPL session very cluttered but you can still use your REPL):

@async run(`browser-sync start --server --start --file "./*.html"`)

At this point, you should see browser-sync spit out a lot of information that looks something like this:

[Browsersync] Access URLs:
 --------------------------------------
       Local: http://localhost:3000
    External: http://192.168.1.186:3000
 --------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 --------------------------------------
[Browsersync] Serving files from: ./

You'll want to grab that URL that looks like http://localhost:3000 in the Local spot and then open that in your web browser. With the URL that you get (it could be on a different port), navigate to http://localhost:3000/tmp.html.

Finally, when you have this all in place, go ahead and add this code block into the tmp.jmd file that was created:

# Hello World This is a Julia Markdown file. ```julia 
2 + 2
```

You should see a few things happen. You'll see a few messages from the Julia REPL stating that Weave.jl is weaving your document to your desired output. Then, you'll see browser-sync say something about updating or syncing. Finally, you will see your browser update to the latest version of your weaved document.

Feel free to play around with this more and see the dynamism of the workflow! I tend to do this when I am iteratively developing reports within Julia and want to tinker within the REPL at the same time I am creating documents. Having to wait for rendering Weave.jl documents and the like was a pain and this took the pain away.

Conclusion

I hope you appreciated this post! If you have any suggestions, comments, or additional workflows this could be used for, please comment below. May your Julia sessions now be even more supercharged!

References

References

Discussion:

Setting Up Julia LSP for Neovim

By: Jacob Zelko

Re-posted from: https://jacobzelko.com/08312022162228-julia-lsp-setup/index.html

Setting Up Julia LSP for Neovim

Date: August 31 2022

Summary: An explanation of how to setup the Julia LSP for Neovim

Keywords: ##summary #neovim #julia #programming #archive

Bibliography

Not Available

Table of Contents

    1. General Guide
  1. References
  2. References
  3. Discussion:

General Guide

This is from Fredrik Ekre at the Julia Discourse with some minimal changes and notes from me:

LanguageServer is somewhat slow to start so it is very useful to use a custom sysimage using PackageCompiler to reduce this time. On my machine I get the first response after 20+ seconds, but with a custom sysimage I can execute LS commands instantaneously.

Here is my setup:

  1. Install Mason.nvim or nvim-lspconfig and install julials (it may also be called something like Julia Language Server Protocol).

  2. Modify init.vim or init.lua to use a custom Julia executable (if it exists):

require'lspconfig'.julials.setup{
    on_new_config = function(new_config, _)
        local julia = vim.fn.expand("~/.julia/environments/nvim-lspconfig/bin/julia")
        if require'lspconfig'.util.path.is_file(julia) then
	    vim.notify("Hello!")
            new_config.cmd[1] = julia
        end
    end
}

(OPTIONAL) If you use Packer to manage your vim setup, run PackerCompile.

NOTE: If you notice, there is a small line named vim.notify("Hello!"). This is to test that julials is engaged when accessing a Julia file – you can check that it is engaged by writing :messages in vim. You should see "Hello!" appear. This line can then safely be removed.

  1. Create the nvim-lspconfig Julia environment by running the following in your shell:

julia --project=~/.julia/environments/nvim-lspconfig -e 'using Pkg; Pkg.add("LanguageServer")'

And then navigate to the directory at ~.julia/environment/nvim-lspconfig.

  1. Copy the following makefile (courtesy of Fredrik Ekre) the nvim-lspconfig directory with the name makefile:

# MIT License. Copyright (c) 2021 Fredrik Ekre
#
# This Makefile can be used to build a custom Julia system image for LanguageServer.jl to
# use with neovims built in LSP support. An up-to date version of this Makefile can be found
# at https://github.com/fredrikekre/.dotfiles/blob/master/.julia/environments/nvim-lspconfig/Makefile
#
# Usage instructions:
#
#   1. Update the neovim configuration to use a custom julia executable. If you use
#      nvim-lspconfig (recommended) you can modify the setup call to the following:
#
#          require("lspconfig").julials.setup({
#              on_new_config = function(new_config, _)
#                  local julia = vim.fn.expand("~/.julia/environments/nvim-lspconfig/bin/julia")
#                  if require("lspconfig").util.path.is_file(julia) then
#                      new_config.cmd[1] = julia
#                  end
#              end,
#              -- ...
#          })
#
#   2. Place this Makefile in ~/.julia/environments/nvim-lspconfig (create the directory if
#      it doesn't already exist).
#
#   3. Change directory to ~/.julia/environments/nvim-lspconfig and run `make`. This will
#      start up neovim in a custom project with a julia process that recods compiler
#      statements. Follow the instructions in the opened source file, and then exit neovim.
#
#   4. Upon exiting neovim PackageCompiler.jl will compile a custom system image which will
#      automatically be used whenever you work on Julia projects in neovim.
#
# Update instructions:
#
#  To update the system image (e.g. when upgrading Julia or upgrading LanguageServer.jl or
#  it's dependencies) run the following commands from the
#  ~/.julia/environments/nvim-lspconfig directory:
#
#      julia --project=. -e 'using Pkg; Pkg.update()'
#      makeJULIA=$(shell which julia)
JULIA_PROJECT=
SRCDIR:=$(shell dirname $(abspath $(firstword $(MAKEFILE_LIST))))
ifeq ($(shell uname -s),Linux)
	SYSIMAGE=languageserver.so
else
	SYSIMAGE=languageserver.dylib
endifdefault: $(SYSIMAGE)$(SYSIMAGE): Manifest.toml packagecompiler/Manifest.toml packagecompiler/precompile_statements.jl
	JULIA_LOAD_PATH=${PWD}:${PWD}/packagecompiler:@stdlib ${JULIA} -e 'using PackageCompiler; PackageCompiler.create_sysimage(:LanguageServer, sysimage_path="$(SYSIMAGE)", precompile_statements_file="packagecompiler/precompile_statements.jl")'Manifest.toml: Project.toml
	JULIA_LOAD_PATH=${PWD}/Project.toml:@stdlib ${JULIA} -e 'using Pkg; Pkg.instantiate()'Project.toml:
	JULIA_LOAD_PATH=${PWD}/Project.toml:@stdlib ${JULIA} -e 'using Pkg; Pkg.add("LanguageServer")'packagecompiler/Manifest.toml: packagecompiler/Project.toml
	JULIA_LOAD_PATH=${PWD}/packagecompiler/Project.toml:@stdlib ${JULIA} -e 'using Pkg; Pkg.instantiate()'packagecompiler/Project.toml:
	mkdir -p packagecompiler
	JULIA_LOAD_PATH=${PWD}/packagecompiler/Project.toml:@stdlib ${JULIA} -e 'using Pkg; Pkg.add("PackageCompiler")'packagecompiler/precompile_statements.jl: Manifest.toml bin/julia
	TMPDIR=$(shell mktemp -d) && \
	cd $${TMPDIR} && \
	JULIA_LOAD_PATH=: ${JULIA} -e 'using Pkg; Pkg.generate("Example")' 2> /dev/null && \
	cd Example && \
	JULIA_LOAD_PATH=$${PWD}:@stdlib ${JULIA} -e 'using Pkg; Pkg.add(["JSON", "fzf_jll", "Random", "Zlib_jll"])' 2> /dev/null && \
	JULIA_LOAD_PATH=$${PWD}:@stdlib ${JULIA} -e 'using Pkg; Pkg.precompile()' 2> /dev/null && \
	echo "$$PACKAGE_CONTENT" > src/Example.jl && \
	JULIA_TRACE_COMPILE=1 nvim src/Example.jl && \ # NOTE: You may need to check that neovim is correctly on your path
	rm -rf $${TMPDIR}bin/julia:
	mkdir -p bin
	echo "$$JULIA_SHIM" > $@
	chmod +x $@clean:
	rm -rf $(SYSIMAGE) packagecompiler bin.PHONY: clean defaultexport JULIA_SHIM
define JULIA_SHIM
#!/bin/bash
JULIA=${JULIA}
if [[ $${JULIA_TRACE_COMPILE} = "1" ]]; then
    exec $${JULIA} --trace-compile=${PWD}/packagecompiler/precompile_statements.jl "$$@"
elif [[ -f ${PWD}/$(SYSIMAGE) ]]; then
    exec $${JULIA} --sysimage=${PWD}/$(SYSIMAGE) "$$@"
else
    exec $${JULIA} "$$@"
fi
endefexport PACKAGE_CONTENT
define PACKAGE_CONTENT
# This file is opened in neovim with a LanguageServer.jl process that records Julia
# compilation statements for creating a custom sysimage.
#
# This file has a bunch of linter errors which will exercise the linter and record
# statements for that. When the diagnostic messages corresponding to those errors show up in
# the buffer the language server should be ready to accept other commands (note: this may
# take a while -- be patient). Here are some suggestions for various LSP functionality that
# can be exercised (your regular keybindings should work):
#
#  - :lua vim.lsp.buf.hover()
#  - :lua vim.lsp.buf.definition()
#  - :lua vim.lsp.buf.references()
#  - :lua vim.lsp.buf.rename()
#  - :lua vim.lsp.buf.formatting()
#  - :lua vim.lsp.buf.formatting_sync()
#  - :lua vim.lsp.buf.code_action()
#  - Tab completion (if you have set this up using LSP)
#  - ...
#
# When you are finished, simply exit neovim and PackageCompiler.jl will use all the recorded
# statements to create a custom sysimage. This sysimage will be used for the language server
# process in the future, and should result in almost instant response.module Exampleimport JSON
import fzf_jll
using Random
using Zlib_jllfunction hello(who, notused)
    println("hello", who)
    shuffle([1, 2, 3])
   shoffle([1, 2, 3])
    fzzf = fzf_jll.fzzf()
    fzf = fzf_jll.fzf(1)
    JSON.print(stdout, Dict("hello" => [1, 2, 3]), 2, 123)
    JSON.print(stdout, Dict("hello" => [1, 2, 3]))
    hi(who)
    return Zlib_jll.libz
endfunction world(s)
    if s == nothing
      hello(s)
  else
      hello(s)
  end
    x = [1, 2, 3]
    for i in 1:length(x)
        println(x[i])
    end
endend # module
endef
  1. Run make. This will set up a dummy project and launch nvim with julia recording everything that is compiled. Wait until the LanguageServer responds (there are a bunch of things in this dummy project that will result in warnings) and then run some LanguageServer commands, for example ::lua vim.lsp.buf.hover() to fetch documentation).

  2. Quit vim.

  3. PackageCompiler will now build a custom languageserver.so sysimage.

  4. Enjoy the Julia LSP!

References

References

Discussion: