Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mikesmullin/nvi
Very opinionated Node.JS VI clone
https://github.com/mikesmullin/nvi
Last synced: about 1 month ago
JSON representation
Very opinionated Node.JS VI clone
- Host: GitHub
- URL: https://github.com/mikesmullin/nvi
- Owner: mikesmullin
- Created: 2013-08-03T20:23:10.000Z (over 11 years ago)
- Default Branch: production
- Last Pushed: 2013-08-19T19:16:35.000Z (over 11 years ago)
- Last Synced: 2024-11-05T08:49:42.860Z (2 months ago)
- Language: CoffeeScript
- Size: 664 KB
- Stars: 50
- Watchers: 5
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Very opinionated Node.JS VI clone
This will become my dream collaborative editor.
**WARNING:** Its in a very ALPHA state right now. Contributions welcome.
![Screenshot](https://raw.github.com/mikesmullin/nvi/development/docs/screenshot.png)
## Vision:
We're taking the best parts of Vim:
* 256-color text-based user interface
* works locally via terminal incl. tmux
* works remotely via ssh
* modes
* buffers
* block edit
* macros
* mouse support
* plugins:
* syntax highlighting
* nerdtree
* ctags
* taglistand making them better:
* collaborative editing
* multiple writers w/ colored cursors
* follow/professor mode
* type inference and code completion (coffeescript will be first)
* easily configured and modded via coffeescript and javascript
* unforgiving bias toward modern systems## Achieved Features:
* 256-color terminal text-based user interface
* tiled window management for buffers
* modes: COMBO, NORMAL, REPLACE, BLOCK, LINE-BLOCK, COMMAND
* connect multiple nvi sessions in host-guests configuration
* local unix and remote tcp socket support for pairing## Installation
```bash
npm install nvi -g
```## Usage
```bash
nvi # new file
nvi # existing file
```## Getting Started
Nvi modes are not Vim modes.
Nvi NORMAL is Vim INSERT.
Nvi COMBO is Vim NORMAL.
These mode names are less confusing to new users.
When you first run Nvi, you begin in Nvi NORMAL mode.
This is intended to provide new users with a sense of familiarity as it is conventional to nano or Notepad on first impression.
This is aided by default hotkey behaviors like:
* Esc: enter Nvi COMBO mode
* Ctrl+S: Save, enter Nvi COMBO mode
* Ctrl+Q: Prompt to save if any changes, then quit
* Ctrl+X: Cut selection to clipboard
* Ctrl+C: Copy selection to clipboard
* Ctrl+V: Paste clipboard## Beginning a Collaborative Editing Session
* Open `nvi` twice
* In the first Nvi, press `` to enter COMBO mode, type: `:listen`, and hit ``
* In the second Nvi, press `` to enter COMBO mode, type: `:connect`, and hit ``## Rambling Specification
only transmit files when they are needing to be opened, as they need to be opened,
and only the parts of the file that need to be rendereddetect changes to files on disk using mtime
and auto-sync those to the groupdetect an out-of-sync state (what is hash of chars on line RAND characters RANDx - RANDY?) if its not the same then resend the document
buffer is closely tied to the view
it only fetches enough bytes to fill the view
it grabs chunks equal to the current view's line in bytesbuffer is aware of how many views are using it
before it seeks the file for more data,
it determines which stripes are being used across all views
it considers overlapping views
and only fetches each line it needs once, in order from first byte to last byte in file
and and when it fetches an area of the buffer that is overlapping in the view render
it updates both views not one then again for the otherfor my collaborative mode,
the guests will not receive mirror copies of the entire directory
the host will share the directory skeleton for the current directory
and recursively but only upon a watch request
watchers will receive push notification from the host (e.g. file added or removed under watchdir)any file a client requests to open, the client, and all other clients will be aware of
client will request the file from host
host will broadcast to other clients that the client has opened the file
and when it has closed the filehost will spoon-feed data to the clients like a buffer would
so the clients only get the data they need to look at that momentalso, diffs are only broadcasted within the context of the all clients collective views, as well
if a file changes on the host system
then we only broadcast that change if it is within the overlapped views of clientsthis is very similar to how ssh vim tmux session would go, except we now have multiple cursors
cursor position updates will be notified (client->host) and rebroadcasted (others) with a throttle
but diffs will not be based on cursor position and exact keypress matches
they will be actual patch diffs in a minimalist formatand what about binary files?
these will not attempt to be diffed
on modification, the latest (mtime) copy overrides everyone else's
(clients will need to have a filesystem mtime watch too)
the process for updating a binary file as a client would be:
connect
right-click > download the file or folder
modify the file locally
mtime inotify triggers guests to receive the bin file as well
any new files created (atime) get auto-shared to the host
client can be asked to resolve merge conflictsits basically like a rapid git session
except i don't like all the assumptions made above re. bin files
i think if the client wants to share a new file, it should be explicit, at least for now
and let's see if it becomes a hassle
or if a client wants to modify an existing binfile, it should also be explicit
because in reality these will change a lot (e.g. during a photoshop editing session)
and we really only care about the final result
and most likely the group will want to talk it over and approve it before accepting it anywayit would be cool if it had git support so that team commits could be saved with attribution for the authors involved when the commit was made
and so the commits only happened on the host side
same with pushesNO that silly git diff is not applicable; it doesn't resolve changes on the same line
so i need my own
i think instead i will just translate the cursor/block edit operations to binary opcodesbasically whatever you can do with a block, there's an opcode for that
so shift selection one character right, shift selection one character left (unlikely use case)
duplicate selection up/down
delete selection
insert before selection
insert after selection
move selection up/down
backspace/delete x characters
insert "literal" characters; assume we are working on an ascii file always
pretty confident that's a safe assumption; collaborative editing binary files
will have their own app and experience
move cursor absolute x, y or relative -x, -ywhen syncing hydrabuffers / views
clients will write changes to disk, without filler
as the view scrolls up/down the file will be prepended/appended
until the file is completeif a user changes a file outside of nvim which they don't have a complete dataset for
hmmok getting too fancy here / prematurely optimizing / overengineering
let's K.I.S.S.we'll transfer the whole ascii file to clients as they request to view it
and then we'll work to keep it in sync from there
its really not that much data to transferexcept am i solving anything the other way around?
a lot of times sync errors happen while users are editing in the areas
we aren't currently watching for changes ini kind of like my ssh-vim-like approach
ok so in that vain, the client does not buffer or store ANY data locally
this way it cannot possibly go out of sync
if it does, they just close/reopen the file to get back in sync
sync will happen on a per-view basissame as if you opened a file and someone changed it while you were reading it
this is also good because it functionally reinforces the idea of
requiring clients to check-in new files or binary file changes
by issuing special commands to have them imported to the hostand then with that i can refuse to render binary files altogether
just display an error like 'binary files unsupported; utf8 text only'so if someone wanted to edit a binary file in this flow, they would
have to issue a special command to fetch the binfile
then edit it locally
then issue a special command to upload the binfile
thus greatly reducing incidental sync errors
and unnecessary data transferfor a view statusbar just show:
relative path, bold filename
dont show line endings like 'unix | mac | win' thats pointless
instead highlight whitespace aggressively and with favoritism for unix
show percentage of file remaining
and cursor x:y posshow treeview directory structure
i'll have to implement this too because it needs remote supportthe modes i'll implement will be:
NORMAL except i'll call it COMBO
INSERT except i'll call it NORMAL
REPLACE i'll keep this the same
V-LINE except i'll call it LINE-BLOCK
V-BLOCK exept i'll call it BLOCKtreat views like a tiling window manager
create a Tab object like tmux-style tabs
tabs will have user's names in collab modeso it starts with one tab and one view
you can never have fewer than one view open
if you do its just resetting the file to an untitled in-memory bufferwhen you add another view
you can only do so by using a specific key combination / cmd
to indicate a vsplit or hsplit or new tabso when this happens, the default is to divide the tab 50/50
then the user can click and drag to resize from therea view can never be less than 1 char high
and 1 char wide;
for every line that isnt wrapped but is cutoff by a short viewport, a > is ending that lineopening more than one tab creates a 1char high tabbar at top of the screen
i may split View into ReadOnlyView extended by EditableView
HydraBuffer doesn't need to know about cursors
the view is always the one i keep landing in
its the one that colors the cursor
and shows it moving around the view
it has really nothing to do with the buffer
if a cursor moves off of a view, does it matter? (if a tree falls in the forest, does it make a sound?)
no.so there's a difference between a stream and a buffer here
the host is going to need to open a buffer
this also means that the entire file that is being edited needs to
be able to fit within the available ram memory
if we are going to save it back to the disk whole again
this is all a limitation of not being able to write in the middle of a file
hmm
i mean technically that's difficult because if anyone could do that
without a lock on the file
then you'd never know where you last left off
in order to insert your changes
plus even if a fancy filesystem supported this
you'd still have to implement your own layer on top of that
like a .vdi or .vmdk file
to make it fully compatible with other filesystems e.g., windows, sftp, nfs, etc.the traditional options i have at my disposal are: writeFile (total replacement) and appendFile (write to end only)
so i can only use writeFile()
which means i have to pass the entire file
which means the entire file must fit in memory
which means buffering only the maximum rendered by the views in memory is pointlesswell unless i get really creative with diffs
for example, if while using a readStream, i notate the offset location
and i assume that the file had a lock on it (could make my own .*.lck files)
then i would never need the whole file in memory
i could just replay chunks from the original file up to the point of my stream offset location
then write my modified contents
and write chunks of all this to a temporary file on disk (/tmp/nvi-filename.tmp)
and then mv the new file over the top of the oldi could get even more crafty and only store in-memory my changes and the location
that they occurred at
and just keep those in-memory until a save action is requested
then do the above replay-and-merge flushing-chunks-to-tmp-disk strategythis way we can collaborate on huge text files, if we want, too
but on average it will reduce the amount of data needing to be thrown aroundso what format would my in-memory buffer take?
i could use the diff/patch format
and record line-by-line changes
except some really long lines could mess with us there110,10,115:abcdefgh
bah, i'm making it too complicated. i can be a bad-ass later. its getting late.
so i'll just use buffers for now
and always assume that the file will fit in memory
and that transmitting a file-at-a-time from host to client is fast enoughadd funny -- COMBO X10! --
counter to the statusbar as each combo is executed within a 1 sec delayno matter where you are in vim, you should always see the current mode
in a reliable place, because modes are so fundamental to vi operation
my modes will be color coded like the vi powerline theme
they will always appear in the view statusbar
except for COMMAND mode which will appear in the statusbar
this will make it very obvious what is going onadditionally, my vim will begin the user in INSERT mode
except remember i'm calling it NORMAL mode
so that when they start out its most like what they might
be famliar with: notepad, and they hvae to hit ESC to get to
COMBO mode (what i call the vi NORMAL mode)make it so all @w and @h are the outer dims
and any decorators like tabs, statusbars, etc. must fit inside that
and relative to their parent dims who controls theminstead of doing a layered TUI that redraws layers ontop of layers
if i need to get more efficient
i could make a pixel/char registry which is basically a matrix of function pointers
the first cell points to a function that controls its appearance
and subsequent cells point back to the id of the first cell of which they also belong
then i just loop through the cells to determine which functions need to execute to redraw
the screenits important to differentiate between the Terminal.cursor (there is only one; used to draw the screen)
and the ViewCursor (there can be one or more per view; used by users to traverse a view)
additionally between a ViewCursor that is the currently possessed (uses Terminal.cursor)
and a ViewCursor that is not possessed (unfocused, or someone else's cursor; drawn manually)fix the command line disappearing on resize
next time i implement bksp, del, etc. see if i can modularize the custom readline functionality i added to the command barde-duplicate information presented in vim statusbars;
treat Window.status_bar as global log notifications like "read 144B, 23C, 104L in 0.23sec"
treat View.status_bar as stateful mode and cursor position informationi'll improve on vim window management
when the terminal resizes, instead of remembering exact widths and collapsing containers on the right
i'll remember relative widths in percentages and try to retain the aspect ratio of each view
when the terminal resizesdisplay errors such as 'Not enough room' in the Window.status_bar
support displaying errors in an obnoxious white-on-red color scheme## TODO most immediately:
* rendering multiple host and guest cursor movements
* arrow keys cursor movement constrained by view text depending on mode* make it draw a dividing line
* make the dividing lines draggable to hresize and vresize* make view statusbar toggle focus with click
* also cursor focus toggle with click
* and render both cursors in same view
* hmm maybe also make it so view status bar only appears if there is more than one?* lclick to place cursor
* lclick+drag to highlight
* double-lclick to highlight word
* triple-lclick to highlight line