├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── command_szip.v ├── commands.v ├── v.mod ├── verminal.v └── verminal_main.v /.gitattributes: -------------------------------------------------------------------------------- 1 | *.v linguist-language=V text=auto eol=lf 2 | *.vv linguist-language=V text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | main 3 | iui 4 | iui/ 5 | verminal 6 | *.exe 7 | *.exe~ 8 | *.so 9 | *.dylib 10 | *.dll 11 | vls.log 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Verminal 2 | Terminal emulator written in V 3 | 4 | ![image](https://user-images.githubusercontent.com/16439221/159145675-bc0d85e7-3457-4533-bebb-2091d53e9954.png) 5 | 6 | 7 | ## Compile dependencies 8 | - Module [iUI](https://github.com/isaiahpatton/ui) 9 | - Compile with `-gc boehm` 10 | -------------------------------------------------------------------------------- /command_szip.v: -------------------------------------------------------------------------------- 1 | // Verminal - Terminal Emulator in V 2 | module main 3 | 4 | import iui as ui 5 | import os 6 | import szip 7 | 8 | fn szip_cmd(args []string, mut tbox ui.TextArea) { 9 | if args.len == 1 { 10 | tbox.lines << 'szip: try "szip help" for more information' 11 | return 12 | } 13 | if args[1] == 'help' { 14 | tbox.lines << 'Usage: szip [arguments]' 15 | tbox.lines << "szip extract [file] [dir] \t - Extract a zip file to directory using V's szip" 16 | return 17 | } 18 | if args[1] == 'extract' { 19 | if args.len < 4 { 20 | tbox.lines << 'Usage: szip extract ' 21 | return 22 | } 23 | 24 | file := args[2] 25 | dir := args[3] 26 | 27 | if !os.exists(file) || !os.exists(dir) { 28 | tbox.lines << 'Zip file or directory specified does not exist' 29 | return 30 | } 31 | 32 | tbox.lines << ' Extracting... ' 33 | res := extract_zip_to_dir(file, dir) or { return } 34 | tbox.lines << 'Result: ' + res.str() 35 | } 36 | } 37 | 38 | // Fixed version of szip.extract_zip_to_dir 39 | pub fn extract_zip_to_dir(file string, dir string) ?bool { 40 | mut zip := szip.open(file, .best_speed, .read_only) or { panic(err) } 41 | total := zip.total() or { return false } 42 | for i in 0 .. total { 43 | zip.open_entry_by_index(i) or {} 44 | do_to := os.real_path(os.join_path(dir, zip.name())) 45 | 46 | os.mkdir_all(os.dir(do_to)) or { println(err) } 47 | os.write_file(do_to, '') or {} 48 | 49 | if os.is_dir(do_to) { 50 | continue 51 | } 52 | 53 | zip.extract_entry(do_to) or { 54 | } 55 | } 56 | return true 57 | } 58 | -------------------------------------------------------------------------------- /commands.v: -------------------------------------------------------------------------------- 1 | // Verminal - Terminal Emulator in V 2 | module main 3 | 4 | import iui as ui 5 | import os 6 | import gx 7 | 8 | fn cmd_theme(mut win ui.Window, mut tbox ui.TextArea, args []string) { 9 | if args.len == 1 { 10 | tbox.lines << 'Usage: theme ' 11 | return 12 | } 13 | 14 | if args[1] == 'light' { 15 | win.set_theme(ui.theme_default()) 16 | } 17 | 18 | if args[1] == 'gray' { 19 | win.set_theme(ui.theme_dark()) 20 | } 21 | 22 | if args[1] == 'dark' { 23 | win.set_theme(theme_dark()) 24 | } 25 | 26 | if args[1] == 'red' { 27 | win.set_theme(ui.theme_black_red()) 28 | } 29 | 30 | if args[1] == 'green' { 31 | win.set_theme(theme_green()) 32 | } 33 | } 34 | 35 | pub fn theme_green() ui.Theme { 36 | return ui.Theme{ 37 | name: 'Terminal Green' 38 | text_color: gx.rgb(24, 245, 24) 39 | background: gx.black 40 | button_bg_normal: gx.black 41 | button_bg_hover: gx.black 42 | button_bg_click: gx.black 43 | button_border_normal: gx.rgb(130, 130, 130) 44 | button_border_hover: gx.black 45 | button_border_click: gx.black 46 | menubar_background: gx.rgb(30, 30, 30) 47 | menubar_border: gx.rgb(30, 30, 30) 48 | dropdown_background: gx.black 49 | dropdown_border: gx.black 50 | textbox_background: gx.black 51 | textbox_border: gx.black 52 | checkbox_selected: gx.gray 53 | checkbox_bg: gx.black 54 | progressbar_fill: gx.gray 55 | scroll_track_color: gx.black 56 | scroll_bar_color: gx.gray 57 | } 58 | } 59 | 60 | fn cmd_cd(mut win ui.Window, mut tbox ui.TextArea, args []string) { 61 | mut path := win.extra_map['path'] 62 | if args.len == 1 { 63 | tbox.text = tbox.text + path 64 | return 65 | } 66 | 67 | if args[1] == '..' { 68 | path = path.substr(0, path.replace('\\', '/').last_index('/') or { 0 }) // ' 69 | } else { 70 | if os.is_abs_path(args[1]) { 71 | path = os.real_path(args[1]) 72 | } else { 73 | path = os.real_path(path + '/' + args[1]) 74 | } 75 | } 76 | if os.exists(path) { 77 | win.extra_map['path'] = path 78 | } else { 79 | tbox.text = tbox.text + 'Cannot find the path specified: ' + path 80 | } 81 | } 82 | 83 | fn tree_cmd(dir string, mut tbox ui.TextArea, tabs int) { 84 | path := os.real_path(dir) 85 | files := os.ls(path) or { [] } 86 | for file in files { 87 | joined := os.join_path(path, file) 88 | if os.is_dir(joined) { 89 | tbox.lines << '│\t'.repeat(tabs) + '├───' + file 90 | tree_cmd(joined, mut tbox, tabs + 1) 91 | } 92 | } 93 | if tabs == 0 { 94 | add_new_input_line(mut tbox) 95 | } 96 | } 97 | 98 | fn cmd_dir(mut tbox ui.TextArea, path string, args []string) { 99 | mut ls := os.ls(os.real_path(path)) or { [''] } 100 | mut txt := ' Directory of ' + path + '\n\n' 101 | for file in ls { 102 | txt = txt + '\t' + file + '\n' 103 | } 104 | // os.file_last_mod_unix(os.real_path(path + '/' + file)).str() 105 | tbox.text = tbox.text + txt 106 | } 107 | 108 | fn cmd_v(mut tbox ui.TextArea, args []string) { 109 | mut pro := os.execute('cmd /min /c ' + args.join(' ')) 110 | tbox.text = tbox.text + pro.output.trim_space() 111 | } 112 | 113 | fn cmd_exec(mut win ui.Window, mut tbox ui.TextArea, args []string) { 114 | // Make sure we are in the correct directory 115 | os.chdir(win.extra_map['path']) or { tbox.lines << err.str() } 116 | 117 | if os.user_os() == 'windows' { 118 | cmd_exec_win(mut win, mut tbox, args) 119 | } else { 120 | cmd_exec_unix(mut win, mut tbox, args) 121 | } 122 | } 123 | 124 | // Linux 125 | fn cmd_exec_unix(mut win ui.Window, mut tbox ui.TextArea, args []string) { 126 | mut cmd := os.Command{ 127 | path: args.join(' ') 128 | } 129 | 130 | cmd.start() or { tbox.lines << err.str() } 131 | for !cmd.eof { 132 | out := cmd.read_line() 133 | if out.len > 0 { 134 | for line in out.split_into_lines() { 135 | tbox.lines << line.trim_space() 136 | } 137 | } 138 | } 139 | add_new_input_line(mut tbox) 140 | 141 | cmd.close() or { tbox.lines << err.str() } 142 | } 143 | 144 | // Windows; 145 | // os.Command not fully implemented on Windows, so cmd.exe is used 146 | // 147 | fn cmd_exec_win(mut win ui.Window, mut tbox ui.TextArea, args []string) { 148 | mut pro := os.new_process('cmd') 149 | 150 | mut argsa := ['/min', '/c', args.join(' ')] 151 | pro.set_args(argsa) 152 | 153 | pro.set_redirect_stdio() 154 | pro.run() 155 | 156 | for pro.is_alive() { 157 | out := pro.stdout_read() 158 | if out.len > 0 { 159 | for line in out.split_into_lines() { 160 | tbox.lines << line.trim_space() 161 | } 162 | } 163 | err := pro.stderr_read() 164 | if err.len > 0 { 165 | for line in err.split('\n') { 166 | tbox.lines << line.trim_space() 167 | } 168 | } 169 | } 170 | add_new_input_line(mut tbox) 171 | 172 | pro.close() 173 | } 174 | -------------------------------------------------------------------------------- /v.mod: -------------------------------------------------------------------------------- 1 | Module { 2 | name: 'verminal' 3 | description: 'Terminal emul. in V' 4 | version: '0.1' 5 | license: 'MIT / Boost' 6 | dependencies: [] 7 | } 8 | -------------------------------------------------------------------------------- /verminal.v: -------------------------------------------------------------------------------- 1 | // Verminal - Terminal Emulator in V 2 | module main 3 | 4 | import iui as ui 5 | import os 6 | import gx 7 | 8 | fn create_box(win_ptr voidptr) &ui.TextArea { 9 | mut win := &ui.Window(win_ptr) 10 | 11 | path := os.real_path(os.home_dir()) 12 | win.extra_map['path'] = path 13 | 14 | mut box := ui.textarea(win, ['Verminal 0.4.2', 'Copyright © 2021-2022 Isaiah.', '', path + '>']) 15 | box.set_id(mut win, 'vermbox') 16 | box.padding_y = 10 17 | box.code_syntax_on = false 18 | box.draw_event_fn = box_draw 19 | box.before_txtc_event_fn = before_txt_change 20 | box.set_bounds(0, 0, 800, 420) 21 | 22 | return box 23 | } 24 | 25 | fn box_draw(mut win ui.Window, com &ui.Component) { 26 | mut this := *com 27 | if mut this is ui.TextArea { 28 | this.is_selected = true 29 | 30 | this.caret_top = this.lines.len - 1 31 | line := this.lines[this.caret_top] 32 | 33 | size := win.gg.window_size() 34 | cp := win.extra_map['path'] 35 | 36 | if this.width != size.width || this.height != size.height { 37 | this.width = size.width 38 | this.height = size.height - 24 39 | } 40 | 41 | if line.contains(cp + '>') { 42 | if this.caret_left < cp.len + 1 { 43 | this.caret_left = cp.len + 1 44 | } 45 | } 46 | } 47 | } 48 | 49 | fn before_txt_change(mut win ui.Window, tb ui.TextArea) bool { 50 | mut is_backsp := tb.last_letter == 'backspace' 51 | 52 | mut tbox := get_current_terminal(win) 53 | tbox.scroll_i = tbox.lines.len 54 | 55 | if is_backsp { 56 | mut txt := tb.lines[tb.caret_top] 57 | mut cline := txt // txt[txt.len - 1] 58 | mut path := win.extra_map['path'] 59 | if cline.ends_with(path + '>') { 60 | return true 61 | } 62 | } 63 | 64 | mut is_enter := tb.last_letter == 'enter' 65 | 66 | if is_enter { 67 | mut txt := tb.lines[tb.caret_top] 68 | mut cline := txt // txt[txt.len - 1] 69 | mut path := win.extra_map['path'] 70 | 71 | if cline.contains(path + '>') { 72 | mut cmd := cline.split(path + '>')[1] 73 | on_cmd(mut win, tb, cmd) 74 | } 75 | return true 76 | } 77 | return false 78 | } 79 | 80 | fn on_cmd(mut win ui.Window, box ui.TextArea, cmd string) { 81 | args := cmd.split(' ') 82 | 83 | mut tbox := get_current_terminal(win) 84 | 85 | if args[0] == 'cd' { 86 | cmd_cd(mut win, mut tbox, args) 87 | add_new_input_line(mut tbox) 88 | } else if args[0] == 'help' { 89 | tbox.lines << win.extra_map['verm-help'] 90 | add_new_input_line(mut tbox) 91 | } else if args[0] == 'version' || args[0] == 'ver' { 92 | tbox.lines << 'Verminal - A terminal emulator written in V' 93 | tbox.lines << '\tVersion: 0.4, UI Version: ' + ui.version 94 | add_new_input_line(mut tbox) 95 | } else if args[0] == 'cls' || args[0] == 'clear' { 96 | tbox.lines.clear() 97 | tbox.scroll_i = 0 98 | add_new_input_line(mut tbox) 99 | } else if args[0] == 'font-size' { 100 | win.font_size = args[1].int() 101 | add_new_input_line(mut tbox) 102 | } else if args[0] == 'dira' { 103 | mut path := win.extra_map['path'] 104 | cmd_dir(mut tbox, path, args) 105 | add_new_input_line(mut tbox) 106 | } else if args[0] == 'v' || args[0] == 'dir' || args[0] == 'git' { 107 | go cmd_exec(mut win, mut tbox, args) 108 | } else if args[0].len == 2 && args[0].ends_with(':') { 109 | win.extra_map['path'] = os.real_path(args[0]) 110 | add_new_input_line(mut tbox) 111 | tbox.caret_top += 1 112 | } else if args[0] == 'tree' { 113 | path := os.real_path(win.extra_map['path']) 114 | go tree_cmd(path, mut tbox, 0) 115 | } else if args[0] == 'szip' { 116 | szip_cmd(args, mut tbox) 117 | tbox.lines << ' ' 118 | add_new_input_line(mut tbox) 119 | } else if args[0] == 'terminal-height' { 120 | tbox.lines << win.gg.window_size().str() 121 | tbox.lines << ' ' 122 | add_new_input_line(mut tbox) 123 | } else if args[0] == 'theme' { 124 | cmd_theme(mut win, mut tbox, args) 125 | add_new_input_line(mut tbox) 126 | } else if args[0] == 'new_tab' { 127 | new_tab(win) 128 | } else { 129 | cmd_exec(mut win, mut tbox, args) 130 | } 131 | 132 | win.extra_map['lastcmd'] = cmd 133 | } 134 | 135 | fn add_new_input_line(mut tbox ui.TextArea) { 136 | tbox.lines << tbox.win.extra_map['path'] + '>' 137 | } 138 | 139 | // 140 | // Dark Theme for a Terminal 141 | // 142 | pub fn theme_dark() ui.Theme { 143 | return ui.Theme{ 144 | name: 'Dark Terminal' 145 | text_color: gx.rgb(245, 245, 245) 146 | background: gx.black 147 | button_bg_normal: gx.black 148 | button_bg_hover: gx.black 149 | button_bg_click: gx.black 150 | button_border_normal: gx.rgb(130, 130, 130) 151 | button_border_hover: gx.black 152 | button_border_click: gx.black 153 | menubar_background: gx.rgb(30, 30, 30) 154 | menubar_border: gx.rgb(30, 30, 30) 155 | dropdown_background: gx.black 156 | dropdown_border: gx.black 157 | textbox_background: gx.black 158 | textbox_border: gx.black 159 | checkbox_selected: gx.gray 160 | checkbox_bg: gx.black 161 | progressbar_fill: gx.gray 162 | scroll_track_color: gx.black 163 | scroll_bar_color: gx.gray 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /verminal_main.v: -------------------------------------------------------------------------------- 1 | // Verminal - Terminal Emulator in V 2 | module main 3 | 4 | import iui as ui 5 | import os.font 6 | import os 7 | 8 | [console] 9 | fn main() { 10 | mut font_path := font.default() 11 | windows_term := 'C:/windows/fonts/consola.ttf' 12 | if os.exists(windows_term) { 13 | font_path = windows_term 14 | } 15 | 16 | mut win := ui.window_with_config(theme_dark(), 'Verminal', 800, 437, ui.WindowConfig{ 17 | font_path: font_path 18 | font_size: 14 19 | ui_mode: true 20 | }) 21 | 22 | mut tb := ui.tabbox(win) 23 | tb.set_id(mut win, 'terminal:tabs') 24 | 25 | mut box := create_box(win) 26 | tb.add_child('Verminal #0', box) 27 | win.add_child(tb) 28 | 29 | win.gg.run() 30 | } 31 | 32 | fn get_current_terminal(win &ui.Window) &ui.TextArea { 33 | mut tb := &ui.Tabbox(win.get_from_id('terminal:tabs')) 34 | mut kid := tb.kids[tb.active_tab][0] 35 | if mut kid is ui.TextArea { 36 | return kid 37 | } 38 | return voidptr(0) 39 | } 40 | 41 | fn new_tab(win &ui.Window) { 42 | mut tb := &ui.Tabbox(win.get_from_id('terminal:tabs')) 43 | mut box := create_box(win) 44 | 45 | name := 'Verminal #' + tb.kids.len.str() 46 | tb.add_child(name, box) 47 | } 48 | --------------------------------------------------------------------------------