Skip to content

Conversation

HiPhish
Copy link
Contributor

@HiPhish HiPhish commented Oct 9, 2018

The implementation is extremely crude and certainly missing tons of cleanup, but it works on paper.

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 9, 2018

The current implementation does change the buffer, but the user needs to switch to another window first to see the changes. I'm certain that just changing the buffer the window points to cannot be all there is to it. The do_buffer function is nearly 300 lines long, a good chunk of it is just figuring out what it needs to do based on the arguments, but there is also a lot of cleanup.

I have looked into the implementation of nvim_set_current_buf, but its use of do_buffer assumes that we want to set the buffer in the current window, not just any arbitrary window, so I cannot use its implementation.

As a side-note, nvim_set_current_buf could then be implemented on top of nvim_win_set_buf in VimScript as

function! nvim_set_current_buf(buffer)
    call nvim_win_set_buf(nvim_get_current_win(), a:buffer)
endfunction

Opinions on this by the Neovim maintainers?

@bfredl
Copy link
Member

bfredl commented Oct 9, 2018

I have looked into the implementation of nvim_set_current_buf, but its use of do_buffer assumes that we want to set the buffer in the current window, not just any arbitrary window, so I cannot use its implementation.

For better and worse, a lot of API (and recent vimL functionality) is implemented by changing curwin and/or curbuf, call an existing function that assumes curwin/curbuf, and then restoring the old current values. So I wouldn't be this quick to conclude that do_buffer can't be used.

@justinmk
Copy link
Member

justinmk commented Oct 9, 2018

but the user needs to switch to another window first to see the changes.

Most likely that indirectly runs do_buffer, accidentally doing the necessary fixups.

nvim_set_current_buf, but its use of do_buffer assumes that we want to set the buffer in the current window

Why not set the current window temporarily, then unwind it? That's the usual way. Disabling autocommands etc makes it fast.

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 9, 2018

For better and worse, a lot of API (and recent vimL functionality) is implemented by changing curwin and/or curbuf, call an existing function that assumes curwin/curbuf, and then restoring the old current values.

Why not set the current window temporarily, then unwind it? That's the usual way.

And for a moment there I though I could get away from Vim's ritualistic code by going to the actual source. It's rituals all the way down, isn't it?

@justinmk justinmk closed this Oct 9, 2018
@justinmk justinmk reopened this Oct 9, 2018
@justinmk
Copy link
Member

justinmk commented Oct 9, 2018

(oops)

Well, the insides can be ugly, but we hide the ugly behind a nice API. Better to get it right before worrying about elegance.

One could change do_buffer to take a buf_T parameter in the future, perhaps.

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 9, 2018

Well, the insides can be ugly, but we hide the ugly behind a nice API. Better to get it right before worrying about elegance.

You are right. My problem with ritualistic code is that it is easy to miss part of the ritual and have things work fine for the most part, but not always. Take for example the updated code I just pushed, it works well, but the error handling is messed up. Looking at the function, it would have been better if I had just written it straight up in VimScript instead.

function! nvim_win_set_buf(win, buf)
    let old_win = nvim_get_current_win();
    call nvim_set_current_win(a:win)
    call nvim_set_current_buf(a:buf)
    call nvim_set_current_win(old_win)
endfunction

I had hoped that writing this directly in the source would avoid triggering fifty side effects from switching windows, but that would require digging even deeper into Neovim's core code and going down to the bare bytes. At this point I'm not sure if there is even a point in continuing with this anymore.

@bfredl
Copy link
Member

bfredl commented Oct 10, 2018

I had hoped that writing this directly in the source would avoid triggering fifty side effects from switching windows, but that would require digging even deeper into Neovim's core code and going down to the bare bytes. At this point I'm not sure if there is even a point in continuing with this anymore.

No need to be so defeatist this quickly. "Ritual" or not, other API functions have managed to avoid filthy side effects without too much raw byte banging. If we look at nvim_win_set_option (the arguably most side effect-full operation so far), and dig into the implementation in api/private/helpers.c, we find that it uses switch_win/restore_win, which is purposely built for this save/restore situation. It will probably work better that goto_tabpage_win which does implement user visible switching, and indeed would be equivalent to the vimscript visible functions.

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 10, 2018

@bfredl You are right, I guess I'm just in a bad mood and threw in the towel too early. switch_win/restore_win seems to do the trick, looking for comments on my implementation.

@justinmk
Copy link
Member

Much better! Just needs a couple tests and address the TODO. Might want to add another sentence or two in the docstring explaining the motivation or an example.

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 10, 2018

I noticed something weird:

" There are two windows: 1000 and 1001, I am in window 1001 at the moment
call nvim_win_set_option(1000, 'stl', 'Hello world')
" The above changes the status line in the other window, as expected
call nvim_win_set_buf(1000, 2)
" The above changes the buffer, as expected

Now both windows show the same status line. Switching back to the original buffer restores my Hello world status line. It is as if the option is local to the buffer, not the window. Is this correct? The same happens if I stay inside the window I want to change and use nvim_set_current_buf().

Just needs a couple tests and address the TODO.

Sure thing, I just wanted to get this to work before applying the polish. What should be the API level? 6?

@justinmk
Copy link
Member

It is as if the option is local to the buffer, not the window. Is this correct?

Not sure. How does it behave with VimL?

What should be the API level? 6?

See NVIM_API_LEVEL in CMakeLists.txt

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 11, 2018

Not sure. How does it behave with VimL?

That was VimL. Nevermind anyway, I looked into how options are set:

// File src/nvim/private/helpers.c
// Omitting irrelevant code
static void set_option_value_for(char *key,
                                 int numval,
                                 char *stringval,
                                 int opt_flags,
                                 int opt_type,
                                 void *from,
                                 Error *err)
{
  switch (opt_type)
  {
    case SREQ_WIN:
      switch_win(&save_curwin, &save_curtab, (win_T *)from, win_find_tabpage((win_T *)from), false) 
      set_option_value_err(key, numval, stringval, opt_flags, err);
      restore_win(save_curwin, save_curtab, true);
      break;
    case SREQ_BUF:
      switch_buffer(&save_curbuf, (buf_T *)from);
      set_option_value_err(key, numval, stringval, opt_flags, err);
      restore_buffer(&save_curbuf);
      break;
  }
}

There is not really such a thing as a "window-local" option, nvim_win_set_option(n, 'foo', 'bar') is really just nvim_buf_set_option(nvim_win_get_buf(n), 'foo', 'bar').

@bfredl
Copy link
Member

bfredl commented Oct 11, 2018

Note the docs claim statusline is the one and only "global-local window option" (i e it doesn't be have like any other window option), so do expect weird behaviour with it, especially in the API. It seems nvim_win_set_option(1000, 'stl', 'Hello world') also sets the buffer local value, so when you visit the buffer in a new window that hasn't seen the buffer before, the local value gets copied. It might be worth trying with some other window option, like winhl.

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 11, 2018

It might be worth trying with some other window option, like winhl.

I tried it and I see the same behaviour: the "window-local" options switches along with the buffer. It makes sense if you look at the code in my previous post: in both cases set_option_value_err is called with the same arguments, the only difference is the part of the ritual beforehand and after. For a window we switch to the window in question, thus also switching to its buffer, and for a buffer we switch straight to the buffer in question.

(I hate how Vim as taught me to press CTRL-W to delete the current word, I always end up closing a browser window after having almost finished a post)

@bfredl
Copy link
Member

bfredl commented Oct 11, 2018

Ahyes, it likely behaves like :setl in both cases, but for a window option it should rather work like plain :set, except maybe for stl (which will remain an unhappy child in any case)

bfredl and others added 13 commits October 12, 2018 00:30
The old code could lead to a memory error in the following situation:

0. The previous cursor position was row 50 since before, on a grid
larger than 50 rows.
1. grid_resize changes the grid height to 40, and invalidly assumes the
resize moved the physical cursor to row 0
2. Some event used a operation that could move the cursor (such as clear), and
then reset the cursor to the "true" position row 50 (pointless after #8221, but
I forgot to remove it)
3. raw_line/cheap_to_print is invoked, and tries to inspect the grid at
row 50 (memory error)
4. grid_cursor_goto would have been called at this point, and set a
valid cursor position 0-39.
This was caused by cursor position being invalid right
after tui_grid_resize, which is now fixed
Problem:    Function to set terminal name is too long.
Solution:   Refactor the function.  Fix typo in test.
vim/vim@69e0569
Problem:    resolve() was not tested with a symlink cycle.
Solution:   Add a test. (Dominique Pelle, closes vim/vim#3513)
vim/vim@2610990
Problem:    MS-Windows: executable() is not reliable.
Solution:   Use $PATHEXT properly. (Yasuhiro Matsumoto, closes vim/vim#3412)
vim/vim@8295666
Windows has "Read and execute" permission via ACL
but nvim and libuv do not support ACL.
Windows does not support the executable bit in chmod-style permissions
but it is safe to assume that if the file exists and is readable,
then it is most likely executable.
This means that win.ini and shell32.dll are "executable"
because they exist, are readable, and are in PATH.

PATHEXT does not affect the executable permission of a file;
it exists to run files on the shell while omitting the file extension.
Assume that PATHEXT is intended for cmd.exe only
because powershell can execute powershell files (ie. *.ps1)
without changing PATHEXT or related cmd.exe environment variable.

In the future, nvim should check the outputs of 'assoc' and 'ftype',
cmd.exe internal commands, or check the registry.
Powershell can be used for ACL if C++/C# API is too difficult to use.
…system

Problem:    Test_executable fails when there is a dog in the system.
Solution:   Rename the dog. (Hirohito Higashi)
vim/vim@a05a0d3
Problem:    "simalt ~x" in .vimrc blocks swap file prompt.
Solution:   Flush buffers before prompting. (Yasuhiro Matsumoto,
            closes vim/vim#3518, closes vim/vim#2192)
vim/vim@798184c
Problem:    Autocmd test fails.
Solution:   Do call inchar() when flushing typeahead.
vim/vim@6a2633b
)

Problem:    Deleting in a block selection causes problems.
Solution:   Check the length of the line before adding bd.textcol and
            bd.textlen. (Christian Brabandt, closes vim/vim#2825)
vim/vim@35e802e
Problem:    Error in return not caught by try/catch.
Solution:   Call update_force_abort(). (Yasuhiro Matsomoto, closes vim/vim#2483)
vim/vim@fabaf75
janlazo and others added 3 commits October 12, 2018 00:30
…after '<,'>

Problem:    One character cmdline abbreviation not triggered after '<,'>.
Solution:   Skip over the special range. (Christian Brabandt, closes vim/vim#2320)
vim/vim@5e3423d
…#9104)

Problem:    Cannot cleanup before loading another colorscheme.
Solution:   Add the ColorSchemePre autocommand event.
vim/vim@60a6836
@HiPhish HiPhish changed the title [WIP/RFC] Implement API function nvim_win_set_buf [RFC] Implement API function nvim_win_set_buf Oct 11, 2018
@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 11, 2018

I have now written a test for the new function and cleaned up linter complaints. Requesting consideration for merging.

@marvim marvim added the RFC label Oct 11, 2018
This new API method allows setting the buffer of a window based on the
window ID. It complements the existing `nvim_win_get_buf`.
@bfredl
Copy link
Member

bfredl commented Oct 12, 2018

History looks a bit weird. perhaps rebase the latest commit on master?

@@ -113,7 +113,7 @@ set(NVIM_VERSION_PATCH 2)
set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers

# API level
set(NVIM_API_LEVEL 5) # Bump this after any API change.
set(NVIM_API_LEVEL 6) # Bump this after any API change.
Copy link
Member

@bfredl bfredl Oct 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to bump this, 5 is already pre-release. One can always just use the latest level in a PR; if it needs to be bumped, CI (or make functionaltest locally) will tell you.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this wasn't fixed. Did so in the merge.

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 12, 2018

Is the history better now? GitHub shows all those other commits from master for some reason.

@bfredl
Copy link
Member

bfredl commented Oct 12, 2018

I think you merged instead, which doesn't remove these commits. Reset to
c55287a again and use git rebase -i master. Hopefully it would only show one commit (implement API method), but if the rest remain, just delete those lines.

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 13, 2018

I followed the instructions, but the history is still messed up. Could it be that I have messed up my master when I did git pull upstream master? I could do

git reset --hard 384770556ba02433904baf78f499b9bb48010040

on the master branch to get back to the last time before my git pull and then do things the right way (whatever the right way might be).

@justinmk justinmk changed the title [RFC] Implement API function nvim_win_set_buf [RFC] API: Implement nvim_win_set_buf Oct 14, 2018
@justinmk
Copy link
Member

justinmk commented Oct 14, 2018

@HiPhish I force-pushed to your branch. I used git merge --squash to get rid of the unnecessary commits:

git checkout upstream/master
git merge --squash refs/pull/upstream/9100
git commit
git push -f https://github.com/HiPhish/neovim HEAD:api-win-set-buf

@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 15, 2018

@justinmk Thank you. What is the Right Way™ of keeping up to data with master? This sort of thing keeps happening to me. Here is what I usually do:

git checkout master
git pull upstream master  # upstream is the official repository
git checkout my-feature-branch
git rebase master

@HiPhish HiPhish changed the title [RFC] API: Implement nvim_win_set_buf [RDY] API: Implement nvim_win_set_buf Oct 15, 2018
@marvim marvim added RDY and removed RFC labels Oct 15, 2018
@justinmk
Copy link
Member

justinmk commented Oct 15, 2018

@HiPhish No need to git checkout master, instead use git fetch --all (--prune is also a good idea IMO).

Here's my habit:

git fetch --all --prune
git rebase -i --autosquash upstream/master

Could also use the git ri alias from CONTRIBUTING.md.

git fetch --all --prune
git ri upstream/master

api_set_error(err,
kErrorTypeException,
"Failed to switch to window %d",
window);
Copy link
Member

@justinmk justinmk Oct 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also test the failure modes (including the !win || !buf case above). In the past we've had hidden bugs until we added test cases for failure modes.

(Edit: I'm not sure when Failed to switch to window % error would happen, it might not be possible to test that.)

It's easy to test the error cases using expect_err(). See for example:

expect_err('Invalid method: bogus$',

Example:

expect_err('error message', window, 'set_buf', 99942, 23423)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added tests for invalid window- and buffer ID, but as you said, I have no idea how to make the swap intentionally fail.

This new API method allows setting the buffer of a window based on the
window ID. It complements the existing `nvim_win_get_buf`.
@HiPhish
Copy link
Contributor Author

HiPhish commented Oct 23, 2018

bump Anything else that needs to be done?

@justinmk justinmk closed this in b1aaa0a Nov 25, 2018
@justinmk justinmk removed the RDY label Nov 25, 2018
@justinmk
Copy link
Member

Merged after fixing indentation and API level. Thanks @HiPhish ,!

justinmk added a commit to justinmk/neovim that referenced this pull request Dec 30, 2018
Besides the "visible" improvements, this release features numerous
internal improvements to the UI/screen code and test infrastructure.

Numerous patches were merged from Vim, which are not mentioned below.

FEATURES:

07ad5d7 clipboard: Support custom VimL functions neovim#9304
725da1f neovim#9401 win/TUI: Improve terminal/console support
7a8dadb neovim#9077 startup: Use $XDG_CONFIG_DIRS/nvim/sysinit.vim if it exists
feec926 neovim#9299 support <cmd> mapping in more places
0653ed6 neovim#9028 diff/highlight: Show underline for low-priority CursorLine
bddcbbb signs: Add "numhl" argument neovim#9113
05f9c7c clipboard: support Wayland (neovim#9230)
14ae394 neovim#9052 TUI: add support for undercurl and underline color
4fa3492 neovim#9023 man.vim: soft (dynamic) wrap neovim#9023

API:

8b39e4e neovim#6920 API: implement object namespaces
b1aaa0a API: Implement nvim_win_set_buf() neovim#9100
8de87c7 neovim#8180 API: virtual text annotations (nvim_buf_set_virtual_text)
2b9fc9a neovim#8660 API: add nvim_buf_is_loaded()
    API: buf_get_lines, buf_line_count handle unloaded buffers
88f77c2 API: nvim_buf_get_offset_for_line
94841e5 API/UI: neovim#8221 ext_newgrid, ext_hlstate
    (use line-based rather than char-based updates)

UI

b5cfac0 neovim#8806 TUI: use BCE again more often, (smoother resizes/scrolling)
77b5e9a neovim#9315 screen: add missing status redraw when redraw_later(CLEAR) was used
5f15788 TUI: clip invalid regions on resize (neovim#8779), fixes neovim#8774
c936ae0 neovim#9193 TUI: improvements for scrolling and clearing
f204274 neovim#9143 UI: disable clearing almost everywhere
f4b2b66 neovim#9079 TUI: always use safe cursor movement after resize
d36afaf neovim#9211 ui_options: also send when starting or from OptionSet
67f80d4 TUI: Avoid reset_cursor_color in old VTE neovim#9191
e55ebae neovim#9021 don't erase screen on `:hi Normal` during startup
c5790d9 neovim#8915 TUI: Hint wrapped lines to terminals.

FIXES:

231de72 RPC: turn errors from async calls into notifications
907ad92 TUI: Restore terminal title via "title stacking" (neovim#9407)
cb76a8a genappimage: Unset $ARGV0 at invocation neovim#9376
b48efd9 neovim#9347 TUI: FreeBSD: Improve support for BSD vt console
c16529a TUI: Konsole 18.07.70 supports DECSCUSR (neovim#9364)
aec096f os/lang: use the correct LC_NUMERIC also for OS X
5fee0be provider: improve error message (neovim#9344)
3c42d7a TUI: alacritty supports set_cursor_color neovim#9353
7bff9a5 TUI: Alacritty supports DECSCUSR (neovim#9048)
57acfce macOS: infer primary language if $LANG is empty neovim#9345
bc132ae runtime/syntax: Fix highlighting of augroup contents (neovim#9328)
715fdfe neovim#9297 VimL/confirm(): Show dialog even if :silent
799d9c3 clipboard: Prefer xclip (neovim#9302)
6dae777 provider/nodejs: fix npm,yarn detection
16bc1e9 neovim#9218 channel: avoid buffering output when only terminal and no callbacks are active
72fecad neovim#8804 Fix crash in lang_init() on macOS if lang_region = NULL
d581398 ruby: detect rbenv shims for other versions (neovim#8733)
e568ac7 neovim#9123 third-party/unibilium: Fix parsing of extended capability entries
c4c74c3 jobstart(): Fix hang on non-executable cwd neovim#9204
1cf50cb provider/nodejs: Simultaneously query npm and yarn neovim#9054
6c496db undo: Fix infinite loop if undo_read_byte returns EOF neovim#2880
f8f8357 neovim#9034 'swapfile: always show dialog'

CHANGES:

c236e80 neovim#9024 --embed: wait for UI unless --headless
180b50d neovim#9248 python: 'neovim' module was renamed to 'pynvim'
2000b6a neovim#8589 VimL: Remove legacy aliases "v:errmsg", "v:shell_error", "v:this_session"
deb18a0 defaults: background=dark neovim#2894 (neovim#9205)
c1187d4 defaults: win: 'shellpipe' for cmd.exe (neovim#8827)
justinmk added a commit to justinmk/neovim that referenced this pull request Dec 31, 2018
Besides the "visible" improvements, this release features numerous
internal improvements to the UI/screen code and test infrastructure.

Numerous patches were merged from Vim, which are not mentioned below.

FEATURES:

07ad5d7 clipboard: Support custom VimL functions neovim#9304
725da1f neovim#9401 win/TUI: Improve terminal/console support
7a8dadb neovim#9077 startup: Use $XDG_CONFIG_DIRS/nvim/sysinit.vim if it exists
feec926 neovim#9299 support <cmd> mapping in more places
0653ed6 neovim#9028 diff/highlight: Show underline for low-priority CursorLine
bddcbbb signs: Add "numhl" argument neovim#9113
05f9c7c clipboard: support Wayland (neovim#9230)
14ae394 neovim#9052 TUI: add support for undercurl and underline color
4fa3492 neovim#9023 man.vim: soft (dynamic) wrap neovim#9023

API:

8b39e4e neovim#6920 API: implement object namespaces
b1aaa0a API: Implement nvim_win_set_buf() neovim#9100
8de87c7 neovim#8180 API: virtual text annotations (nvim_buf_set_virtual_text)
2b9fc9a neovim#8660 API: add nvim_buf_is_loaded()
    API: buf_get_lines, buf_line_count handle unloaded buffers
88f77c2 API: nvim_buf_get_offset_for_line
94841e5 API/UI: neovim#8221 ext_newgrid, ext_hlstate
    (use line-based rather than char-based updates)

UI

b5cfac0 neovim#8806 TUI: use BCE again more often, (smoother resizes/scrolling)
77b5e9a neovim#9315 screen: add missing status redraw when redraw_later(CLEAR) was used
5f15788 TUI: clip invalid regions on resize (neovim#8779), fixes neovim#8774
c936ae0 neovim#9193 TUI: improvements for scrolling and clearing
f204274 neovim#9143 UI: disable clearing almost everywhere
f4b2b66 neovim#9079 TUI: always use safe cursor movement after resize
d36afaf neovim#9211 ui_options: also send when starting or from OptionSet
67f80d4 TUI: Avoid reset_cursor_color in old VTE neovim#9191
e55ebae neovim#9021 don't erase screen on `:hi Normal` during startup
c5790d9 neovim#8915 TUI: Hint wrapped lines to terminals.

FIXES:

231de72 RPC: turn errors from async calls into notifications
907ad92 TUI: Restore terminal title via "title stacking" (neovim#9407)
cb76a8a genappimage: Unset $ARGV0 at invocation neovim#9376
b48efd9 neovim#9347 TUI: FreeBSD: Improve support for BSD vt console
c16529a TUI: Konsole 18.07.70 supports DECSCUSR (neovim#9364)
aec096f os/lang: use the correct LC_NUMERIC also for OS X
5fee0be provider: improve error message (neovim#9344)
3c42d7a TUI: alacritty supports set_cursor_color neovim#9353
7bff9a5 TUI: Alacritty supports DECSCUSR (neovim#9048)
57acfce macOS: infer primary language if $LANG is empty neovim#9345
bc132ae runtime/syntax: Fix highlighting of augroup contents (neovim#9328)
715fdfe neovim#9297 VimL/confirm(): Show dialog even if :silent
799d9c3 clipboard: Prefer xclip (neovim#9302)
6dae777 provider/nodejs: fix npm,yarn detection
16bc1e9 neovim#9218 channel: avoid buffering output when only terminal and no callbacks are active
72fecad neovim#8804 Fix crash in lang_init() on macOS if lang_region = NULL
d581398 ruby: detect rbenv shims for other versions (neovim#8733)
e568ac7 neovim#9123 third-party/unibilium: Fix parsing of extended capability entries
c4c74c3 jobstart(): Fix hang on non-executable cwd neovim#9204
1cf50cb provider/nodejs: Simultaneously query npm and yarn neovim#9054
6c496db undo: Fix infinite loop if undo_read_byte returns EOF neovim#2880
f8f8357 neovim#9034 'swapfile: always show dialog'

CHANGES:

c236e80 neovim#9024 --embed: wait for UI unless --headless
180b50d neovim#9248 python: 'neovim' module was renamed to 'pynvim'
2000b6a neovim#8589 VimL: Remove legacy aliases "v:errmsg", "v:shell_error", "v:this_session"
deb18a0 defaults: background=dark neovim#2894 (neovim#9205)
c1187d4 defaults: win: 'shellpipe' for cmd.exe (neovim#8827)
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.

5 participants