@@ -28,7 +28,7 @@ Ox is an independent text editor that can be used to write everything from text
If you're looking for a text editor that...
1. :feather: Is lightweight and efficient
2. :wrench: Can be configured to your heart's content
-3. :package: Has features out of the box, including
+3. :package: Has features out of the box, including
- syntax highlighting
- undo and redo
- search and replace
@@ -40,22 +40,22 @@ If you're looking for a text editor that...
It runs in your terminal as a text-user-interface, just like vim, nano and micro, however, it is not based on any existing editors and has been built from the ground up.
-It is mainly used on linux systems, but macOS and Windows users (via WSL) are free to give it a go.
+It is mainly designed on linux systems, but macOS and Windows users (via WSL) are free to give it a go. Work is currently underway to get it working perfectly on all systems.
## Selling Points
### Lightweight and Efficient
-- :feather: Ox is lightweight, with the precompiled binary taking up roughly 4mb in storage space.
+- :feather: Ox is lightweight, with the precompiled binary taking up roughly 5mb in storage space.
- :knot: It uses a `rope` data structure which allows incremental editing, file reading and file writing, which will speed up performance, particularly on huge files.
-- :crab: It was built in Rust, which is a quick lower level language that has a strong reputation in the performance department.
+- :crab: It was built in Rust, which is a quick lower-level language that has a strong reputation in the performance department.
### Strong configurability
- :electric_plug: Plug-In system where you can write your own plug-ins or integrate other people's
- :wrench: A wide number of options for configuration with everything from colours to the status line to syntax highlighting being open to customisation
- :moon: Ox uses Lua as a configuration language for familiarity when scripting and configuring
-- 🤝 A configuration assistant to quickly get Ox set up for you from the get-go
+- :handshake: A configuration assistant to quickly get Ox set up for you from the get-go
### Out of the box features
@@ -161,6 +161,8 @@ ox
This will open up an empty document.
+However, if you've just downloaded Ox, the configuration assistant will automatically start up and help you configure the editor initially.
+
If you wish to open a file straight from the command line, you can run
```sh
ox /path/to/file
@@ -228,6 +230,8 @@ We've covered most keyboard shortcuts, but there are some other features you mig
Ox features a configuration system that allows the editor to be modified and personalised.
+By default, you will be greeted by a configuration assistant when first starting Ox, when no configuration file is in place. This will help you generate a configuration file.
+
By default, Ox will look for a file here: `$XDG_CONFIG_HOME/.oxrc` or `~/.oxrc`.
On Windows, Ox will try to look here `C:/Users/user/ox/.oxrc` (where `user` is the user name of your account)
@@ -236,18 +240,16 @@ Ox's configuration language is [Lua](https://lua.org).
For reference, there is a default config in the `config` folder in the repository. You can either download it and place it in the default config directory or create your own using the example ones as a reference.
-If you don't have a config file or don't want to mess around with it, don't worry, Ox has default settings it will use.
-
## Documentation
If you've been through the quick start guide above, but are looking for more detail, you can find in-depth documentation on the [wiki page](https://github.com/curlpipe/ox/wiki/)
-This will take you step-by-step in great detail through 5 different stages:
+This will take you step-by-step in great detail through 6 different stages:
1. **Installation** - advice and how-tos on installation
-2. **Starting** - using the command line interface
-3. **Using** - editing a document and controlling the editor
-4. **Configuring** - writing plug-ins, changing the layout, adding to and changing the syntax highlighting
+2. **Configuring** - changing the layout, adding to and changing the syntax highlighting
+3. **General Editing** - editing a document and controlling the editor
+4. **Command Line** - using the command line interface
5. **Plugins** - installing or uninstalling community plug-ins and writing or distributing your own plug-ins
6. **Roadmap** - planned features
diff --git a/config/.oxrc b/config/.oxrc
index 4cc188d8..8b37a91f 100644
--- a/config/.oxrc
+++ b/config/.oxrc
@@ -49,6 +49,12 @@ event_mapping = {
["pagedown"] = function()
editor:move_page_down()
end,
+ ["esc"] = function()
+ editor:cancel_selection()
+ end,
+ ["alt_v"] = function()
+ editor:cursor_to_viewport()
+ end,
["ctrl_g"] = function()
local line = editor:prompt("Go to line")
editor:move_to(0, tonumber(line))
@@ -116,30 +122,54 @@ event_mapping = {
editor:open_command_line()
end,
["alt_up"] = function()
- -- current line information
- local line = editor:get_line()
local cursor = editor.cursor
- -- insert a new line
- editor:insert_line_at(line, cursor.y - 1)
- -- delete old copy and reposition cursor
- editor:remove_line_at(cursor.y + 1)
- -- restore cursor position
- editor:move_to(cursor.x, cursor.y - 1)
- -- correct indentation level
- autoindent:fix_indent()
+ local select = editor.selection
+ local single = select.x == cursor.x and select.y == cursor.y
+ if single then
+ -- move single line
+ editor:move_line_up()
+ autoindent:fix_indent()
+ else
+ -- move an entire selection
+ if cursor.y > select.y then
+ for line = select.y, cursor.y do
+ editor:move_to(cursor.x, line)
+ editor:move_line_up()
+ end
+ else
+ for line = cursor.y, select.y do
+ editor:move_to(cursor.x, line)
+ editor:move_line_up()
+ end
+ end
+ editor:move_to(cursor.x, cursor.y - 1)
+ editor:select_to(select.x, select.y - 1)
+ end
end,
["alt_down"] = function()
- -- current line information
- local line = editor:get_line()
local cursor = editor.cursor
- -- insert a new line
- editor:insert_line_at(line, cursor.y + 2)
- -- delete old copy and reposition cursor
- editor:remove_line_at(cursor.y)
- -- restore cursor position
- editor:move_to(cursor.x, cursor.y + 1)
- -- correct indentation level
- autoindent:fix_indent()
+ local select = editor.selection
+ local single = select.x == cursor.x and select.y == cursor.y
+ if single then
+ -- move single line
+ editor:move_line_down()
+ autoindent:fix_indent()
+ else
+ -- move an entire selection
+ if cursor.y > select.y then
+ for line = cursor.y, select.y, -1 do
+ editor:move_to(cursor.x, line)
+ editor:move_line_down()
+ end
+ else
+ for line = select.y, cursor.y, -1 do
+ editor:move_to(cursor.x, line)
+ editor:move_line_down()
+ end
+ end
+ editor:move_to(cursor.x, cursor.y + 1)
+ editor:select_to(select.x, select.y + 1)
+ end
end,
["ctrl_w"] = function()
editor:remove_word()
@@ -309,5 +339,4 @@ syntax:set("deletion", {255, 100, 100}) -- Lists in various markup languages e.g
-- Import plugins (must be at the bottom of this file)
load_plugin("pairs.lua")
load_plugin("autoindent.lua")
---load_plugin("pomodoro.lua")
---load_plugin("update_notification.lua")
+load_plugin("quickcomment.lua")
diff --git a/kaolinite/src/document.rs b/kaolinite/src/document.rs
index 2ac2ae7d..2f774ff3 100644
--- a/kaolinite/src/document.rs
+++ b/kaolinite/src/document.rs
@@ -262,7 +262,7 @@ impl Document {
self.file.insert(idx, st);
// Update cache
let line: String = self.file.line(loc.y).chars().collect();
- self.lines[loc.y] = line.trim_end_matches(&['\n', '\r']).to_string();
+ self.lines[loc.y] = line.trim_end_matches(['\n', '\r']).to_string();
// Update unicode map
let dbl_start = self.dbl_map.shift_insertion(loc, st, self.tab_width);
let tab_start = self.tab_map.shift_insertion(loc, st, self.tab_width);
@@ -341,7 +341,7 @@ impl Document {
self.file.remove(start..end);
// Update cache
let line: String = self.file.line(y).chars().collect();
- self.lines[y] = line.trim_end_matches(&['\n', '\r']).to_string();
+ self.lines[y] = line.trim_end_matches(['\n', '\r']).to_string();
self.old_cursor = self.loc().x;
Ok(())
}
@@ -432,6 +432,36 @@ impl Document {
Ok(())
}
+ /// Swap a line upwards
+ /// # Errors
+ /// When out of bounds
+ pub fn swap_line_up(&mut self) -> Result<()> {
+ let cursor = self.char_loc();
+ let line = self.line(cursor.y).ok_or(Error::OutOfRange)?;
+ self.insert_line(cursor.y.saturating_sub(1), line)?;
+ self.delete_line(cursor.y + 1)?;
+ self.move_to(&Loc {
+ x: cursor.x,
+ y: cursor.y.saturating_sub(1),
+ });
+ Ok(())
+ }
+
+ /// Swap a line downwards
+ /// # Errors
+ /// When out of bounds
+ pub fn swap_line_down(&mut self) -> Result<()> {
+ let cursor = self.char_loc();
+ let line = self.line(cursor.y).ok_or(Error::OutOfRange)?;
+ self.insert_line(cursor.y + 2, line)?;
+ self.delete_line(cursor.y)?;
+ self.move_to(&Loc {
+ x: cursor.x,
+ y: cursor.y + 1,
+ });
+ Ok(())
+ }
+
/// Cancels the current selection
pub fn cancel_selection(&mut self) {
self.cursor.selection_end = self.cursor.loc;
@@ -953,6 +983,8 @@ impl Document {
// Bounds checking
if self.loc().y != y && y <= self.len_lines() {
self.cursor.loc.y = y;
+ } else if y > self.len_lines() {
+ self.cursor.loc.y = self.len_lines();
}
// Snap to end of line
self.fix_dangling_cursor();
@@ -1140,7 +1172,7 @@ impl Document {
self.tab_map.insert(i, tab_map);
// Cache this line
self.lines
- .push(line.trim_end_matches(&['\n', '\r']).to_string());
+ .push(line.trim_end_matches(['\n', '\r']).to_string());
}
// Store new loaded point
self.info.loaded_to = to;
@@ -1297,17 +1329,21 @@ impl Document {
self.file.slice(self.selection_range()).to_string()
}
+ /// Commit a change to the undo management system
pub fn commit(&mut self) {
let s = self.take_snapshot();
+ self.undo_mgmt.backpatch_cursor(&self.cursor);
self.undo_mgmt.commit(s);
}
+ /// Completely reload the file
pub fn reload_lines(&mut self) {
let to = std::mem::take(&mut self.info.loaded_to);
self.lines.clear();
self.load_to(to);
}
+ /// Delete the currently selected text
pub fn remove_selection(&mut self) {
self.file.remove(self.selection_range());
self.reload_lines();
diff --git a/kaolinite/src/event.rs b/kaolinite/src/event.rs
index 485f0751..99a28e2f 100644
--- a/kaolinite/src/event.rs
+++ b/kaolinite/src/event.rs
@@ -189,4 +189,11 @@ impl UndoMgmt {
pub fn at_file(&self) -> bool {
self.undo.len() == self.on_disk
}
+
+ /// Change the cursor position of the previous snapshot
+ pub fn backpatch_cursor(&mut self, cursor: &Cursor) {
+ if let Some(snapshot) = self.undo.last_mut() {
+ snapshot.cursor = *cursor;
+ }
+ }
}
diff --git a/plugins/autoindent.lua b/plugins/autoindent.lua
index ba61ae9a..0d4b0bd3 100644
--- a/plugins/autoindent.lua
+++ b/plugins/autoindent.lua
@@ -1,5 +1,5 @@
--[[
-Auto Indent v0.10
+Auto Indent v0.11
Helps you when programming by guessing where indentation should go
and then automatically applying these guesses as you program
@@ -112,6 +112,7 @@ function autoindent:set_indent(y, new_indent)
editor:insert_line_at(new_line, y)
editor:remove_line_at(y + 1)
-- Place the cursor at a sensible position
+ if x < 0 then x = 0 end
editor:move_to(x, y)
end
@@ -192,8 +193,73 @@ for i = 32, 126 do
end
end
+function dedent_amount(y)
+ local tabs = editor:get_line_at(y):match("^\t") ~= nil
+ if tabs then
+ return 1
+ else
+ return document.tab_width
+ end
+end
+
+-- Shortcut to indent a selection
+event_mapping["ctrl_tab"] = function()
+ local cursor = editor.cursor
+ local select = editor.selection
+ if cursor.y == select.y then
+ -- Single line is selected
+ local level = autoindent:get_indent(cursor.y)
+ autoindent:set_indent(cursor.y, level + 1)
+ else
+ -- Multiple lines selected
+ if cursor.y > select.y then
+ for line = select.y, cursor.y do
+ editor:move_to(0, line)
+ local indent = autoindent:get_indent(line)
+ autoindent:set_indent(line, indent + 1)
+ end
+ else
+ for line = cursor.y, select.y do
+ editor:move_to(0, line)
+ local indent = autoindent:get_indent(line)
+ autoindent:set_indent(line, indent + 1)
+ end
+ end
+ local cursor_tabs = dedent_amount(cursor.y)
+ local select_tabs = dedent_amount(select.y)
+ editor:move_to(cursor.x + cursor_tabs, cursor.y)
+ editor:select_to(select.x + select_tabs, select.y)
+ end
+ editor:cursor_snap()
+end
+
-- Shortcut to dedent a line
event_mapping["shift_tab"] = function()
- local level = autoindent:get_indent(editor.cursor.y)
- autoindent:set_indent(editor.cursor.y, level - 1)
+ local cursor = editor.cursor
+ local select = editor.selection
+ if cursor.x == select.x and cursor.y == select.y then
+ -- Dedent a single line
+ local level = autoindent:get_indent(editor.cursor.y)
+ autoindent:set_indent(editor.cursor.y, level - 1)
+ else
+ -- Dedent a group of lines
+ if cursor.y > select.y then
+ for line = select.y, cursor.y do
+ editor:move_to(0, line)
+ local indent = autoindent:get_indent(line)
+ autoindent:set_indent(line, indent - 1)
+ end
+ else
+ for line = cursor.y, select.y do
+ editor:move_to(0, line)
+ local indent = autoindent:get_indent(line)
+ autoindent:set_indent(line, indent - 1)
+ end
+ end
+ local cursor_tabs = dedent_amount(cursor.y)
+ local select_tabs = dedent_amount(select.y)
+ editor:move_to(cursor.x - cursor_tabs, cursor.y)
+ editor:select_to(select.x - select_tabs, select.y)
+ end
+ editor:cursor_snap()
end
diff --git a/plugins/emmet.lua b/plugins/emmet.lua
index a5fdc4a7..7e83894d 100644
--- a/plugins/emmet.lua
+++ b/plugins/emmet.lua
@@ -1,5 +1,5 @@
--[[
-Emmet v0.2
+Emmet v0.3
Implementation of Emmet for Ox for rapid web development
]]--
@@ -85,10 +85,11 @@ def place_cursor(expansion):
img_match = find_cursor_index(r']*src="()"[^>]*>', 'src')
input_match = find_cursor_index(r']*type="()"[^>]*>', 'type')
label_match = find_cursor_index(r'