Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ v fmt -verify main.v input.v syntax.v syntax_test.v syntax_embedded.v

To fix drift: `v fmt -w main.v input.v syntax.v syntax_test.v syntax_embedded.v` (sometimes a second pass on `input.v` is needed if the formatter stabilizes across files). CI builds **V from `vlang/v` master** each run; a different local `v` binary can disagree on layout—when in doubt, format with the same compiler revision as CI (clone `https://github.com/vlang/v.git`, `make`, then use `./v fmt`).

GitHub Actions runs this verify step on every push/PR (`ci.yml`) and before release builds (`release.yml`).
GitHub Actions runs this verify step on every push/PR (`ci.yml`) and before release builds (`release.yml`). Release uses `fmt -verify` on all platforms (including Windows); do not mutate sources with `fmt -w` in release.

`input.v` is an empty `module main` stub retained for historical layout / fmt coverage; editor input lives in `main.v` via `term.ui`.

## CI expectations

Expand All @@ -39,6 +41,8 @@ GitHub Actions runs this verify step on every push/PR (`ci.yml`) and before rele

User-facing install docs prefer **Wax** (`wax tap` / `wax install`). **Homebrew** is documented as an alternative that consumes the same tap formula and release assets—keep wording aligned with `README.md` and `scripts/bench-cli.sh` (avoid implying Brew is the only way to get tools like hyperfine).

Release checksum files are named `vro-<platform>.sha256` (not `<archive>.sha256`). Installer and tap scripts must use that name.

## Change discipline

Match existing naming and structure in `main.v` / `input.v` / `syntax.v`. Prefer small, focused diffs; do not reformat unrelated code.
Match existing naming and structure in `main.v` / `syntax.v`. Prefer small, focused diffs; do not reformat unrelated code.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ Syntax rules load dynamically from YAML files named `<name>.yaml`, where `<name>
curl -fsSL https://raw.githubusercontent.com/undivisible/vro/main/install.sh | bash
```

Prefer pinning the script to a tag or commit when scripting installs. Release `.sha256` files prove archive integrity against GitHub Releases, not cryptographic authenticity of the publisher.

Clone install (builds with `v` in `PATH`): run `./install.sh` from the repo root.
`VRO_USE_RELEASE=1`, `VRO_VERSION=v1.0.0`, `VRO_INSTALL_DIR=…`, `VRO_NO_VERIFY=1` supported (see `install.sh`).
`VRO_USE_RELEASE=1`, `VRO_VERSION=v1.0.0`, `VRO_INSTALL_DIR=…`, `VRO_NO_VERIFY=1` supported (see `install.sh`). Checksums are fetched as `vro-<os>-<arch>.sha256`.

**Shell completions** (optional): copy `contrib/completions/vro.{bash,zsh,fish}` into your shell’s completion path.

Expand Down
77 changes: 46 additions & 31 deletions main.v
Original file line number Diff line number Diff line change
Expand Up @@ -692,17 +692,7 @@ fn editor_load_buffer_content(mut e EditorConfig, content string) {
}

fn editor_open(mut e EditorConfig, filename string) ! {
e.hl_cache_path = ''
e.filename = filename
content := os.read_file(filename)!
editor_load_buffer_content(mut e, content)
e.cx = 0
e.cy = 0
e.rx = 0
e.rowoff = 0
e.coloff = 0
e.dirty = 0
editor_refresh_git_marks(mut e)
editor_open_into_buffer(mut e, filename)!
}

fn editor_open_into_buffer(mut e EditorConfig, filename string) ! {
Expand All @@ -721,14 +711,14 @@ fn editor_open_into_buffer(mut e EditorConfig, filename string) ! {

fn editor_save_to_path(mut e EditorConfig, filename string) bool {
data := editor_rows_to_string(e)
tmp := filename + '.vro.tmp'
tmp := filename + '.vro.tmp'
os.write_file(tmp, data) or {
editor_set_status_message(mut e, 'Cannot save! I/O error')
editor_set_status_message(mut e, 'Cannot save! I/O error: ${err.msg()}')
return false
}
os.mv(tmp, filename) or {
os.rm(tmp) or {}
editor_set_status_message(mut e, 'Cannot save! I/O error')
editor_set_status_message(mut e, 'Cannot save! I/O error: ${err.msg()}')
return false
}
e.filename = filename
Expand Down Expand Up @@ -954,6 +944,18 @@ fn editor_ensure_syntax(mut e EditorConfig) {
}
}

fn hl_carry_states_equal(a []bool, b []bool) bool {
if a.len != b.len {
return false
}
for i in 0 .. a.len {
if a[i] != b[i] {
return false
}
}
return true
}

fn editor_ensure_hl_carry(mut e EditorConfig) {
if e.hl_disable || e.hl_syn.rules.len == 0 {
e.hl_carry_enter = [][]bool{}
Expand Down Expand Up @@ -982,6 +984,10 @@ fn editor_ensure_hl_carry(mut e EditorConfig) {
line := e.rows[li].render.bytestr()
carry = hl_carry_row(mut e.hl_syn, line, carry)
e.rows[li].hl_cached = false
// If leave-carry matches the next row's existing enter state, the tail is stable.
if li + 1 < e.rows.len && hl_carry_states_equal(carry, e.hl_carry_enter[li + 1]) {
break
}
}
e.hl_carry_dirty_from = -1
return
Expand Down Expand Up @@ -1623,14 +1629,16 @@ fn editor_prompt_key(mut e EditorConfig, key int) bool {
return true
}

fn editor_clear_screen() {
print('\x1b[2J')
print('\x1b[H')
}

fn editor_run_command(mut e EditorConfig, input string) bool {
cmdline := input.trim_space()
if cmdline.len == 0 {
cmd, args := app_command_parts(input)
if cmd.len == 0 {
return true
}
parts := cmdline.split(' ')
cmd := parts[0].to_lower()
args := if parts.len > 1 { cmdline[cmd.len + 1..].trim_space() } else { '' }

match cmd {
'q', 'quit', 'exit', 'x' {
Expand All @@ -1639,13 +1647,11 @@ fn editor_run_command(mut e EditorConfig, input string) bool {
editor_set_status_message(mut e, msg)
return true
}
print('\x1b[2J')
print('\x1b[H')
editor_clear_screen()
return false
}
'q!', 'quit!', 'exit!', 'x!' {
print('\x1b[2J')
print('\x1b[H')
editor_clear_screen()
return false
}
'wq' {
Expand All @@ -1656,8 +1662,7 @@ fn editor_run_command(mut e EditorConfig, input string) bool {
if e.dirty > 0 {
return true
}
print('\x1b[2J')
print('\x1b[H')
editor_clear_screen()
return false
}
'w', 'write', 'save' {
Expand Down Expand Up @@ -2554,7 +2559,15 @@ fn editor_process_key(mut e EditorConfig, c int, text string) bool {
else {}
}

snap_before := editor_snapshot(e)
// Skip full-buffer undo snapshots for keys that never mutate text.
// Find/save only allocate when needed; command bar can still mutate.
skip_undo_snap := c == ctrl_key(`f`) || c == ctrl_key(`s`)
mut snap_before := EditorSnapshot{}
mut have_snap := false
if !skip_undo_snap {
snap_before = editor_snapshot(e)
have_snap = true
}
dirty_before := e.dirty
match c {
ctrl_key(`x`) {
Expand Down Expand Up @@ -2633,11 +2646,13 @@ fn editor_process_key(mut e EditorConfig, c int, text string) bool {
}

if e.dirty != dirty_before {
e.undo_stack << snap_before
if e.undo_stack.len > undo_stack_max {
e.undo_stack.delete(0)
if have_snap {
e.undo_stack << snap_before
if e.undo_stack.len > undo_stack_max {
e.undo_stack.delete(0)
}
e.redo_stack = []EditorSnapshot{}
}
e.redo_stack = []EditorSnapshot{}
}
if e.dirty != dirty_before || e.dirty == 0 {
e.quit_times_left = quit_times
Expand Down Expand Up @@ -2755,7 +2770,7 @@ fn is_terminal_command_name(s string) bool {

fn app_split_open(mut app VroApp, split EditorPaneSplit, path string) bool {
if path.len == 0 {
app_set_status_message(mut app, 'Usage: split <path>')
app_set_status_message(mut app, 'Usage: right|left|top|bottom <path>')
return true
}
if is_terminal_command_name(path) {
Expand Down
77 changes: 51 additions & 26 deletions scripts/gen_embedded.v
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
import os

fn is_safe_syntax_basename(name string) bool {
if name.len == 0 {
return false
}
for c in name {
ok := (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || (c >= `0` && c <= `9`)
|| c == `_` || c == `-`
if !ok {
return false
}
}
return true
}

fn main() {
dir := 'syntax'
files := os.ls(dir) or { panic(err) }
mut yaml_files := []string{}
for f in files {
if f.ends_with('.yaml') {
yaml_files << f
}
}
yaml_files.sort()
dir := 'syntax'
files := os.ls(dir) or { panic(err) }
mut yaml_files := []string{}
for f in files {
if f.ends_with('.yaml') {
yaml_files << f
}
}
yaml_files.sort()

mut out := os.create('syntax_embedded.v') or { panic(err) }
defer { out.close() }
mut out := os.create('syntax_embedded.v') or { panic(err) }
defer {
out.close()
}

out.writeln('module main') or { panic(err) }
out.writeln('') or { panic(err) }
out.writeln('// Auto-generated. DO NOT EDIT. Run: v run scripts/gen_embedded.v') or { panic(err) }
out.writeln('// Embeds all syntax YAML files into the binary so highlighting works') or { panic(err) }
out.writeln('// even when syntax/ is not installed on disk (e.g. wax/brew installs).') or { panic(err) }
out.writeln('') or { panic(err) }
out.writeln('fn embedded_syntax_yaml(ft string) ?string {') or { panic(err) }
out.writeln('\treturn match ft {') or { panic(err) }
for f in yaml_files {
name := f.replace('.yaml', '')
out.writeln("\t\t'${name}' { \$embed_file('syntax/${f}').to_string() }") or { panic(err) }
}
out.writeln('\t\telse { none }') or { panic(err) }
out.writeln('\t}') or { panic(err) }
out.writeln('}') or { panic(err) }
out.writeln('module main') or { panic(err) }
out.writeln('') or { panic(err) }
out.writeln('// Auto-generated. DO NOT EDIT. Run: v run scripts/gen_embedded.v') or {
panic(err)
}
out.writeln('// Embeds all syntax YAML files into the binary so highlighting works') or {
panic(err)
}
out.writeln('// even when syntax/ is not installed on disk (e.g. wax/brew installs).') or {
panic(err)
}
out.writeln('') or { panic(err) }
out.writeln('fn embedded_syntax_yaml(ft string) ?string {') or { panic(err) }
out.writeln('\treturn match ft {') or { panic(err) }
for f in yaml_files {
name := f.replace('.yaml', '')
if !is_safe_syntax_basename(name) {
panic('unsafe syntax basename: ${f}')
}
out.writeln("\t\t'${name}' { \$embed_file('syntax/${f}').to_string() }") or { panic(err) }
}
out.writeln('\t\telse { none }') or { panic(err) }
out.writeln('\t}') or { panic(err) }
out.writeln('}') or { panic(err) }
}
Loading
Loading