Skip to content

rdai324/rust_vim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

109 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rust-vim

Video Demo

The video demo can be viewed via the canva link below:

https://www.canva.com/design/DAG7nESdqIo/vjKd4K8wUZdbsKegVfCb7Q/watch?utm_content=DAG7nESdqIo&utm_campaign=designshare&utm_medium=link2&utm_source=uniquelinks&utlId=h4eb724a466

Video Slide Presentation

The video slide presentation can be viewed via the canva link below:

https://www.canva.com/design/DAG7jEwLHoQ/WzW-_BRhdsUn6g717dJnWw/watch?utm_content=DAG7jEwLHoQ&utm_campaign=designshare&utm_medium=link2&utm_source=uniquelinks&utlId=h9878677e9d

Final Report

Members

Name Student Number Email
Ray Dai 1006473086 ray.dai@mail.utoronto.ca
James He 1004118171 james.he@mail.utoronto.ca

Motivation

In the software engineering community, Vim has stood the test of time and proven itself as a classic code editor. When mastered, the editor offers enormous potential to boost coding productivity. Although it is relatively easy to get started with Vim, there is always more to learn - new motions, commands, registers, and workflows at large that unleashes even higher level of control and efficiency.

As software engineers, we aspire to continuously improve our skills. Therefore the motivations for this project are threefold. Firstly, we would like to improve our coding productivity by getting more proficient with Vim. We believe that implementing a stripped-down version of Vim, and understanding the internal architecture is an execellent way to take our Vim proficiency to the next level.

Secondly, we aim to gain practical experience with Rust programming. Through implementing a decently-sized software system, we aim to gain a deeper appreciation and understanding of Rust. Factoring in the timeline constraint, we think a stripped-down implementation of rust offers the ideal balance between technical complexity and project scope.

Lastly, to our knowledge, there does not exist a lightweight Vim-style CLI text editor with Language-Server-Protocol (LSP) and Copilot-style LLM integration for code autocompletion. Our project aims to be a starting point for addressing this gap. We aim to first develop a CLI text editor in Rust. Then, if time allows, we will implement LSP and LLM support and demonstrate popular features such as code autocompletion.

Objectives

The key objective of this project is to develop a CLI text editor. This means that the bare minimum features that must be implemented include a Terminal UI, the ability to manipulate (i.e. open, read, write, and close) text files, and the ability to read user input for editing text files. These are fundamental features that are required to establish bare minimum functionality of our text editor.

Side objectives include implementing key features inspired by vim. This includes implementing the following features, which are described in more detail in the Key Features section

  • A state machine for different modes of operation, including Normal Mode, Insert Mode, Command Mode, and a new Search Mode (separated from vim’s Command Mode)
  • A status bar at the bottom displaying the cursor’s location in the file, as well as user input and feedback messages for the user to read
  • Text soft-wrapping so that lines of text which exceed the width of the terminal window will be automatically wrapped around to a new line without inserting newline characters

Finally, there were two extra bonus objectives of this project. The first one is to implement Language Server Protocol (LSP) support for different coding languages, to allow for features such as keyword coloring, jump to definition, and even rudimentary code completion based on recent text edits. The second is to support LLM integration, allowing users to prompt a supported LLM platform to generate a proposed set of edits based on the user’s prompt and text file contents, which the user can then accept or reject. Unfortunately due to time constraints, these objectives were not completed, however, there are plans for potential future implementation using rust crates similar to llm-lsp (https://crates.io/crates/llm-lsp).

Key Features

As a CLI text editor, the following core features are supported for basic text editing functionality

  • The ability to both open and read existing files, as well as create new ones if the provided file name does not already exist
  • A cursor that the user can manipulate using arrow keys, in order to show where character insertion/deletion will take place.
    • The cursor is bound to regions of the terminal that correspond to the file's contents, even if the terminal window itself is shrunk by the user
      • Shrinking the terminal window will push the cursor upwards and/or to the left as needed to ensure it stays in a legal position for indexing file contents
    • The cursor snaps to the end of a shorter line when moving up/down between lines, rather than illegally occupy the empty space which does not correspond to file contents
    • The cursor ‘slips’ through extra columns of a wide character (notably emoticons and tab spaces) to prevent the cursor from illegally occupying the middle of a wide character, causing the editor to incorrectly index file contents.
    • The cursor changes shape in Insert Mode to allow users to add text to the beginning or end of a line.
  • The ability to scroll through lines of the text file if it exceeds the height of the terminal window.
    • File contents start scrolling upwards when the cursor reaches the bottom of the terminal window, and scroll downwards when the cursor reaches the top of the terminal window
    • Scrolling stops once the beginning and/or end of the file is reached, and briefly displays a message to the user if further scrolling is attempted
  • The ability to adapt to different terminal shapes and sizes, even if the user changes it during runtime.

Since this is a vim-inspired text editor, vim’s most important modes are also implemented to include the following:

  • Normal Mode for viewing
    • This is a heavily-stripped down version of vim’s Normal Mode, serving as a “hub” for transitioning between other modes, or otherwise just navigating the cursor and viewing file contents using the arrow keys.
  • Insert Mode for text editing
  • Command Mode for entering commands. The following commands are implemented
    • Write (:w)
    • Quit (:q)
    • Write-Quit (:wq)
    • Line Number Toggling (:num)
    • Line Deletion (:dd)
  • Search Mode for searching strings
    • Unlike vim, this was separated from the Command Mode for better code structuring and ease of use
    • If matches exist, they will then be highlighted until the user hits [Esc] in Normal Mode or begins a new search
    • Supports Regex searching

Hotkeys and navigation between modes is discussed in the user guide below. Other vim features also implemented in this app include a status bar at the bottom of the terminal window, and text soft-wrapping.

The status bar acts as a fundamental part of our app’s ui, as it displays key information such as the cursor’s location in the file, the current mode and important hot-keys, feedback/error messages, and the user input for commands and search queries. The cursor’s location is represented as a row, and a column, which corresponds to which line of the file, and which column of the file the cursor is currently located at.

Text soft-wrapping allows rust-vim to display lines of text which exceed the width of the terminal window, by automatically wrapping them around to a new line, without inserting extra newline characters. Our implementation of text wrapping is also robust enough to adapt to different terminal sizes, even if the user resizes the terminal during runtime. Text wrapping was implemented to help improve user experience, as the alternative would be to have long lines go out of the terminal window and add horizontal scrolling. By wrapping text instead, the true contents of the file are made more clear to the user, and there is less ambiguity about whether the line extends past the terminal window. For users that may prefer horizontal scrolling over text wrapping, a command to switch between wrapping text and horizontal scrolling may be implemented in the future.

Due to time constraints, the following vim modes, commands, and features will not be supported,

  • Visual Mode Highlighting
    • Copy-Paste
    • Mass-delete
  • Command sequencing in Normal Mode
  • Binary, Org, and Replace modes
  • Text undo/redo functionality

Finally, a quick-help pop-up was also implemented to give users a reference manual for how to use the commands and hotkeys provided. Users can view it from the Normal Mode by tapping [z], scroll through it with up/down arrow keys, and close it using [Esc].

User Guide

This section acts as a brief user guide of how to use the different features rust-vim provides. For instructions on how to compile, and then run rust-vim to open or create a file, please refer to the Reproducibility Guide.

Hotkey Summary

The Help Pop-up below shows a brief overall summary of the important hotkeys in each mode. Help Pop-up

Detailed explanations of each mode are provided in the corresponding sections below

Normal Mode

On startup, rust-vim will use the calling terminal window to display the contents of the file in Normal Mode, with the cursor starting in the top left of the screen at the first column of the first line of text. The Normal Mode is used for viewing the contents of the file, and to act as a ‘hub’ between the other modes. Users can move the cursor over file contents via the arrow keys. Moving the cursor to the top or bottom of the terminal window and continuing to move it up or down will cause the content in the terminal window to scroll, allowing users to view off-screen content if there is any. Scrolling stops once the beginning or end of the file is reached.

The following hotkeys are used to navigate to other modes from Normal Mode: [i] to enter Insert Mode and start editing the file contents [:] to enter Command Mode and start writing commands [/] to enter Search Mode and start writing a search query

Additionally, the user can also open up the quick-help pop-up using the [z] hotkey, to view a quick-reference user manual. The help pop-up’s contents can be scrolled using the up/down arrow keys, and users can return to Normal Mode using the [Esc] key.

Insert Mode

In Insertion Mode, the user can still move the cursor with the arrow keys just like in Normal Mode. To edit the file, simply type on the keyboard to insert characters to the right of the cursor’s current location. The cursor will then automatically move rightwards with whatever was typed, just like in traditional text editors. Use the [Enter] key to insert a new line, and the [Backspace] key to remove the character to the left of the cursor’s current location.

To exit Insert Mode and return to Normal Mode, simply tap the [Esc] key.

Command Mode

In Command Mode, the cursor is locked, and users can no longer move it with arrow keys. Instead, users can type in desired commands to be executed. rust-vim automatically records the user’s keystrokes, and displays them for reference in the status bar below the file contents.

Users can use the [Backspace] key to delete the right-most character of the command being typed, in case they make a mistake. Deleting all characters in this manner (including the [:] character used to enter Command Mode) will return users back to Normal Mode. Users can also use the [Esc] key to exit Command Mode prematurely without submitting a command, returning them back to Normal Mode.

Once the user has typed out a desired command to run, users can press the [Enter] key to submit and run the command, before returning to Normal Mode if the file was not closed. Implemented commands are shown below:

  • [:w] to write and save over the file without quitting rust-vim
  • [:q] to terminate rust-vim without writing to the file, and then restore the terminal window to its previous state before starting rust-vim
    • This command will first display a pop-up window asking the user to confirm their intention to quit without saving
    • Options can be selected with left/right arrow keys
    • Selected option can be confirmed using [Enter] key
    • Users can also hit [Esc] to cancel the command, closing the pop-up and returning them to Normal Mode
  • [:wq] to write and save over the file, terminate rust-vim, and then restore the terminal window to its previous state before starting rust-vim
  • [:num] to toggle whether rust-vim should also display line numbers to the left of the file contents
  • [:dd] to delete the current file line at the cursor

If the submitted command does not match any of the above, the user is returned to the Normal Mode with an error message shown in the status bar informing the user that their command was invalid. This error message goes away after any user input is received.

Search Mode

In Search Mode, the cursor is locked, and users can no longer move it with arrow keys. Instead, users can type in the desired string to be queried for in the file. rust-vim automatically records the user’s keystrokes, and displays them for reference in the status bar below the file contents. rust-vim supports Regex searching.

Users can use the [Backspace] key to delete the right-most character of the command being typed, in case they make a mistake. Deleting all characters in this manner (including the [/] character used to enter Search Mode) will return users back to Normal Mode. Users can also use the [Esc] key to exit Search Mode prematurely without querying anything, returning them back to Normal Mode.

Once a user has finished typing the string they wish to search for, they can submit the query using the [Enter] key. If matches are found, rust-vim will automatically highlight them and return the user to Normal Mode. Search highlights will persist until the user hits [Esc] in Normal Mode, or until the user begins a new search query.

If rust-vim does not find any matches for the submitted query in the file, users are returned to Normal Mode with an error message indicating this result.

Reproducibility Guide

To build rust-vim, first ensure that cargo has been installed on the system. You can install cargo by following these instructions here: https://doc.rust-lang.org/cargo/getting-started/installation.html

Next, download this entire github repo as a zip file, and extract it. After extraction, you should have the src/ folder, as well as the Cargo.lock and Cargo.toml files together in the same folder. The path to this folder will be referred to as <extraction_path>.

To build the binary file, navigate to <extraction_path> in a terminal window, and run the following command

cargo build --release

This will build the release version of rust-vim, located at

<extracted_path>/target/release/rust-vim

After building, feel free to relocate the rust-vim binary file to any location of your choosing, as long as you keep track of where it is. Once you’ve decided on a location to keep rust-vim, we recommend adding the full path to that location to your device’s PATH environment variable for ease of use. Instructions on how to do so can be found here below for Windows, Linux, and MacOS

Once installed, you can run rust-vim by simply running the following command in a terminal

./<path_to_rust-vim>/rust-vim <file_name>

Which will open the file with the name <file_name> if it exists, or create it if it doesn’t.

If you added rust-vim’s location to your device’s PATH environment variable, you can instead simply use

./rust-vim <file_name>

rust-vim also works with paths to files that are not in the current directory. For example, to open a file named rustacean.txt located in the ./rust_is_cool directory, you can use the following command

./rust-vim rust_is_cool/rustacean.txt

Contributions

The team members took equal responsibilities in preparing the presentation slides, making the presentation recordings, and drafting the final report.

The team members developed the core features collaboratively. Ray primarily focused on a user interface that closely mimics Vim. James focused on the backend that efficiently stores and retrieves the text. The interaction between the front-end and backend was a joint effort between James and Ray.

Please refer to the table below for a detailed breakdown of implementation contribution.

Name Task Other Notes
Ray Terminal UI Setup
  • Clear the terminal and display the application
  • Restore the terminal on app closure
  • Accept user inputs
  • Handle the user resizing terminal window without crashing
Ray Status/Message Bar
  • Display current mode
  • Display error messages
  • Display user input
  • Display cursor infile location
James Read from file system to buffer
  • Load file into the backend model
  • Create an empty file if the file does not already exist
James Write to file system
  • Serialize the backend model into a file for persistent storage
Ray Text Wrapping
  • Must adapt to terminal resizing
Ray Cursor Positioning, Tracking, and Movement
  • Cursor must be bound to file contents and terminal window
  • Cursor's infile location must be tracked for backend use
  • Snap cursor to end of line when needed
  • Slip cursor through wide characters
  • Must adapt to user shrinking terminal window by pushing cursor up/left as needed
Ray Scroll File Contents
James Insert content to buffer
  • Handle inputs from the UI and record them properly in the backend model
James Delete content from buffer
  • Handle delete requests from the UI
  • Translate display cursor location into character index in the backend model
  • Delete the text at the corresponding location
Ray Controller State Machine for Vim Modes
  • Some features were implemented as extra modes under the hood
James Search for content in buffer
  • Traverse the backend data model to return matches.
Ray Highlight Matches to Search Queries
Ray Implement Line Number Toggle & Delete Line Commands
  • Shift file contents and cursor as needed to adapt to added UI elements
Ray Help and Confirm Quit Pop-up Window

Lessons Learned and Concluding Remarks

Two key lessons were learned from this project.

For one, we learned firsthand about the many struggles that can arise when co-operatively developing code without proper project management. In a professional environment, we would first design an agreed upon blueprint for the overall project as a first step, including overall architecture, skeleton code, and API definitions between key components, before individually working on assigned parts. Unfortunately, due to our busy schedules, we were unable to meet up and create this blueprint, instead skipping this crucial first step and jumping straight into writing code. As a result, our code does not follow the proposed MVC architecture very strictly, which may result in problems such as code readability, maintainability, scalability, and potentially even performance drawbacks.

One key example of this is the display_content which was stored in the App struct in the controller. The display_content field of the App struct is meant to store the wrapped lines of text (along with metadata like line number) for the View to display. According to the MVC architecture, the display_content would be more appropriately located in either the View (since the wrapped lines would be directly displayed) or the Model (since the original lines of text should be managed by the Model). Instead, the current implementation was built based on a tutorial for the ratatui crate (https://ratatui.rs/tutorials/json-editor/app/) which uses a different paradigm, where the entire App state is managed in one struct (which we placed in the controller to manage other state variables such as mode), while another file handles the actual display in the terminal. As a result, there was some confusion on implementation when passing the project between hands.

Another key lesson learned from this project was the complexity that can go into developing effective text writers, and the importance of good base algorithms over implementation fixes. One example was figuring out how to manage the cursor’s location in the file. A good algorithm is capable of handling many edge cases without requiring too many fixes for unhandled edge cases. In comparison, our algorithm to calculate and track the cursor's position in the file resulted in many unexpected complications arising such as the presence of wide characters, which if improperly handled, could result in the cursor entering an illegal position in the middle of the character.

Tracking the cursor was also difficult because of the text wrapping, which meant that one line in the file could actually be represented by multiple lines on the terminal window. This meant that tracking things such as the cursor’s infile line index, and the cursor’s infile column number, couldn’t be done by simply using the cursor’s coordinates in the terminal. Instead, relevant details for each displayed line had to be stored separately, and then looked up in order to calculate the cursor’s infile line index and infile column number. The requirement of handling terminal resizing and scrolling only made this task more complex, and added more edge cases to handle seperately.

Due to these complexities and inefficiencies caused by poor project management, we were also unfortunately unable to complete the bonus objectives of implementing LSP and LLM support into rust-vim for the submission of this project. However, there were plans to do so using the llm-lsp rust crate (https://crates.io/crates/llm-lsp) or similar crates.

To conclude, while there is still plenty of work that could be done to improve this application, developing rusty-vim was still a very intellectually stimulating experience, which taught us many lessons about both technical details of developing an application in rust with the crates used, as well as the importance of proper project management and organization. The unexpected complexity and challenges faced in this project have also given us a much greater appreciation for all the different considerations that must be taken when developing something as seemingly simple as a CLI text editor.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages