├── src
├── notes.v
├── assets
│ ├── fmt.png
│ ├── merge.png
│ ├── run.png
│ ├── vicon.png
│ ├── word.png
│ ├── word0.png
│ ├── explore.png
│ ├── search.png
│ ├── v-logo.png
│ ├── file-icon.png
│ ├── help-icon.png
│ ├── theme
│ │ ├── btn.png
│ │ ├── menu.png
│ │ └── btn-light.png
│ ├── Anomaly-Mono.ttf
│ ├── JetBrainsMono.ttf
│ ├── icons8-edit-24.png
│ ├── icons8-save-24.png
│ ├── FiraCode-Regular.ttf
│ ├── JetBrainsMono-Regular.ttf
│ ├── ezgif.com-gif-maker(3).png
│ ├── ezgif.com-gif-maker(5).png
│ ├── icons8-change-theme-24.png
│ └── credit.txt
├── image_view.v
├── tree-list.v
├── code_interaction.v
├── vide-theme.v
├── events.v
├── verminal_commands.v
├── new_proj.v
├── verminal.v
├── code_suggestions.v
├── tabs.v
├── menus.v
├── config.v
├── main.v
└── vcreate.v
├── v.mod
├── .gitattributes
├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
└── .github
└── workflows
├── blank.yml
└── rele.yml
/src/notes.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | struct Note {
4 | text string
5 | }
6 |
--------------------------------------------------------------------------------
/src/assets/fmt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/fmt.png
--------------------------------------------------------------------------------
/src/assets/merge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/merge.png
--------------------------------------------------------------------------------
/src/assets/run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/run.png
--------------------------------------------------------------------------------
/src/assets/vicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/vicon.png
--------------------------------------------------------------------------------
/src/assets/word.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/word.png
--------------------------------------------------------------------------------
/src/assets/word0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/word0.png
--------------------------------------------------------------------------------
/src/assets/explore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/explore.png
--------------------------------------------------------------------------------
/src/assets/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/search.png
--------------------------------------------------------------------------------
/src/assets/v-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/v-logo.png
--------------------------------------------------------------------------------
/src/assets/file-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/file-icon.png
--------------------------------------------------------------------------------
/src/assets/help-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/help-icon.png
--------------------------------------------------------------------------------
/src/assets/theme/btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/theme/btn.png
--------------------------------------------------------------------------------
/src/assets/theme/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/theme/menu.png
--------------------------------------------------------------------------------
/src/assets/Anomaly-Mono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/Anomaly-Mono.ttf
--------------------------------------------------------------------------------
/src/assets/JetBrainsMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/JetBrainsMono.ttf
--------------------------------------------------------------------------------
/src/assets/icons8-edit-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/icons8-edit-24.png
--------------------------------------------------------------------------------
/src/assets/icons8-save-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/icons8-save-24.png
--------------------------------------------------------------------------------
/src/assets/theme/btn-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/theme/btn-light.png
--------------------------------------------------------------------------------
/src/assets/FiraCode-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/FiraCode-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/JetBrainsMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/JetBrainsMono-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/ezgif.com-gif-maker(3).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/ezgif.com-gif-maker(3).png
--------------------------------------------------------------------------------
/src/assets/ezgif.com-gif-maker(5).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/ezgif.com-gif-maker(5).png
--------------------------------------------------------------------------------
/src/assets/icons8-change-theme-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pisaiah/Vide/HEAD/src/assets/icons8-change-theme-24.png
--------------------------------------------------------------------------------
/v.mod:
--------------------------------------------------------------------------------
1 | Module {
2 | name: 'vide2'
3 | description: 'Testing'
4 | version: '1.0'
5 | license: 'MIT'
6 | dependencies: []
7 | }
8 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 | *.bat eol=crlf
3 |
4 | **/*.v linguist-language=V
5 | **/*.vv linguist-language=V
6 | **/*.vsh linguist-language=V
7 | **/v.mod linguist-language=V
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 | insert_final_newline = true
5 | trim_trailing_whitespace = true
6 |
7 | [*.v]
8 | indent_style = tab
9 | indent_size = 4
10 |
--------------------------------------------------------------------------------
/src/assets/credit.txt:
--------------------------------------------------------------------------------
1 | Save icon by Icons8
2 | Edit icon by Icons8
3 |
4 | Anomaly Mono:
5 | https://github.com/benbusby/anomaly-mono/
--------------------------------------------------------------------------------
/src/image_view.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 |
5 | fn image_view(path string) &ui.ScrollView {
6 | mut p := ui.Panel.new()
7 |
8 | mut im := ui.Image.new(file: path)
9 | p.add_child(im)
10 |
11 | mut sv := ui.ScrollView.new(
12 | view: p
13 | )
14 | sv.set_border_painted(false)
15 |
16 | return sv
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | main
3 | vide2
4 | *.exe
5 | *.exe~
6 | *.so
7 | *.dylib
8 | *.dll
9 | *.def
10 |
11 | # Ignore binary output folders
12 | bin/
13 |
14 | # Ignore common editor/system specific metadata
15 | .DS_Store
16 | .idea/
17 | .vscode/
18 | *.iml
19 |
20 | # ENV
21 | .env
22 |
23 | # vweb and database
24 | *.db
25 | *.js
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Isaiah
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/tree-list.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 | import os
5 |
6 | // Tree v2:
7 |
8 | // Make an Tree list from files from dir
9 | fn make_tree2(fold string) &ui.TreeNode {
10 | files := os.ls(fold) or { [] }
11 |
12 | mut nodes := []&ui.TreeNode{}
13 |
14 | for fi in files {
15 | if fi.starts_with('.git') || fi.contains('.exe') || fi.contains('.dll') {
16 | continue
17 | }
18 | mut sub := &ui.TreeNode{
19 | text: fold + '/' + fi
20 | }
21 |
22 | if !fi.starts_with('.') {
23 | join := os.join_path(fold, fi)
24 | subfiles := os.ls(join) or { [] }
25 | for f in subfiles {
26 | node := make_tree2(os.join_path(join, f))
27 | sub.nodes << node
28 | }
29 | }
30 | nodes << sub
31 | }
32 |
33 | mut node := &ui.TreeNode{
34 | text: fold
35 | nodes: nodes
36 | }
37 |
38 | return node
39 | }
40 |
41 | fn tree2_click(mut ctx ui.GraphicsContext, tree &ui.Tree2, node &ui.TreeNode) {
42 | txt := node.text
43 | dump(txt)
44 | path := os.real_path(txt)
45 | dump(path)
46 | if !os.is_dir(path) {
47 | new_tab(ctx.win, txt)
48 | }
49 | }
50 |
51 | // Refresh Tree list
52 | fn refresh_tree(mut window ui.Window, fold string, mut tree ui.Tree2) {
53 | // TODO
54 | dump('REFRESH')
55 | tree.children.clear()
56 |
57 | dump(fold)
58 | files := os.ls(fold) or { [] }
59 | tree.click_event_fn = tree2_click
60 |
61 | for fi in files {
62 | mut node := make_tree2(os.join_path(fold, fi))
63 | tree.add_child(node)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/code_interaction.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 |
5 | struct MyPopup {
6 | ui.Popup
7 | mut:
8 | texts []string
9 | p &ui.Panel
10 | sv &ui.ScrollView
11 | }
12 |
13 | fn code_popup() &MyPopup {
14 | mut p := ui.Panel.new(
15 | layout: ui.BoxLayout.new(
16 | ori: 1
17 | vgap: 1
18 | hgap: 1
19 | )
20 | )
21 | p.set_bounds(0, 0, 300, 150)
22 |
23 | mut sv := ui.ScrollView.new(
24 | view: p
25 | bounds: ui.Bounds{0, 0, 300, 150}
26 | )
27 |
28 | mut pop := &MyPopup{
29 | p: p
30 | sv: sv
31 | }
32 |
33 | pop.add_child(sv)
34 | pop.set_bounds(0, 0, 300, 150)
35 |
36 | return pop
37 | }
38 |
39 | fn (mut this MyPopup) set_texts(mut tb ui.Textbox, lines []string, aft int) {
40 | this.p.children.clear()
41 | for line in lines {
42 | mut o := ui.Label.new(
43 | text: line
44 | )
45 | o.subscribe_event('draw', fn (mut e ui.MouseEvent) {
46 | e.target.height = e.ctx.line_height
47 | e.target.width = e.target.parent.width - 2
48 |
49 | is_in := ui.is_in(e.target, e.ctx.win.mouse_x, e.ctx.win.mouse_y)
50 |
51 | if is_in {
52 | e.ctx.gg.draw_rect_filled(e.target.x, e.target.y, e.target.width, e.target.height,
53 | e.ctx.theme.button_bg_hover)
54 | }
55 | })
56 | o.subscribe_event('mouse_up', fn [mut tb, mut this, aft] (mut e ui.MouseEvent) {
57 | bef := tb.lines[tb.caret_y][0..(tb.caret_x - aft)]
58 | ln := bef + e.target.text
59 |
60 | tb.lines[tb.caret_y] = ln
61 | tb.caret_x = ln.len
62 |
63 | this.hide(e.ctx)
64 | })
65 | o.set_bounds(0, 0, 100, 30)
66 | this.p.add_child(o)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 | A simple IDE designed for the [V Programming Language](https://vlang.io/) made in V.
4 |
5 | 
6 | [](https://github.com/pisaiah/Vide/releases)
7 | 
8 |
9 | ## Features:
10 | - **UI for VPM**: Vide integrates with VPM to manage your project dependencies.
11 | - **Multiple Projects**: Work on multiple projects simultaneously within the same IDE.
12 | - **Autocomplete** (Beta): Enjoy code suggestions as you type (still in beta, but improving!).
13 | - **GUI Builder** (Planned)
14 | - **Online Playground** (Planned)
15 |
16 | ## Screenshots:
17 |
18 | 
UI for VPM
19 | 
Multiple Projects
20 | 
Autocomplete (Beta)
21 |
22 |
23 | ## Resource Requirements
24 |
25 | | IDE | Disk | RAM | Render |
26 | |---------|---------|---------|----------|
27 | | | | |
28 | | Vɪᴅᴇ | < 5MB | ~ 70MB | GG/Sokol |
29 | | Vɪᴅᴇ | < 5MB | ~ 4MB | [Win32](https://github.com/pisaiah/v/tree/win32-native-rendering) |
30 | | VS Code | 300MB | ~ 300MB | Electron |
31 | | | | | |
32 |
33 | ## Contributing
34 | Feel free to contribute to the development of Vɪᴅᴇ by creating an account on GitHub. Your feedback, bug reports, and pull requests are highly appreciated!
35 |
36 | ## See Also
37 |
38 | - [iUI](https://github.com/pisaiah/ui), UI Library for V.
39 | - [vPaint](https://github.com/pisaiah/vpaint), MS-Paint alternative written in V.
40 | - [Vizard](https://github.com/pisaiah/vizard), Utility for creating wizard guis in V.
41 |
42 | - [V](https://github.com/vlang/v)
43 |
44 |
--------------------------------------------------------------------------------
/src/vide-theme.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 | import gx
5 |
6 | // Vide Light Theme
7 | pub fn vide_light_theme() &ui.Theme {
8 | mut theme := ui.theme_default()
9 | theme.name = 'Vide Light'
10 | theme.button_fill_fn = vide_theme_button_fill_fn
11 | theme.bar_fill_fn = vide_theme_bar_fill_fn
12 | theme.setup_fn = vide_theme_setup
13 | theme.menu_bar_fill_fn = vide_theme_menubar_fill_fn
14 | return theme
15 | }
16 |
17 | // Vide Default Dark Theme
18 | pub fn vide_dark_theme() &ui.Theme {
19 | mut theme := ui.theme_dark()
20 | theme.name = 'Vide Default Dark'
21 | // theme.button_fill_fn = vide_theme_button_fill_fn
22 | theme.bar_fill_fn = vide_theme_bar_fill_fn
23 | theme.setup_fn = vide_theme_setup
24 | theme.menu_bar_fill_fn = vide_theme_menubar_fill_fn
25 |
26 | // V colors
27 | theme.accent_fill = gx.rgb(93, 135, 191)
28 | theme.accent_fill_second = gx.rgb(88, 121, 165)
29 | theme.accent_fill_third = gx.rgb(83, 107, 138)
30 |
31 | // theme.button_bg_normal = theme.accent_fill_third
32 |
33 | return theme
34 | }
35 |
36 | pub fn vide_theme_setup(mut win ui.Window) {
37 | mut ctx := win.graphics_context
38 | // mut o_file := $embed_file('assets/theme/btn.png')
39 | // mut o_icons := win.create_gg_image(o_file.data(), o_file.len)
40 | // ctx.icon_cache['vide_theme-btn'] = win.gg.cache_image(o_icons)
41 |
42 | // mut o_file1 := $embed_file('assets/theme/bar.png')
43 | // o_icons = win.create_gg_image(o_file1.data(), o_file1.len)
44 | // ctx.icon_cache['vide_theme-bar'] = ctx.gg.cache_image(o_icons)
45 |
46 | mut o_file2 := $embed_file('assets/theme/menu.png')
47 | mut o_icons := win.create_gg_image(o_file2.data(), o_file2.len)
48 | ctx.icon_cache['vide_theme-menu'] = ctx.gg.cache_image(o_icons)
49 |
50 | // mut o_file3 := $embed_file('assets/theme/barw.png')
51 | // o_icons = win.create_gg_image(o_file3.data(), o_file3.len)
52 | // ctx.icon_cache['vide_theme-bar-w'] = ctx.gg.cache_image(o_icons)
53 | }
54 |
55 | pub fn vide_theme_button_fill_fn(x int, y int, w int, h int, r int, bg gx.Color, ctx &ui.GraphicsContext) {
56 | if bg == ctx.theme.button_bg_normal {
57 | ctx.gg.draw_image_by_id(x - 1, y - 1, w + 2, h + 2, ctx.icon_cache['vide_theme-btn'])
58 | } else {
59 | ctx.gg.draw_rounded_rect_filled(x, y, w, h, r, bg)
60 | }
61 | }
62 |
63 | pub fn vide_theme_bar_fill_fn(x int, y f32, w int, h f32, hor bool, ctx &ui.GraphicsContext) {
64 | if hor {
65 | hh := h / 2
66 | ctx.gg.draw_rect_filled(x, y, w, hh, gx.rgb(88, 128, 181))
67 | ctx.gg.draw_rect_filled(x, y + hh, w, hh, gx.rgb(68, 100, 140))
68 | ctx.gg.draw_rect_empty(x, y, w, h, ctx.theme.scroll_bar_color)
69 | } else {
70 | xx := x - 1
71 | ww := (w + 2) / 2
72 |
73 | ctx.gg.draw_rect_filled(xx, y, w + 3, h, gx.rgb(88, 128, 181))
74 | ctx.gg.draw_rect_filled(xx + ww, y, ww + 1, h, gx.rgb(68, 100, 140))
75 | }
76 | }
77 |
78 | pub fn vide_theme_menubar_fill_fn(x int, y int, w int, h int, ctx &ui.GraphicsContext) {
79 | ctx.gg.draw_image_by_id(x, y, w, h + 1, ctx.icon_cache['vide_theme-menu'])
80 | }
81 |
--------------------------------------------------------------------------------
/src/events.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 | import gg
5 |
6 | // Change the width of the project tree to correspond with the collapse state.
7 | fn (mut app App) proj_tree_draw(mut e ui.DrawEvent) {
8 | if app.shown_activity != 0 {
9 | e.target.width = -1
10 | return
11 | }
12 |
13 | if app.collapse_tree {
14 | mx := app.win.mouse_x
15 | if mx < e.target.width || mx < e.target.x {
16 | if e.target.width < 250 {
17 | e.target.width += app.activty_speed
18 | }
19 | return
20 | }
21 |
22 | if e.target.width > app.activty_speed {
23 | e.target.width -= app.activty_speed
24 | }
25 | if e.target.width <= app.activty_speed {
26 | e.target.width = 0
27 | }
28 | } else {
29 | if e.target.width < 250 {
30 | e.target.width += app.activty_speed
31 | }
32 | }
33 |
34 | height := gg.window_size().height - 32
35 |
36 | if height > 0 {
37 | e.target.height = height
38 | }
39 | }
40 |
41 | // Change the width of the project tree to correspond with the collapse state.
42 | fn (mut app App) search_pane_draw(mut e ui.DrawEvent) {
43 | if app.shown_activity != 1 {
44 | e.target.width = -1
45 | return
46 | }
47 |
48 | if app.collapse_search {
49 | mx := app.win.mouse_x
50 | if mx < e.target.width || mx < e.target.x {
51 | if e.target.width < 250 {
52 | e.target.width += app.activty_speed
53 | }
54 | return
55 | }
56 |
57 | if e.target.width > app.activty_speed {
58 | e.target.width -= app.activty_speed
59 | }
60 | if e.target.width <= app.activty_speed {
61 | e.target.width = 0
62 | }
63 | } else {
64 | if e.target.width < 250 {
65 | e.target.width += app.activty_speed
66 | }
67 | }
68 |
69 | height := gg.window_size().height - 32
70 |
71 | if height > 0 {
72 | e.target.height = height
73 | }
74 | }
75 |
76 | // Change the collapse state when the button is clicked
77 | fn (mut app App) calb_click(mut e ui.MouseEvent) {
78 | if app.shown_activity != 0 {
79 | app.shown_activity = 0
80 | app.collapse_tree = false
81 | } else {
82 | app.collapse_tree = !app.collapse_tree
83 | }
84 | }
85 |
86 | // Change the collapse state when the button is clicked
87 | fn (mut app App) serb_click(mut e ui.MouseEvent) {
88 | if app.shown_activity != 1 {
89 | app.shown_activity = 1
90 | app.collapse_search = false
91 | } else {
92 | app.collapse_search = !app.collapse_search
93 | }
94 | }
95 |
96 | // Set the width of the verminal's ScrollView to
97 | // the width of the SplitView (aka the parent)
98 | fn terminal_scrollview_fill(mut e ui.DrawEvent) {
99 | e.target.width = e.target.parent.width
100 | }
101 |
102 | // Set the width and height of the SplitView to fill the content area.
103 | fn splitview_fill(mut e ui.DrawEvent) {
104 | size := e.ctx.gg.window_size()
105 |
106 | w := size.width - e.target.rx - 1
107 | h := size.height - 30
108 |
109 | if w < 0 || h < 0 {
110 | return
111 | }
112 |
113 | e.target.width = w
114 | e.target.height = h
115 | }
116 |
117 | // Have the main HBox's size be set to the window size
118 | @[deprecated: 'Not needed with latest ui, as we use Panel now']
119 | fn content_pane_fill_window(mut e ui.DrawEvent) {
120 | }
121 |
122 | // Have Tabbox take up the full width of the SplitView
123 | fn tabbox_fill_width(mut e ui.DrawEvent) {
124 | size := e.ctx.gg.window_size()
125 | wid := size.width - e.target.x - 1
126 | if wid < 0 {
127 | return
128 | }
129 | e.target.width = wid
130 |
131 | // TODO: add open/close tab event.
132 |
133 | mut app := e.ctx.win.get[&App]('app')
134 | mut tb := e.ctx.win.get[&ui.Tabbox]('main-tabs')
135 |
136 | if app.confg.open_paths.len != tb.kids.len {
137 | // dump('need update')
138 |
139 | for name in tb.kids.keys() {
140 | if name !in app.confg.open_paths {
141 | app.confg.open_paths << name
142 | }
143 | }
144 |
145 | for i, name in app.confg.open_paths {
146 | if name !in tb.kids.keys() {
147 | app.confg.open_paths.delete(i)
148 | // app.confg.open_paths.delete(name)
149 | }
150 | }
151 | app.confg.save()
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/verminal_commands.v:
--------------------------------------------------------------------------------
1 | // Verminal
2 | module main
3 |
4 | import iui as ui
5 | import os
6 |
7 | fn cmd_cd(mut win ui.Window, mut tbox ui.Textbox, args []string) {
8 | mut path := win.extra_map['path']
9 | if args.len == 1 {
10 | tbox.text = tbox.text + path
11 | return
12 | }
13 |
14 | if args[1] == '..' {
15 | path = path.substr(0, path.replace('\\', '/').last_index('/') or { 0 }) // '
16 | } else {
17 | if os.is_abs_path(args[1]) {
18 | path = os.real_path(args[1])
19 | } else {
20 | path = os.real_path(path + '/' + args[1])
21 | }
22 | }
23 | if os.exists(path) {
24 | win.extra_map['path'] = path
25 | } else {
26 | tbox.text = tbox.text + 'Cannot find the path specified: ' + path
27 | }
28 | }
29 |
30 | fn cmd_dir(mut tbox ui.Textbox, path string, args []string) {
31 | mut ls := os.ls(os.real_path(path)) or { [''] }
32 | tbox.lines << ' Directory of "' + path + '".'
33 | for file in ls {
34 | tbox.lines << '\t' + file
35 | }
36 | }
37 |
38 | fn cmd_v(mut tbox ui.Textbox, args []string) {
39 | mut pro := os.execute('cmd /min /c ' + args.join(' '))
40 | tbox.text = tbox.text + pro.output.trim_space()
41 | }
42 |
43 | fn verminal_cmd_exec(mut win ui.Window, mut tbox ui.Textbox, args []string) {
44 | // Make sure we are in the correct directory
45 | os.chdir(win.extra_map['path']) or { tbox.lines << err.str() }
46 |
47 | if os.user_os() == 'windows' {
48 | cmd_exec_win(mut win, mut tbox, args)
49 | } else {
50 | cmd_exec_unix(mut win, mut tbox, args)
51 | }
52 |
53 | win.extra_map['update_scroll'] = 'true'
54 | }
55 |
56 | // Linux
57 | fn cmd_exec_unix(mut win ui.Window, mut tbox ui.Textbox, args []string) {
58 | mut cmd := os.Command{
59 | path: args.join(' ')
60 | }
61 |
62 | cmd.start() or { tbox.lines << err.str() }
63 | for !cmd.eof {
64 | out := cmd.read_line()
65 | if out.len > 0 {
66 | for line in out.split_into_lines() {
67 | tbox.lines << line.trim_space()
68 | }
69 | }
70 | }
71 | add_new_input_line(mut tbox, win)
72 |
73 | cmd.close() or { tbox.lines << err.str() }
74 | }
75 |
76 | // Windows
77 | fn cmd_exec_win(mut win ui.Window, mut tbox ui.Textbox, args []string) {
78 | mut pro := os.new_process('C:\\Windows\\System32\\cmd.exe')
79 |
80 | mut argsa := ['/min', '/c', args.join(' ')]
81 | pro.set_args(argsa)
82 |
83 | pro.set_redirect_stdio()
84 | pro.run()
85 |
86 | for pro.is_alive() {
87 | mut out := pro.stdout_read()
88 | mut oute := pro.stderr_read()
89 |
90 | if oute.len > 0 {
91 | for line in oute.split_into_lines() {
92 | tbox.lines << line.trim_space()
93 | }
94 | }
95 |
96 | if out.len > 0 {
97 | for line in out.split_into_lines() {
98 | tbox.lines << line.trim_space()
99 | }
100 | }
101 | }
102 | add_new_input_line(mut tbox, win)
103 |
104 | pro.close()
105 | }
106 |
107 | // Run command without updating a text box
108 | fn run_exec(args []string) []string {
109 | if os.user_os() == 'windows' {
110 | return run_exec_win(args)
111 | } else {
112 | return run_exec_unix(args)
113 | }
114 | }
115 |
116 | // Linux
117 | fn run_exec_unix(args []string) []string {
118 | mut cmd := os.Command{
119 | path: args.join(' ')
120 | }
121 |
122 | mut content := []string{}
123 | cmd.start() or { content << err.str() }
124 | for !cmd.eof {
125 | out := cmd.read_line()
126 | if out.len > 0 {
127 | for line in out.split_into_lines() {
128 | content << line.trim_space()
129 | }
130 | }
131 | }
132 |
133 | cmd.close() or { content << err.str() }
134 | return content
135 | }
136 |
137 | // Windows;
138 | fn run_exec_win(args []string) []string {
139 | mut pro := os.new_process('cmd')
140 |
141 | mut argsa := ['/min', '/c', args.join(' ')]
142 | pro.set_args(argsa)
143 |
144 | pro.set_redirect_stdio()
145 | pro.run()
146 |
147 | mut content := []string{}
148 | for pro.is_alive() {
149 | mut out := pro.stdout_read()
150 | if out.len > 0 {
151 | // println(out)
152 | for line in out.split_into_lines() {
153 | content << line.trim_space()
154 | }
155 | }
156 | }
157 |
158 | pro.close()
159 | return content
160 | }
161 |
--------------------------------------------------------------------------------
/.github/workflows/blank.yml:
--------------------------------------------------------------------------------
1 | name: Build binary artifacts
2 |
3 | #on:
4 | # push:
5 | # tags:
6 | # - weekly.**
7 | # - 0.**
8 |
9 | on:
10 | push:
11 | branches:
12 | - master
13 |
14 | jobs:
15 |
16 | build-linux:
17 | runs-on: ubuntu-20.04
18 | env:
19 | CC: gcc
20 | ZIPNAME: vide_linux.zip
21 | steps:
22 | - name: Setup V
23 | uses: vlang/setup-v@v1
24 | with:
25 | # Default: ${{ github.token }}
26 | token: ${{ github.token }}
27 | version: 'weekly.2023.02'
28 | version-file: ''
29 | check-latest: true
30 | stable: false
31 | architecture: ''
32 | - uses: actions/checkout@v1
33 | - name: Compile
34 | run: |
35 | sudo apt-get -qq update
36 | sudo apt-get -qq install libgc-dev
37 | sudo apt install build-essential
38 | sudo apt-get --yes --force-yes install libxi-dev libxcursor-dev mesa-common-dev
39 | sudo apt-get --yes --force-yes install libgl1-mesa-glx
40 | v install https://github.com/isaiahpatton/ui
41 | git clone https://github.com/isaiahpatton/vide
42 | v -cc $CC -skip-unused -gc boehm vide
43 | - name: Remove excluded
44 | run: |
45 | rm -rf .git
46 | - name: Create ZIP archive
47 | run: |
48 | zip -r9 --symlinks $ZIPNAME vide/
49 | - name: Create artifact
50 | uses: actions/upload-artifact@v2
51 | with:
52 | name: linux
53 | path: vide_linux.zip
54 |
55 | build-macos:
56 | runs-on: macos-latest
57 | env:
58 | CC: clang
59 | ZIPNAME: vide_macos.zip
60 | steps:
61 | - name: Setup V
62 | uses: vlang/setup-v@v1
63 | with:
64 | # Default: ${{ github.token }}
65 | token: ${{ github.token }}
66 | version: 'weekly.2023.02'
67 | version-file: ''
68 | check-latest: true
69 | stable: false
70 | architecture: ''
71 | - uses: actions/checkout@v1
72 | - name: Compile
73 | run: |
74 | v install https://github.com/isaiahpatton/ui
75 | git clone https://github.com/isaiahpatton/vide
76 | v -cc $CC -skip-unused -gc boehm vide
77 | - name: Remove excluded
78 | run: |
79 | rm -rf .git
80 | - name: Create ZIP archive
81 | run: |
82 | zip -r9 --symlinks $ZIPNAME vide/
83 | - name: Create artifact
84 | uses: actions/upload-artifact@v2
85 | with:
86 | name: macos
87 | path: vide_macos.zip
88 |
89 | build-windows:
90 | runs-on: windows-latest
91 | env:
92 | CC: msvc
93 | ZIPNAME: vide_windows.zip
94 | steps:
95 | - name: Setup V
96 | uses: vlang/setup-v@v1
97 | with:
98 | # Default: ${{ github.token }}
99 | token: ${{ github.token }}
100 | version: 'weekly.2023.02'
101 | version-file: ''
102 | check-latest: true
103 | stable: false
104 | architecture: ''
105 | - uses: actions/checkout@v1
106 | - uses: msys2/setup-msys2@v2
107 | - name: Compile
108 | run: |
109 | git clone https://github.com/vlang/v
110 | cd v
111 | .\make.bat
112 | .\v.exe install https://github.com/isaiahpatton/ui
113 | .\v.exe symlink
114 | git clone https://github.com/isaiahpatton/vide
115 | v -cc gcc -skip-unused -gc boehm -cflags -static vide
116 | - name: Remove excluded
117 | shell: msys2 {0}
118 | run: |
119 | rm -rf .git
120 | cd v
121 | cd vide
122 | rm -rf *.v
123 | rm -rf .git
124 | cd ..
125 | cd ..
126 | - name: Create archive
127 | shell: msys2 {0}
128 | run: |
129 | cd v
130 | cd vide
131 | cd ..
132 | powershell Compress-Archive vide $ZIPNAME
133 | mv $ZIPNAME ../
134 | cd ..
135 | # NB: the powershell Compress-Archive line is from:
136 | # https://superuser.com/a/1336434/194881
137 | # It is needed, because `zip` is not installed by default :-|
138 | - name: Create artifact
139 | uses: actions/upload-artifact@v2
140 | with:
141 | name: windows
142 | path: vide_windows.zip
--------------------------------------------------------------------------------
/src/new_proj.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 |
5 | fn (mut app App) new_project_click(mut win ui.Window, com ui.MenuItem) {
6 | app.new_project(mut win)
7 | }
8 |
9 | fn (mut app App) new_project(mut win ui.Window) {
10 | mut modal := ui.Modal.new(title: 'New Project')
11 |
12 | modal.top_off = 10
13 | modal.in_height = 370
14 |
15 | mut ip := ui.Panel.new(
16 | layout: ui.BoxLayout.new(ori: 1)
17 | )
18 |
19 | ip.add_child(create_input(mut win, 'Name', 'my project'))
20 | ip.add_child(create_input(mut win, 'Description', 'Hello world!'))
21 | ip.add_child(create_input(mut win, 'Version', '0.0.0'))
22 |
23 | ip.set_pos(10, 5)
24 | modal.add_child(ip)
25 |
26 | mut lic := make_license_section()
27 |
28 | mut lic_tb := ui.Titlebox.new(text: 'License', children: [lic])
29 | lic_tb.set_bounds(25, 125, 5, 25)
30 | modal.add_child(lic_tb)
31 |
32 | mut templ := make_templ_section()
33 | mut templ_tb := ui.Titlebox.new(text: 'Template', children: [templ])
34 | templ_tb.set_bounds(260, 125, 5, 25)
35 | modal.add_child(templ_tb)
36 |
37 | modal.needs_init = false
38 |
39 | mut close := ui.Button.new(
40 | text: 'Create'
41 | bounds: ui.Bounds{114, 334, 160, 28}
42 | )
43 |
44 | mut can := ui.Button.new(
45 | text: 'Cancel'
46 | bounds: ui.Bounds{10, 334, 100, 28}
47 | )
48 | can.set_area_filled(false)
49 | can.subscribe_event('mouse_up', fn (mut e ui.MouseEvent) {
50 | e.ctx.win.components = e.ctx.win.components.filter(mut it !is ui.Modal)
51 | })
52 | modal.add_child(can)
53 |
54 | win.extra_map['np-lic'] = 'MIT'
55 | win.extra_map['np-templ'] = 'hello_world'
56 |
57 | close.subscribe_event('mouse_up', app.new_project_click_close)
58 |
59 | modal.add_child(close)
60 | win.add_child(modal)
61 | }
62 |
63 | fn (mut app App) new_project_click_close(mut e ui.MouseEvent) {
64 | mut win := e.ctx.win
65 | name := win.get[&ui.TextField]('NewProj-Name').text
66 | des := win.get[&ui.TextField]('NewProj-Description').text
67 | ver := win.get[&ui.TextField]('NewProj-Version').text
68 |
69 | lic := win.extra_map['np-lic']
70 | dir := app.confg.workspace_dir
71 | templ := win.extra_map['np-templ']
72 |
73 | new_project(
74 | name: name
75 | description: des
76 | version: ver
77 | license: lic
78 | template: templ
79 | app: app
80 | )
81 |
82 | win.components = win.components.filter(mut it !is ui.Modal)
83 |
84 | mut com := win.get[&ui.Tree2]('proj-tree')
85 | refresh_tree(mut win, dir, mut com)
86 | }
87 |
88 | fn make_license_section() &ui.Panel {
89 | mut p := ui.Panel.new(
90 | layout: ui.BoxLayout.new(ori: 1)
91 | )
92 |
93 | choices := [
94 | 'MIT',
95 | 'Unlicense/CC0',
96 | 'GPL',
97 | 'Apache',
98 | 'Mozilla Public',
99 | 'All Rights Reserved',
100 | 'Other',
101 | ]
102 |
103 | mut box := ui.Selectbox.new(
104 | text: 'MIT'
105 | items: choices
106 | )
107 | box.set_bounds(-5, 0, 200, 0)
108 |
109 | box.subscribe_event('item_change', fn (mut e ui.ItemChangeEvent) {
110 | e.ctx.win.extra_map['np-lic'] = e.target.text
111 | })
112 |
113 | p.add_child(box)
114 | return p
115 | }
116 |
117 | fn make_templ_section() &ui.Panel {
118 | mut p := ui.Panel.new(
119 | layout: ui.BoxLayout.new(ori: 1)
120 | )
121 |
122 | choices := ['hello_world', 'web', 'basic_window', 'border_layout']
123 |
124 | mut group := ui.buttongroup[ui.Checkbox]()
125 | for choice in choices {
126 | mut box := ui.Checkbox.new(text: choice)
127 | box.set_bounds(0, 4, 190, 30)
128 | box.subscribe_event('draw', checkbox_pack_height)
129 |
130 | group.add(box)
131 | p.add_child(box)
132 | }
133 |
134 | group.subscribe_event('mouse_up', fn (mut e ui.MouseEvent) {
135 | e.ctx.win.extra_map['np-templ'] = e.target.text
136 | })
137 |
138 | group.setup()
139 | return p
140 | }
141 |
142 | fn checkbox_pack_height(mut e ui.DrawEvent) {
143 | e.target.height = e.ctx.line_height + 5
144 | }
145 |
146 | fn create_input(mut win ui.Window, title string, val string) &ui.Panel {
147 | mut box := ui.Panel.new(
148 | layout: ui.BoxLayout.new(ori: 0, vgap: 1)
149 | )
150 |
151 | mut work_lbl := ui.Label.new(text: title)
152 |
153 | work_lbl.set_bounds(0, 4, 125, 30)
154 | box.add_child(work_lbl)
155 |
156 | mut work := ui.TextField.new(text: val)
157 |
158 | work.subscribe_event('draw', fn (mut e ui.DrawEvent) {
159 | txt := e.target.text
160 | e.target.width = int(f64_max(300, e.ctx.text_width(txt) + txt.len))
161 | e.target.height = e.ctx.line_height + 8
162 | })
163 |
164 | work.set_id(mut win, 'NewProj-' + title)
165 | box.add_child(work)
166 |
167 | return box
168 | }
169 |
--------------------------------------------------------------------------------
/src/verminal.v:
--------------------------------------------------------------------------------
1 | //
2 | // Verminal - Terminal Emulator in V
3 | //
4 | module main
5 |
6 | import iui as ui
7 | import os
8 |
9 | pub fn create_box(mut win ui.Window) &ui.Textbox {
10 | path := os.real_path(os.home_dir())
11 | win.extra_map['path'] = path
12 |
13 | mut box := ui.Textbox.new(lines: [path + '>'])
14 | box.set_id(mut win, 'vermbox')
15 |
16 | box.subscribe_event('draw', vermbox_draw)
17 |
18 | box.before_txtc_event_fn = before_txt_change
19 | box.set_bounds(0, 0, 300, 80)
20 |
21 | return box
22 | }
23 |
24 | fn vermbox_draw(mut e ui.DrawEvent) {
25 | mut this := e.ctx.win.get[&ui.Textbox]('vermbox')
26 |
27 | this.caret_y = this.lines.len - 1
28 | line := this.lines[this.caret_y]
29 | cp := e.ctx.win.extra_map['path']
30 |
31 | if line.contains(cp + '>') {
32 | if this.caret_x < cp.len + 1 {
33 | this.caret_x = cp.len + 1
34 | }
35 | }
36 |
37 | hei := (this.lines.len + 1) * ui.get_line_height(e.ctx)
38 | pw := this.parent.height
39 | if hei < pw {
40 | this.height = pw
41 | } else {
42 | this.height = hei
43 | }
44 |
45 | this.width = this.parent.width
46 |
47 | if 'update_scroll' in e.ctx.win.extra_map {
48 | jump_sv(mut e.ctx.win, this.height, this.lines.len)
49 | e.ctx.win.extra_map.delete('update_scroll')
50 | }
51 | }
52 |
53 | fn before_txt_change(mut win ui.Window, tb ui.Textbox) bool {
54 | is_backsp := tb.last_letter == 'backspace'
55 |
56 | if is_backsp {
57 | txt := tb.lines[tb.caret_y]
58 | path := win.extra_map['path']
59 | if txt.ends_with(path + '>') {
60 | return true
61 | }
62 | }
63 |
64 | is_enter := tb.last_letter == 'enter'
65 | jump_sv(mut win, tb.height, tb.lines.len)
66 |
67 | if is_enter {
68 | mut tbox := win.get[&ui.Textbox]('vermbox')
69 | tbox.last_letter = ''
70 |
71 | mut txt := tb.lines[tb.caret_y]
72 | mut cline := txt // txt[txt.len - 1]
73 | mut path := win.extra_map['path']
74 |
75 | if cline.contains(path + '>') {
76 | mut cmd := cline.split(path + '>')[1]
77 | on_cmd(mut win, tb, cmd)
78 | }
79 | return true
80 | }
81 | return false
82 | }
83 |
84 | fn jump_sv(mut win ui.Window, tbh int, lines int) {
85 | mut sv := win.get[&ui.ScrollView]('vermsv')
86 | val := tbh - sv.height
87 | if lines <= 1 {
88 | sv.scroll_i = 0
89 | return
90 | }
91 | sv.scroll_i = val / sv.increment
92 | }
93 |
94 | fn on_cmd(mut win ui.Window, box ui.Textbox, cmd string) {
95 | args := cmd.split(' ')
96 |
97 | mut tbox := win.get[&ui.Textbox]('vermbox')
98 | if args[0] == 'cd' {
99 | cmd_cd(mut win, mut tbox, args)
100 | add_new_input_line(mut tbox, win)
101 | } else if args[0] == 'help' {
102 | tbox.lines << win.extra_map['verm-help']
103 | add_new_input_line(mut tbox, win)
104 | } else if args[0] == 'version' || args[0] == 'ver' {
105 | tbox.lines << 'Verminal: 0.5'
106 | add_new_input_line(mut tbox, win)
107 | } else if args[0] == 'cls' || args[0] == 'clear' {
108 | tbox.lines.clear()
109 | tbox.scroll_i = 0
110 | add_new_input_line(mut tbox, win)
111 | } else if args[0] == 'font-size' {
112 | win.font_size = args[1].int()
113 | add_new_input_line(mut tbox, win)
114 | } else if args[0] == 'dira' {
115 | mut path := win.extra_map['path']
116 | cmd_dir(mut tbox, path, args)
117 | add_new_input_line(mut tbox, win)
118 | } else if args[0] == 'loadfiles' {
119 | $if emscripten ? {
120 | C.emscripten_run_script(c'iui.trigger = "lloadfiles"')
121 | }
122 | mut com := win.get[&ui.Tree2]('proj-tree')
123 | if args.len == 1 {
124 | refresh_tree(mut win, '/home/web_user/.vide/workspace', mut com)
125 | } else {
126 | refresh_tree(mut win, args[1], mut com)
127 | }
128 | } else if args[0] == 'v' || args[0] == 'dir' || args[0] == 'git' {
129 | spawn verminal_cmd_exec(mut win, mut tbox, args)
130 | } else if args[0].len == 2 && args[0].ends_with(':') {
131 | win.extra_map['path'] = os.real_path(args[0])
132 | add_new_input_line(mut tbox, win)
133 | tbox.caret_y += 1
134 | } else {
135 | verminal_cmd_exec(mut win, mut tbox, args)
136 | }
137 |
138 | jump_sv(mut win, box.height, tbox.lines.len)
139 |
140 | win.extra_map['update_scroll'] = 'true'
141 | win.extra_map['lastcmd'] = cmd
142 | }
143 |
144 | fn wasm_save_files() {
145 | $if emscripten ? {
146 | C.emscripten_run_script(c'iui.trigger = "savefiles"')
147 | }
148 | }
149 |
150 | fn write_file(path string, text string) ! {
151 | println('Writing content to ${path}')
152 | os.write_file(path, text)!
153 | if path.ends_with('.v') || path.ends_with('.mod') {
154 | wasm_save_files()
155 | }
156 | }
157 |
158 | fn add_new_input_line(mut tbox ui.Textbox, win &ui.Window) {
159 | tbox.lines << win.extra_map['path'] + '>'
160 | }
161 |
--------------------------------------------------------------------------------
/.github/workflows/rele.yml:
--------------------------------------------------------------------------------
1 | name: Build binary artifacts
2 |
3 | on:
4 | push:
5 | tags:
6 | - weekly.**
7 | - 0.**
8 |
9 | jobs:
10 |
11 | build-linux:
12 | runs-on: ubuntu-20.04
13 | env:
14 | CC: gcc
15 | ZIPNAME: vide_linux.zip
16 | steps:
17 | - name: Setup V
18 | uses: vlang/setup-v@v1
19 | with:
20 | # Default: ${{ github.token }}
21 | token: ${{ github.token }}
22 | version: 'weekly.2023.02'
23 | version-file: ''
24 | check-latest: true
25 | stable: false
26 | architecture: ''
27 | - uses: actions/checkout@v1
28 | - name: Compile
29 | run: |
30 | sudo apt-get -qq update
31 | sudo apt-get -qq install libgc-dev
32 | sudo apt install build-essential
33 | sudo apt-get --yes --force-yes install libxi-dev libxcursor-dev mesa-common-dev
34 | sudo apt-get --yes --force-yes install libgl1-mesa-glx
35 | v install https://github.com/isaiahpatton/ui
36 | git clone https://github.com/isaiahpatton/vide
37 | v -cc $CC -skip-unused -gc boehm vide
38 | - name: Remove excluded
39 | run: |
40 | rm -rf .git
41 | - name: Create ZIP archive
42 | run: |
43 | zip -r9 --symlinks $ZIPNAME vide/
44 | - name: Create artifact
45 | uses: actions/upload-artifact@v2
46 | with:
47 | name: linux
48 | path: vide_linux.zip
49 |
50 | build-macos:
51 | runs-on: macos-latest
52 | env:
53 | CC: clang
54 | ZIPNAME: vide_macos.zip
55 | steps:
56 | - name: Setup V
57 | uses: vlang/setup-v@v1
58 | with:
59 | # Default: ${{ github.token }}
60 | token: ${{ github.token }}
61 | version: 'weekly.2023.02'
62 | version-file: ''
63 | check-latest: true
64 | stable: false
65 | architecture: ''
66 | - uses: actions/checkout@v1
67 | - name: Compile
68 | run: |
69 | v install https://github.com/isaiahpatton/ui
70 | git clone https://github.com/isaiahpatton/vide
71 | v -cc $CC -skip-unused -gc boehm vide
72 | - name: Remove excluded
73 | run: |
74 | rm -rf .git
75 | - name: Create ZIP archive
76 | run: |
77 | zip -r9 --symlinks $ZIPNAME vide/
78 | - name: Create artifact
79 | uses: actions/upload-artifact@v2
80 | with:
81 | name: macos
82 | path: vide_macos.zip
83 |
84 | build-windows:
85 | runs-on: windows-latest
86 | env:
87 | CC: msvc
88 | ZIPNAME: vide_windows.zip
89 | steps:
90 | - name: Setup V
91 | uses: vlang/setup-v@v1
92 | with:
93 | # Default: ${{ github.token }}
94 | token: ${{ github.token }}
95 | version: 'weekly.2023.02'
96 | version-file: ''
97 | check-latest: true
98 | stable: false
99 | architecture: ''
100 | - uses: actions/checkout@v1
101 | - uses: msys2/setup-msys2@v2
102 | - name: Compile
103 | run: |
104 | git clone https://github.com/vlang/v
105 | cd v
106 | .\make.bat
107 | .\v.exe install https://github.com/isaiahpatton/ui
108 | .\v.exe symlink
109 | git clone https://github.com/isaiahpatton/vide
110 | v -cc gcc -skip-unused -gc boehm -cflags -static vide
111 | - name: Remove excluded
112 | shell: msys2 {0}
113 | run: |
114 | rm -rf .git
115 | cd v
116 | cd vide
117 | rm -rf *.v
118 | rm -rf .git
119 | cd ..
120 | cd ..
121 | - name: Create archive
122 | shell: msys2 {0}
123 | run: |
124 | cd v
125 | cd vide
126 | cd ..
127 | powershell Compress-Archive vide $ZIPNAME
128 | mv $ZIPNAME ../
129 | cd ..
130 | # NB: the powershell Compress-Archive line is from:
131 | # https://superuser.com/a/1336434/194881
132 | # It is needed, because `zip` is not installed by default :-|
133 | - name: Create artifact
134 | uses: actions/upload-artifact@v2
135 | with:
136 | name: windows
137 | path: vide_windows.zip
138 |
139 | release:
140 | name: Create Github Release
141 | needs: [build-linux, build-windows, build-macos]
142 | runs-on: ubuntu-20.04
143 | steps:
144 | - name: Get short tag name
145 | uses: jungwinter/split@v1
146 | id: split
147 | with:
148 | msg: ${{ github.ref }}
149 | seperator: /
150 | - name: Create Release
151 | id: create_release
152 | uses: ncipollo/release-action@v1
153 | with:
154 | token: ${{ secrets.GITHUB_TOKEN }}
155 | tag: ${{ steps.split.outputs._2 }}
156 | name: ${{ steps.split.outputs._2 }}
157 | commit: ${{ github.sha }}
158 | draft: false
159 | prerelease: false
160 |
161 | publish:
162 | needs: [release]
163 | runs-on: ubuntu-20.04
164 | strategy:
165 | matrix:
166 | version: [linux, macos, windows]
167 | steps:
168 | - uses: actions/checkout@v1
169 | - name: Fetch artifacts
170 | uses: actions/download-artifact@v1
171 | with:
172 | name: ${{ matrix.version }}
173 | path: ./${{ matrix.version }}
174 | - name: Get short tag name
175 | uses: jungwinter/split@v1
176 | id: split
177 | with:
178 | msg: ${{ github.ref }}
179 | seperator: /
180 | - name: Get release
181 | id: get_release_info
182 | uses: bruceadams/get-release@v1.3.2
183 | env:
184 | GITHUB_TOKEN: ${{ github.token }}
185 | - name: Upload Release Asset
186 | id: upload-release-asset
187 | uses: actions/upload-release-asset@v1.0.1
188 | env:
189 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
190 | with:
191 | upload_url: ${{ steps.get_release_info.outputs.upload_url }}
192 | asset_path: ${{ matrix.version }}/vide_${{ matrix.version }}.zip
193 | asset_name: vide_${{ matrix.version }}.zip
194 | asset_content_type: application/zip
195 |
--------------------------------------------------------------------------------
/src/code_suggestions.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 | import os
5 |
6 | const modules = ['arrays', 'benchmark', 'bitfield', 'cli', 'clipboard', 'clipboard.dummy',
7 | 'clipboard.x11', 'compress', 'compress.deflate', 'compress.gzip', 'compress.zlib', 'context',
8 | 'context.onecontext', 'crypto', 'crypto.aes', 'crypto.bcrypt', 'crypto.blowfish', 'crypto.cipher',
9 | 'crypto.des', 'crypto.ed25519', 'crypto.ed25519.internal.edwards25519', 'crypto.hmac',
10 | 'crypto.internal.subtle', 'crypto.md5', 'crypto.pem', 'crypto.rand', 'crypto.rc4', 'crypto.sha1',
11 | 'crypto.sha256', 'crypto.sha512', 'datatypes', 'datatypes.fsm', 'main', 'db', 'db.mssql',
12 | 'db.mysql', 'db.pg', 'db.sqlite', 'dl', 'dl.loader', 'dlmalloc', 'encoding', 'encoding.base32',
13 | 'encoding.base58', 'encoding.base64', 'encoding.binary', 'encoding.csv', 'encoding.hex',
14 | 'encoding.html', 'encoding.leb128', 'encoding.utf8', 'encoding.utf8.east_asian', 'eventbus',
15 | 'flag', 'fontstash', 'gg', 'gg.m4', 'gx', 'hash', 'hash.crc32', 'hash.fnv1a', 'io', 'io.util',
16 | 'json', 'json.cjson', 'log', 'maps', 'math', 'math.big', 'math.bits', 'math.complex',
17 | 'math.fractions', 'math.internal', 'math.stats', 'math.unsigned', 'math.vec', 'mssql', 'mysql',
18 | 'net', 'net.conv', 'net.ftp', 'net.html', 'net.http', 'net.http.chunked', 'net.http.mime',
19 | 'net.mbedtls', 'net.openssl', 'net.smtp', 'net.ssl', 'net.unix', 'net.urllib', 'net.websocket',
20 | 'orm', 'os', 'os.cmdline', 'os.filelock', 'os.font', 'os.notify', 'pg', 'picoev',
21 | 'picohttpparser', 'rand', 'rand.buffer', 'rand.config', 'rand.constants', 'rand.mt19937',
22 | 'rand.musl', 'rand.pcg32', 'rand.seed', 'rand.splitmix64', 'rand.sys', 'rand.wyrand',
23 | 'rand.xoroshiro128pp', 'readline', 'regex', 'runtime', 'semver', 'sokol', 'sokol.audio',
24 | 'sokol.c', 'sokol.f', 'sokol.gfx', 'sokol.memory', 'sokol.sapp', 'sokol.sfons', 'sokol.sgl',
25 | 'sqlite', 'stbi', 'strconv', 'strings', 'strings.textscanner', 'sync', 'sync.pool',
26 | 'sync.stdatomic', 'szip', 'term', 'term.termios', 'term.ui', 'time', 'time.misc', 'toml',
27 | 'toml.ast', 'toml.ast.walker', 'toml.checker', 'toml.decoder', 'toml.input', 'toml.parser',
28 | 'toml.scanner', 'toml.to', 'toml.token', 'toml.util', 'vweb', 'vweb.assets', 'vweb.csrf',
29 | 'vweb.sse', 'wasm', 'x', 'x.json2', 'x.ttf']
30 |
31 | fn find_all_dot_match(sub string, mut e ui.DrawTextlineEvent) ([]string, int, int) {
32 | doti := sub.index('.') or { return [''], 0, 0 }
33 | dot := sub[0..(doti + 1)]
34 | aft := sub[(doti + 1)..]
35 |
36 | dw := e.ctx.gg.text_width(dot)
37 |
38 | trim := dot.trim_space()
39 |
40 | mut mats := find_all_matches(mut e.ctx.win, trim, aft)
41 | mats.sort(a.len > b.len)
42 | return mats, dw, aft.len
43 | }
44 |
45 | fn text_box_active_line_draw(mut e ui.DrawTextlineEvent) {
46 | mut box := e.target
47 | if mut box is ui.Textbox {
48 | txt := box.lines[e.line]
49 |
50 | sub := txt[0..box.caret_x].replace('\t', ' '.repeat(8))
51 |
52 | line_height := ui.get_line_height(e.ctx) + 5
53 |
54 | mut mats, mut dw, mut aft := []string{}, 0, 0
55 |
56 | if sub.index('.') or { -1 } != -1 {
57 | mats, dw, aft = find_all_dot_match(sub, mut e)
58 | }
59 |
60 | mut app := e.ctx.win.get[&App]('app')
61 |
62 | if app.popup.shown {
63 | app.popup.hide(e.ctx)
64 | }
65 |
66 | if sub.starts_with('import ') && sub.len > 'import '.len {
67 | spl := sub.split('import ')[1]
68 | for s in modules {
69 | if s.starts_with(spl) {
70 | mats << s
71 | }
72 | }
73 | if mats.len == 1 && sub.contains(mats[0]) {
74 | return
75 | }
76 | dw = e.ctx.text_width('import ')
77 | aft = spl.len
78 | }
79 |
80 | if mats.len == 0 {
81 | if app.popup.shown {
82 | app.popup.hide(e.ctx)
83 | }
84 | return
85 | }
86 |
87 | if mats.len == 1 {
88 | if sub.ends_with(mats[0]) {
89 | return
90 | }
91 | }
92 |
93 | mut max_wid := e.ctx.gg.text_width(mats[0] + ' ')
94 | if max_wid < 100 {
95 | max_wid = 100
96 | }
97 |
98 | for mat in mats {
99 | mw := e.ctx.gg.text_width(mat)
100 | if mw > max_wid {
101 | max_wid = mw
102 | }
103 | }
104 |
105 | x := e.x + dw - 4
106 |
107 | e.ctx.gg.draw_rect_empty(x, e.y, max_wid, line_height, e.ctx.theme.button_border_normal)
108 |
109 | px := e.x + dw - e.target.x - 4
110 | py := e.y + line_height - e.target.y
111 |
112 | if app.popup.shown {
113 | app.popup.hide(e.ctx)
114 | }
115 |
116 | app.popup.width = max_wid
117 | app.popup.sv.width = max_wid
118 | app.popup.p.width = max_wid
119 | ph := line_height * mats.len
120 | app.popup.p.height = ph
121 | if ph < 150 {
122 | app.popup.height = ph
123 | app.popup.sv.height = ph
124 | } else {
125 | app.popup.height = 150
126 | app.popup.sv.height = 150
127 | }
128 |
129 | app.popup.set_texts(mut box, mats, aft)
130 | app.popup.show(box, px, py, e.ctx)
131 | }
132 | }
133 |
134 | fn find_all_matches(mut win ui.Window, mod string, str string) []string {
135 | if str.len <= 0 {
136 | return []
137 | }
138 | strs := find_all_fn_in_vlib(mut win, mod)
139 |
140 | for st in strs {
141 | if st == str {
142 | return [st]
143 | }
144 | }
145 |
146 | mut matches := []string{}
147 | for st in strs {
148 | if st.contains(str) && !matches.contains(st) {
149 | matches << st
150 | }
151 | }
152 | return matches
153 | }
154 |
155 | fn all_vlib_mod(mut win ui.Window) []string {
156 | id := 'vlib'
157 | if id in win.extra_map {
158 | return win.extra_map[id].split(' ')
159 | }
160 |
161 | mut arr := []string{}
162 | mut vlib := os.dir(get_v_exe()).replace('\\', '/') + '/vlib'
163 | for file in os.ls(vlib) or { [''] } {
164 | arr << file
165 | }
166 | win.extra_map[id] = arr.join(' ')
167 | return arr
168 | }
169 |
170 | fn find_all_fn_in_vlib(mut win ui.Window, mod string) []string {
171 | id := 'sug-' + mod
172 | if id in win.extra_map {
173 | return win.extra_map[id].split(' ')
174 | }
175 |
176 | mut arr := []string{}
177 | mut vlib := os.dir(get_v_exe()).replace('\\', '/') + '/vlib'
178 | mut mod_dir := vlib + '/' + mod
179 | for file in os.ls(mod_dir) or { [''] } {
180 | lines := os.read_lines(mod_dir + '/' + file) or { [''] }
181 | for line in lines {
182 | if line.starts_with('pub fn') && !line.starts_with('pub fn (') {
183 | name := line.split('pub fn ')[1].split('(')[0]
184 | arr << name
185 | }
186 | }
187 | }
188 | win.extra_map[id] = arr.join(' ')
189 | return arr
190 | }
191 |
--------------------------------------------------------------------------------
/src/tabs.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 | import os
5 | import clipboard
6 |
7 | fn (mut app App) welcome_tab(folder string) {
8 | mut logo := ui.image_from_bytes(mut app.win, vide_png1.to_bytes(), 229, 90)
9 | logo.set_bounds(0, 0, 229, 90)
10 |
11 | mut info_lbl := ui.Label.new(
12 | text: 'Simple IDE for V made in V.'
13 | bold: true
14 | )
15 |
16 | padding_top := 5
17 |
18 | mut vbox := ui.Panel.new(
19 | layout: ui.BoxLayout.new(ori: 1, vgap: 15)
20 | )
21 |
22 | info_lbl.set_pos(0, 0)
23 | info_lbl.pack()
24 |
25 | mut hbox := ui.Panel.new(
26 | layout: ui.BoxLayout.new(hgap: 0, vgap: 0)
27 | )
28 |
29 | hbox.add_child(logo)
30 | hbox.set_bounds(0, 0, 230, 51)
31 |
32 | vbox.add_child(hbox)
33 | vbox.add_child(info_lbl)
34 |
35 | mut sw := ui.Titlebox.new(text: 'Start', children: [app.start_with()], padding: 4)
36 | vbox.add_child(sw)
37 |
38 | mut lbox := app.links_box()
39 | lbox.set_pos(1, 0)
40 |
41 | mut box := ui.Panel.new(
42 | layout: ui.BorderLayout.new(hgap: 25)
43 | )
44 | box.set_bounds(0, padding_top, 550, 350)
45 | box.subscribe_event('draw', center_box)
46 |
47 | mut sbox := app.south_panel()
48 |
49 | box.add_child_with_flag(vbox, ui.borderlayout_center)
50 | box.add_child_with_flag(lbox, ui.borderlayout_east)
51 | box.add_child_with_flag(sbox, ui.borderlayout_south)
52 |
53 | mut sv := ui.ScrollView.new(
54 | view: box
55 | )
56 | sv.set_border_painted(false)
57 |
58 | app.tb.add_child('Welcome', sv)
59 | }
60 |
61 | fn center_box(mut e ui.DrawEvent) {
62 | pw := e.target.parent.width
63 | x := (pw / 2) - (e.target.width / 2)
64 | if pw > 550 {
65 | e.target.set_x(x / 2)
66 | }
67 | }
68 |
69 | fn (mut app App) start_with() &ui.Panel {
70 | mut p := ui.Panel.new(layout: ui.BoxLayout.new(ori: 1))
71 |
72 | mut btn := ui.Button.new(text: 'New Project')
73 | btn.set_bounds(0, 0, 150, 30)
74 | btn.subscribe_event('mouse_up', fn [mut app] (mut e ui.MouseEvent) {
75 | app.new_project(mut e.ctx.win)
76 | })
77 |
78 | mut btn2 := ui.Button.new(text: 'V Documentation')
79 | btn2.set_bounds(0, 0, 150, 30)
80 | btn2.subscribe_event('mouse_up', fn (mut e ui.MouseEvent) {
81 | // app.new_project(mut e.ctx.win)
82 | ui.open_url('https://vlang.io/docs')
83 | // new_tab(e.ctx.win, 'C:\\v\\doc\\docs.md')
84 | })
85 |
86 | p.add_child(btn)
87 | p.add_child(btn2)
88 | return p
89 | }
90 |
91 | fn (mut app App) south_panel() &ui.Panel {
92 | mut p := ui.Panel.new()
93 |
94 | res := os.execute('${app.confg.vexe} version')
95 |
96 | mut out := res.output
97 | if !out.contains('V ') {
98 | out = 'Error executing "v version"\nPlease see Settings'
99 | }
100 |
101 | mut btn := ui.Label.new(
102 | text: 'Compiler: ${out}'
103 | em_size: 0.85
104 | )
105 | btn.pack()
106 |
107 | p.add_child(btn)
108 | return p
109 | }
110 |
111 | fn (mut app App) links_box() &ui.Panel {
112 | mut box := ui.Panel.new(
113 | layout: ui.BoxLayout.new(ori: 1, vgap: 6)
114 | )
115 |
116 | mut title := ui.Label.new(
117 | text: 'Useful Links:'
118 | em_size: 1.2
119 | )
120 | title.pack()
121 | box.add_child(title)
122 |
123 | links := [
124 | 'V Documentation|vlang.io/docs',
125 | 'V stdlib docs|modules.vlang.io',
126 | 'V on Github|github.com/vlang/v',
127 | 'Vide on Github|github.com/pisaiah/vide',
128 | 'Vide on Discord|discord.gg/NruVtYBf5g',
129 | 'r/vlang|reddit.com/r/vlang',
130 | ]
131 |
132 | for val in links {
133 | spl := val.split('|')
134 | mut link := ui.link(
135 | text: spl[0]
136 | url: 'https://' + spl[1]
137 | )
138 | link.set_bounds(4, 0, 150, 25)
139 | box.add_child(link)
140 | }
141 |
142 | mut vv := ui.Label.new(
143 | text: 'Vide™ ${version} - iUI ${ui.version}'
144 | em_size: 0.8
145 | )
146 | vv.set_pos(0, 10)
147 | vv.set_bounds(5, 8, 150, 40)
148 |
149 | box.add_child(vv)
150 | return box
151 | }
152 |
153 | fn new_tab(window &ui.Window, file string) {
154 | dump('opening ' + file)
155 | mut tb := window.get[&ui.Tabbox]('main-tabs')
156 |
157 | if file in tb.kids {
158 | // Don't remake already open tab
159 | tb.active_tab = file
160 | return
161 | }
162 |
163 | if file.ends_with('.vide_test') {
164 | // TODO
165 | }
166 |
167 | if file.ends_with('.png') {
168 | // Test
169 | p := image_view(file)
170 | tb.add_child(file, p)
171 | tb.active_tab = file
172 | return
173 | }
174 |
175 | lines := os.read_lines(file) or { ['ERROR while reading file contents'] }
176 |
177 | mut code_box := ui.Textbox.new(lines: lines)
178 | code_box.text = file
179 |
180 | code_box.set_bounds(0, 0, 620, 250)
181 |
182 | mut scroll_view := ui.scroll_view(
183 | bounds: ui.Bounds{0, 0, 620, 250}
184 | view: code_box
185 | increment: 16
186 | padding: 0
187 | )
188 |
189 | scroll_view.set_border_painted(false)
190 |
191 | scroll_view.subscribe_event('draw', fn (mut e ui.DrawEvent) {
192 | mut tb := e.ctx.win.get[&ui.Tabbox]('main-tabs')
193 | e.target.width = tb.width
194 | e.target.height = tb.height - 26
195 | })
196 |
197 | code_box.subscribe_event('draw', code_box_draw)
198 |
199 | code_box.subscribe_event('current_line_draw', text_box_active_line_draw)
200 |
201 | code_box.before_txtc_event_fn = text_change
202 |
203 | tb.add_child(file, scroll_view)
204 | tb.active_tab = file
205 | }
206 |
207 | fn execute_syntax_check(file string) {
208 | vexe := get_v_exe()
209 | res := os.execute('${vexe} -check-syntax ${file}')
210 | dump(res)
211 | }
212 |
213 | fn code_box_draw(mut e ui.DrawEvent) {
214 | mut tb := e.ctx.win.get[&ui.Tabbox]('main-tabs')
215 | mut cb := e.target
216 | file := e.target.text
217 |
218 | if mut cb is ui.Textbox {
219 | e.target.width = tb.width
220 | hei := ui.get_line_height(e.ctx) * (cb.lines.len + 1)
221 | min := tb.height - 30
222 | if hei > min {
223 | cb.height = hei
224 | } else if cb.height < min {
225 | cb.height = min
226 | }
227 |
228 | // Do save
229 | if cb.ctrl_down && cb.last_letter == 's' {
230 | cb.ctrl_down = false
231 | write_file(file, cb.lines.join('\n')) or {}
232 | execute_syntax_check(file)
233 | }
234 |
235 | // Copy
236 | if cb.ctrl_down && cb.last_letter == 'c' {
237 | cb.ctrl_down = false
238 | // dump(cb.sel)
239 | }
240 |
241 | // Paste
242 | if cb.ctrl_down && cb.last_letter == 'v' {
243 | do_paste(mut cb)
244 | }
245 | }
246 | }
247 |
248 | fn do_paste(mut cb ui.Textbox) {
249 | cb.ctrl_down = false
250 | mut c := clipboard.new()
251 |
252 | cl := cb.lines[cb.caret_y]
253 | be := cl[..cb.caret_x]
254 | af := cl[cb.caret_x..]
255 |
256 | plines := c.get_text().split_into_lines()
257 |
258 | if plines.len == 0 {
259 | c.destroy()
260 | return
261 | }
262 |
263 | if plines.len == 1 {
264 | cb.lines[cb.caret_y] = be + plines[0] + af
265 | } else {
266 | cb.lines[cb.caret_y] = be + plines[0]
267 | for i in 1 .. plines.len - 1 {
268 | cb.lines.insert(cb.caret_y + i, plines[i])
269 | }
270 | cb.lines.insert(cb.caret_y + (plines.len - 1), plines[plines.len - 1] + af)
271 | cb.caret_y = cb.caret_y + (plines.len - 1)
272 | cb.caret_x = plines[plines.len - 1].len
273 | }
274 |
275 | c.destroy()
276 | }
277 |
278 | fn text_change(mut w ui.Window, cb ui.Textbox) bool {
279 | if cb.last_letter == 'backspace' {
280 | }
281 |
282 | if cb.ctrl_down && cb.last_letter == 'c' {
283 | mut x0 := cb.sel or { ui.Selection{} }.x0
284 | mut y0 := cb.sel or { ui.Selection{} }.y0
285 |
286 | mut x1 := cb.sel or { ui.Selection{} }.x1
287 | mut y1 := cb.sel or { ui.Selection{} }.y1
288 |
289 | if y1 > cb.lines.len - 1 {
290 | y1 = cb.lines.len - 1
291 | }
292 |
293 | if y1 < y0 {
294 | sy := if y1 > y0 { y0 } else { y1 }
295 | ey := if y1 > y0 { y1 } else { y0 }
296 | y0 = sy
297 | y1 = ey
298 | sx := x1
299 | ex := x0
300 | x0 = sx
301 | x1 = ex
302 | }
303 |
304 | mut lines := []string{}
305 |
306 | fl := cb.lines[y0][x0..]
307 | el := cb.lines[y1][..x1]
308 |
309 | lines << fl
310 | for i in (y0 + 1) .. y1 {
311 | lines << cb.lines[i]
312 | }
313 | lines << el
314 |
315 | mut c := clipboard.new()
316 | c.copy(lines.join('\n'))
317 | c.destroy()
318 |
319 | return true
320 | }
321 | return false
322 | }
323 |
--------------------------------------------------------------------------------
/src/menus.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 | import os
5 |
6 | const vide_png0 = $embed_file('assets/ezgif.com-gif-maker(5).png')
7 | const vide_png1 = $embed_file('assets/word.png')
8 |
9 | const i_w = 24
10 | const i_h = 24
11 |
12 | pub fn (mut app App) iicon(c bool, b []u8) &ui.Image {
13 | if !c {
14 | return unsafe { nil }
15 | }
16 | return ui.image_from_bytes(mut app.win, b, i_w, i_h)
17 | }
18 |
19 | fn (mut app App) make_menubar() {
20 | // Setup Menubar and items
21 | mut window := app.win
22 | window.bar = ui.Menubar.new()
23 | window.bar.set_padding(4)
24 |
25 | // file_img := $embed_file('assets/file-icon.png')
26 | // edit_img := $embed_file('assets/icons8-edit-24.png')
27 | // help_img := $embed_file('assets/help-icon.png')
28 | save_img := $embed_file('assets/icons8-save-24.png')
29 | // theme_img := $embed_file('assets/icons8-change-theme-24.png')
30 | run_img := $embed_file('assets/run.png')
31 | fmt_img := $embed_file('assets/fmt.png')
32 |
33 | colored := true
34 |
35 | // file_icon := app.iicon(colored, file_img.to_bytes())
36 | // edit_icon := app.iicon(colored, edit_img.to_bytes())
37 | // help_icon := app.iicon(colored, help_img.to_bytes())
38 | save_icon := app.iicon(colored, save_img.to_bytes())
39 | // theme_icon := app.iicon(colored, theme_img.to_bytes())
40 | run_icon := app.iicon(colored, run_img.to_bytes())
41 | fmt_icon := app.iicon(colored, fmt_img.to_bytes())
42 |
43 | file_menu := ui.MenuItem.new(
44 | text: 'File'
45 | // icon: file_icon
46 | uicon: '\uE132'
47 | children: [
48 | ui.MenuItem.new(
49 | text: 'New Project..'
50 | uicon: '\uE9AF'
51 | click_event_fn: app.new_project_click
52 | ),
53 | ui.MenuItem.new(
54 | text: 'New File...'
55 | uicon: '\uE132'
56 | // click_event_fn: new_file_click
57 | ),
58 | ui.MenuItem.new(
59 | uicon: '\uE105'
60 | text: 'Save'
61 | click_event_fn: save_click
62 | ),
63 | ui.MenuItem.new(
64 | text: 'Run'
65 | uicon: '\uEA16'
66 | click_event_fn: run_click
67 | ),
68 | ui.MenuItem.new(
69 | text: 'Manage Modules..'
70 | uicon: '\uEAE8'
71 | // click_event_fn: vpm_click_
72 | ),
73 | ui.MenuItem.new(
74 | text: 'Settings'
75 | uicon: '\uF8B0'
76 | click_event_fn: settings_click
77 | ),
78 | ui.MenuItem.new(
79 | text: 'Manage V'
80 | uicon: '\uEC7A'
81 | // click_event_fn: show_install_modal
82 | ),
83 | ]
84 | )
85 |
86 | edit_menu := ui.MenuItem.new(
87 | text: 'Edit'
88 | // icon: edit_icon
89 | uicon: '\uE104'
90 | )
91 |
92 | help_menu := ui.MenuItem.new(
93 | text: 'Help'
94 | uicon: '\uEA0D'
95 | // icon: help_icon
96 | children: [
97 | ui.MenuItem.new(
98 | text: 'About Vide'
99 | click_event_fn: about_click
100 | ),
101 | ui.MenuItem.new(
102 | text: 'Github'
103 | click_event_fn: gh_click
104 | ),
105 | ui.MenuItem.new(
106 | text: 'Discord'
107 | click_event_fn: dis_click
108 | ),
109 | ui.MenuItem.new(
110 | text: 'About iUI'
111 | ),
112 | ]
113 | )
114 |
115 | mut theme_menu := ui.MenuItem.new(
116 | text: 'Themes'
117 | // icon: theme_icon
118 | uicon: '\uE9D7'
119 | )
120 |
121 | themes := tmanager.get_themes()
122 | for theme2 in themes {
123 | item := ui.MenuItem.new(text: theme2.name, click_event_fn: on_theme_click)
124 | theme_menu.add_child(item)
125 | }
126 |
127 | item := ui.MenuItem.new(text: 'Vide Default Dark', click_event_fn: on_theme_click)
128 | theme_menu.add_child(item)
129 |
130 | item_ := ui.MenuItem.new(text: 'Vide Light Theme', click_event_fn: on_theme_click)
131 | theme_menu.add_child(item_)
132 |
133 | save_menu := ui.MenuItem.new(
134 | // text: 'Save'
135 | icon: save_icon
136 | uicon: '\uE105'
137 | click_event_fn: save_click
138 | )
139 |
140 | run_menu := ui.MenuItem.new(
141 | // text: 'Run'
142 | icon: run_icon
143 | uicon: '\uEA16'
144 | click_event_fn: run_click
145 | )
146 |
147 | fmt_menu := ui.MenuItem.new(
148 | // text: 'v fmt'
149 | icon: fmt_icon
150 | uicon: '\uEA5D'
151 | click_event_fn: fmt_click
152 | )
153 |
154 | window.bar.add_child(file_menu)
155 | window.bar.add_child(edit_menu)
156 | window.bar.add_child(help_menu)
157 | window.bar.add_child(theme_menu)
158 |
159 | window.bar.add_child(save_menu)
160 | window.bar.add_child(run_menu)
161 | window.bar.add_child(fmt_menu)
162 | }
163 |
164 | fn (mut app App) set_theme_from_save() {
165 | /*
166 | name := app.get_saved_value('theme')
167 | if name.len > 1 {
168 | theme := ui.theme_by_name(name)
169 | app.win.set_theme(theme)
170 | theme.setup_fn(mut app.win)
171 | }*/
172 | }
173 |
174 | fn settings_click(mut win ui.Window, com ui.MenuItem) {
175 | mut app := win.get[&App]('app')
176 | // file := os.join_path(app.confg.cfg_dir, 'config.yml')
177 | // new_tab(win, file)
178 | app.show_settings()
179 | }
180 |
181 | const tmanager = ui.ThemeManager.new()
182 |
183 | fn on_theme_click(mut win ui.Window, com ui.MenuItem) {
184 | if com.text == 'Vide Default Dark' {
185 | mut vt := vide_dark_theme()
186 | win.set_theme(vt)
187 | return
188 | }
189 | if com.text == 'Vide Light Theme' {
190 | mut vt := vide_light_theme()
191 | win.set_theme(vt)
192 | return
193 | }
194 |
195 | theme := tmanager.get_theme(com.text)
196 | mut app := win.get[&App]('app')
197 | app.confg.theme = com.text
198 | app.confg.save()
199 | win.set_theme(theme)
200 | }
201 |
202 | fn gh_click(mut win ui.Window, com ui.MenuItem) {
203 | ui.open_url('https://github.com/pisaiah/vide')
204 | }
205 |
206 | fn dis_click(mut win ui.Window, com ui.MenuItem) {
207 | ui.open_url('https://discord.gg/NruVtYBf5g')
208 | }
209 |
210 | fn about_click(mut win ui.Window, com ui.MenuItem) {
211 | mut modal := ui.Modal.new(
212 | title: 'About VIDE'
213 | width: 380
214 | height: 250
215 | )
216 |
217 | mut p := ui.Panel.new(
218 | layout: ui.BoxLayout.new(ori: 1)
219 | )
220 | p.set_pos(8, 16)
221 |
222 | mut label := ui.Label.new(
223 | text: 'Simple IDE for the V Language made in V.\n\nVersion: ${version}\niUI: ${ui.version}'
224 | )
225 |
226 | label.pack()
227 |
228 | mut copy := ui.Label.new(
229 | text: 'Copyright © 2021-2025 by Isaiah.'
230 | em_size: 0.8
231 | pack: true
232 | )
233 | copy.set_pos(16, 175)
234 |
235 | p.add_child(label)
236 | modal.add_child(copy)
237 | modal.add_child(p)
238 | win.add_child(modal)
239 | }
240 |
241 | fn save_click(mut win ui.Window, item ui.MenuItem) {
242 | do_save(mut win)
243 | }
244 |
245 | fn do_save(mut win ui.Window) {
246 | mut com := win.get[&ui.Tabbox]('main-tabs')
247 |
248 | mut tab := com.kids[com.active_tab]
249 | for mut sv in tab {
250 | if mut sv is ui.ScrollView {
251 | for mut child in sv.children {
252 | if mut child is ui.Textbox {
253 | write_file(com.active_tab, child.lines.join('\n')) or {
254 | // set_console_text(mut win, 'Unable to save file!')
255 | }
256 | }
257 | }
258 | }
259 | }
260 | }
261 |
262 | fn refresh_current_tab(mut win ui.Window, file string) {
263 | mut com := win.get[&ui.Tabbox]('main-tabs')
264 |
265 | mut tab := com.kids[com.active_tab]
266 | for mut sv in tab {
267 | if mut sv is ui.ScrollView {
268 | for mut child in sv.children {
269 | if mut child is ui.Textbox {
270 | old_x := child.caret_x
271 | old_y := child.caret_y
272 |
273 | lines := os.read_lines(file) or { child.lines }
274 | child.lines = lines
275 |
276 | child.caret_x = old_x
277 | child.caret_y = old_y
278 | }
279 | }
280 | }
281 | }
282 | }
283 |
284 | fn run_click(mut win ui.Window, item ui.MenuItem) {
285 | com := win.get[&ui.Tabbox]('main-tabs')
286 |
287 | txt := com.active_tab
288 | mut dir := os.dir(txt)
289 |
290 | if dir.ends_with('src') {
291 | dir = os.dir(dir)
292 | }
293 |
294 | args := ['v', '-skip-unused', 'run', dir]
295 |
296 | mut tbox := win.get[&ui.Textbox]('vermbox')
297 |
298 | spawn verminal_cmd_exec(mut win, mut tbox, args)
299 |
300 | jump_sv(mut win, tbox.height, tbox.lines.len)
301 |
302 | win.extra_map['update_scroll'] = 'true'
303 | win.extra_map['lastcmd'] = args.join(' ')
304 | }
305 |
306 | fn fmt_click(mut win ui.Window, item ui.MenuItem) {
307 | com := win.get[&ui.Tabbox]('main-tabs')
308 |
309 | txt := com.active_tab
310 | /*
311 | mut dir := os.dir(txt)
312 |
313 | if dir.ends_with('src') {
314 | dir = os.dir(dir)
315 | }*/
316 |
317 | do_save(mut win)
318 |
319 | args := ['v', 'fmt', '-w', txt]
320 |
321 | mut tbox := win.get[&ui.Textbox]('vermbox')
322 |
323 | verminal_cmd_exec(mut win, mut tbox, args)
324 |
325 | jump_sv(mut win, tbox.height, tbox.lines.len)
326 |
327 | refresh_current_tab(mut win, txt)
328 |
329 | win.extra_map['update_scroll'] = 'true'
330 | win.extra_map['lastcmd'] = args.join(' ')
331 | }
332 |
--------------------------------------------------------------------------------
/src/config.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | import iui as ui
4 | import os
5 |
6 | struct Config {
7 | mut:
8 | cfg_dir string = os.join_path(os.home_dir(), '.vide')
9 | workspace_dir string
10 | vexe string
11 | font_path string
12 | font_size int = 18
13 | theme string = 'Vide Default Dark'
14 | open_paths []string
15 | }
16 |
17 | fn make_config() &Config {
18 | mut cfg := &Config{}
19 |
20 | file := os.join_path(cfg.cfg_dir, 'config.yml')
21 |
22 | if !os.exists(file) {
23 | cfg.load_defaults()
24 | } else {
25 | cfg.load_from_file()
26 | }
27 |
28 | cfg.save()
29 |
30 | return cfg
31 | }
32 |
33 | fn (mut this Config) load_from_file() {
34 | file := os.join_path(this.cfg_dir, 'config.yml')
35 |
36 | lines := os.read_lines(file) or { [''] }
37 | for line in lines {
38 | spl := line.split(': ')
39 | if spl[0].starts_with('# ') {
40 | continue
41 | }
42 | match spl[0] {
43 | 'cfg_dir' { this.cfg_dir = spl[1] }
44 | 'workspace_dir' { this.workspace_dir = spl[1] }
45 | 'vexe' { this.vexe = spl[1] }
46 | 'font_path' { this.font_path = spl[1] }
47 | 'font_size' { this.font_size = spl[1].int() }
48 | 'theme' { this.theme = spl[1] }
49 | 'open_paths' { this.open_paths = spl[1].split(',') }
50 | else {}
51 | }
52 | }
53 | }
54 |
55 | fn (mut this Config) save() {
56 | file := os.join_path(this.cfg_dir, 'config.yml')
57 |
58 | data := [
59 | '# Vide Configuration',
60 | 'cfg_dir: ${this.cfg_dir}',
61 | 'workspace_dir: ${this.workspace_dir}',
62 | 'vexe: ${this.vexe}',
63 | 'font_path: ${this.font_path}',
64 | 'font_size: ${this.font_size}',
65 | 'theme: ${this.theme}',
66 | 'open_paths: ${this.open_paths.join(',')}',
67 | ]
68 |
69 | mut lic := ['\n\n# LICENSE.txt:', '#', '# Copyright (c) 2021-2023 Isaiah\n#',
70 | '# Permission is hereby granted, free of charge, to any person obtaining a copy of this',
71 | '# software and associated documentation files (the “Software”), to deal in the Software',
72 | '# without restriction, including without limitation the rights to use, copy, modify, merge',
73 | '# publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons',
74 | '# to whom the Software is furnished to do so, subject to the following conditions:\n#',
75 | '# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n#',
76 | '# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.']
77 |
78 | write_file(file, data.join('\n') + lic.join('\n')) or {}
79 | }
80 |
81 | fn (mut this Config) load_defaults() {
82 | dot_vide := os.join_path(os.home_dir(), '.vide')
83 |
84 | os.mkdir(dot_vide) or {}
85 |
86 | this.cfg_dir = dot_vide
87 | this.workspace_dir = os.join_path(dot_vide, 'workspace')
88 |
89 | mut font_path := os.join_path(dot_vide, 'FiraCode-Regular.ttf')
90 | if !os.exists(font_path) {
91 | mut font_file := $embed_file('assets/FiraCode-Regular.ttf')
92 | os.write_file_array(font_path, font_file.to_bytes()) or { font_path = ui.default_font() }
93 | }
94 | this.font_path = font_path
95 | this.vexe = 'v'
96 | }
97 |
98 | // Settings Page
99 |
100 | fn (mut app App) show_settings() {
101 | mut page := ui.Page.new(title: 'Vide Settings')
102 |
103 | mut p := ui.Panel.new(
104 | layout: ui.BoxLayout.new(
105 | ori: 1
106 | )
107 | )
108 | p.set_pos(1, 1)
109 |
110 | mut ttf := app.s_cfg_dir()
111 | mut swp := app.s_workspace_dir()
112 | mut sexe := app.s_vexe()
113 | mut sfp := app.s_font_path()
114 | mut sfs := app.s_font_size()
115 |
116 | p.add_child(ttf)
117 | p.add_child(swp)
118 | p.add_child(sexe)
119 | p.add_child(sfp)
120 | p.add_child(sfs)
121 |
122 | p.subscribe_event('draw', fn (mut e ui.DrawEvent) {
123 | pw := e.target.parent.width
124 | size := if pw < 990 { pw } else { int(pw * f32(0.65)) }
125 | e.target.width = size - 10
126 | })
127 |
128 | mut sv := ui.ScrollView.new(
129 | view: p
130 | )
131 |
132 | page.add_child(sv)
133 | app.win.add_child(page)
134 | }
135 |
136 | fn set_field_width(mut e ui.DrawEvent) {
137 | tw := e.ctx.text_width(e.target.text) + 20
138 | e.target.width = if tw > 100 { tw } else { 100 }
139 | e.target.height = 30
140 |
141 | mut wid := 0
142 | for kid in e.target.parent.children {
143 | wid += kid.width
144 | }
145 | e.target.parent.width = wid + 20
146 | }
147 |
148 | fn (mut app App) s_cfg_dir() &ui.SettingsCard {
149 | mut p := ui.Panel.new(layout: ui.BoxLayout.new(ori: 0, vgap: 0))
150 |
151 | mut tf := ui.TextField.new(text: app.confg.cfg_dir)
152 | mut sb := ui.Button.new(text: 'Save')
153 | sb.subscribe_event('mouse_up', fn [mut app, tf] (mut e ui.MouseEvent) {
154 | app.confg.cfg_dir = tf.text
155 | app.confg.save()
156 | })
157 |
158 | tf.subscribe_event('draw', set_field_width)
159 | sb.set_bounds(0, 0, 100, 30)
160 |
161 | p.add_child(tf)
162 | p.add_child(sb)
163 |
164 | mut card := ui.SettingsCard.new(
165 | text: 'Config Directory'
166 | description: 'Where Vide stores files'
167 | stretch: true
168 | )
169 | card.add_child(p)
170 |
171 | return card
172 | }
173 |
174 | fn (mut app App) s_workspace_dir() &ui.SettingsCard {
175 | mut p := ui.Panel.new(layout: ui.BoxLayout.new(ori: 0, vgap: 0))
176 |
177 | mut tf := ui.TextField.new(text: app.confg.workspace_dir)
178 | mut sb := ui.Button.new(text: 'Save')
179 | sb.subscribe_event('mouse_up', fn [mut app, tf] (mut e ui.MouseEvent) {
180 | app.confg.workspace_dir = tf.text
181 | app.confg.save()
182 | })
183 | tf.subscribe_event('draw', set_field_width)
184 | sb.set_bounds(0, 0, 100, 30)
185 |
186 | p.add_child(tf)
187 | p.add_child(sb)
188 |
189 | mut card := ui.SettingsCard.new(
190 | text: 'Workspace Directory'
191 | description: 'The directory that is opened in the file tree'
192 | stretch: true
193 | )
194 | card.add_child(p)
195 | return card
196 | }
197 |
198 | fn (mut app App) s_vexe() &ui.SettingsCard {
199 | mut p := ui.Panel.new(layout: ui.BoxLayout.new(ori: 0, vgap: 0))
200 |
201 | mut tf := ui.TextField.new(text: app.confg.vexe)
202 | mut sb := ui.Button.new(text: 'Save')
203 | sb.subscribe_event('mouse_up', fn [mut app, tf] (mut e ui.MouseEvent) {
204 | app.confg.vexe = tf.text
205 | app.confg.save()
206 | })
207 |
208 | mut card := ui.SettingsCard.new(
209 | text: 'V Executable Path'
210 | description: 'Path to the V executable'
211 | stretch: true
212 | )
213 | card.add_child(p)
214 |
215 | mut teb := ui.Button.new(text: 'Test')
216 | teb.subscribe_event('mouse_up', fn [mut app, tf, mut card] (mut e ui.MouseEvent) {
217 | app.confg.vexe = tf.text
218 | app.confg.save()
219 | res := os.execute('${app.confg.vexe} version')
220 | app.win.tooltip = res.output
221 | card.desc = card.desc.split('(')[0] + ' (test: ${res.output})'
222 | })
223 |
224 | tf.subscribe_event('draw', set_field_width)
225 | sb.set_bounds(0, 0, 95, 30)
226 | teb.set_bounds(0, 0, 65, 30)
227 |
228 | p.add_child(tf)
229 | p.add_child(sb)
230 | p.add_child(teb)
231 |
232 | return card
233 | }
234 |
235 | fn (mut app App) s_font_path() &ui.SettingsCard {
236 | mut p := ui.Panel.new(layout: ui.BoxLayout.new(ori: 0, vgap: 0))
237 |
238 | mut tf := ui.TextField.new(text: app.confg.font_path)
239 | mut sb := ui.Button.new(text: 'Save')
240 | sb.subscribe_event('mouse_up', fn [mut app, tf] (mut e ui.MouseEvent) {
241 | app.confg.font_path = tf.text
242 | app.confg.save()
243 | })
244 | tf.subscribe_event('draw', set_field_width)
245 | sb.set_bounds(0, 0, 100, 30)
246 |
247 | p.add_child(tf)
248 | p.add_child(sb)
249 |
250 | mut card := ui.SettingsCard.new(
251 | text: 'Font Path'
252 | description: 'Path to the font file used'
253 | stretch: true
254 | )
255 | card.add_child(p)
256 | return card
257 | }
258 |
259 | fn (mut app App) s_font_size() &ui.SettingsCard {
260 | mut p := ui.Panel.new(layout: ui.BoxLayout.new(ori: 0, vgap: 0))
261 |
262 | mut tf := ui.numeric_field(app.confg.font_size)
263 | tf.set_bounds(0, 0, 100, 30)
264 |
265 | mut ib := ui.Button.new(text: '+')
266 | ib.subscribe_event('mouse_up', fn [mut app, mut tf] (mut e ui.MouseEvent) {
267 | val := cfs(tf.text.int() + 1)
268 | dump(val)
269 | tf.text = '${val}'
270 | app.set_fs(val)
271 | })
272 |
273 | mut db := ui.Button.new(text: '-')
274 | db.subscribe_event('mouse_up', fn [mut app, mut tf] (mut e ui.MouseEvent) {
275 | val := cfs(tf.text.int() - 1)
276 | dump(val)
277 | tf.text = '${val}'
278 | app.set_fs(val)
279 | })
280 |
281 | ib.set_bounds(0, 0, 50, 30)
282 | db.set_bounds(0, 0, 50, 30)
283 |
284 | p.add_child(tf)
285 | p.add_child(ib)
286 | p.add_child(db)
287 |
288 | mut card := ui.SettingsCard.new(
289 | text: 'Font Size'
290 | description: 'The font size used'
291 | stretch: true
292 | )
293 | card.add_child(p)
294 | return card
295 | }
296 |
297 | fn (mut app App) set_fs(fs int) {
298 | app.win.font_size = fs
299 | app.confg.font_size = fs
300 | app.confg.save()
301 | }
302 |
303 | fn cfs(fs int) int {
304 | if fs < 8 {
305 | return 8
306 | }
307 | if fs > 32 {
308 | return 32
309 | }
310 | return fs
311 | }
312 |
--------------------------------------------------------------------------------
/src/main.v:
--------------------------------------------------------------------------------
1 | // VIDE - A simple IDE for V
2 | // (c) 2021-2024 Isaiah.
3 | module main
4 |
5 | import iui as ui
6 | import os
7 |
8 | const version = '0.1-pre'
9 |
10 | @[heap]
11 | pub struct App {
12 | mut:
13 | win &ui.Window
14 | tb &ui.Tabbox
15 | collapse_tree bool
16 | collapse_search bool = true
17 | shown_activity int
18 | activty_speed int = 30
19 | confg &Config
20 | popup &MyPopup
21 | }
22 |
23 | pub fn C.emscripten_run_script(&char)
24 |
25 | fn main() {
26 | vide_home := os.join_path(os.home_dir(), '.vide')
27 | mut folder := os.join_path(vide_home, 'workspace')
28 |
29 | os.mkdir_all(folder) or {}
30 |
31 | confg := make_config()
32 |
33 | mut win := ui.Window.new(
34 | width: 900
35 | height: 550
36 | title: 'Vide'
37 | font_size: confg.font_size
38 | font_path: confg.font_path
39 | ui_mode: true
40 | )
41 |
42 | $if windows {
43 | ui.set_power_save(true)
44 | }
45 |
46 | if os.exists(confg.workspace_dir) {
47 | folder = confg.workspace_dir
48 | }
49 |
50 | win.set_theme(vide_dark_theme())
51 |
52 | mut app := &App{
53 | win: win
54 | tb: ui.Tabbox.new()
55 | confg: confg
56 | popup: code_popup()
57 | }
58 |
59 | win.id_map['app'] = app
60 |
61 | app.make_menubar()
62 |
63 | mut hbox := ui.Panel.new(
64 | layout: ui.BoxLayout.new(
65 | hgap: 0
66 | vgap: 0
67 | )
68 | )
69 |
70 | tree := app.setup_tree(mut win, folder)
71 |
72 | activity_bar := app.make_activity_bar()
73 | hbox.add_child(activity_bar)
74 |
75 | hbox.add_child(tree)
76 |
77 | // Search box
78 | search := app.setup_search(mut win, folder)
79 | hbox.add_child(search)
80 |
81 | // end;
82 | app.tb.set_id(mut win, 'main-tabs')
83 | app.tb.set_bounds(0, 0, 400, 200)
84 | app.welcome_tab('')
85 |
86 | for name in app.confg.open_paths {
87 | if os.exists(name) {
88 | new_tab(win, name)
89 | }
90 | }
91 |
92 | mut console_box := create_box(mut win)
93 | console_box.z_index = 2
94 | console_box.set_id(mut win, 'consolebox')
95 |
96 | mut sv := ui.ScrollView.new(
97 | view: console_box
98 | increment: 5
99 | bounds: ui.Bounds{
100 | width: 300
101 | height: 100
102 | }
103 | padding: 0
104 | )
105 | sv.noborder = true
106 | sv.set_id(mut win, 'vermsv')
107 |
108 | mut spv := ui.SplitView.new(
109 | first: app.tb
110 | second: sv
111 | min_percent: 20
112 | h1: 70
113 | h2: 20
114 | bounds: ui.Bounds{
115 | y: 3
116 | x: 2
117 | width: 400
118 | height: 400
119 | }
120 | )
121 |
122 | app.tb.subscribe_event('draw', tabbox_fill_width)
123 | sv.subscribe_event('draw', terminal_scrollview_fill)
124 | spv.subscribe_event('draw', splitview_fill)
125 |
126 | hbox.add_child(spv)
127 |
128 | win.add_child(hbox)
129 | win.gg.run()
130 | }
131 |
132 | fn (mut app App) make_activity_bar() &ui.NavPane {
133 | mut np := ui.NavPane.new(
134 | pack: true
135 | collapsed: true
136 | )
137 |
138 | mut item1 := ui.NavPaneItem.new(
139 | icon: '\uED43'
140 | text: 'Workspace'
141 | )
142 |
143 | mut item2 := ui.NavPaneItem.new(
144 | icon: '\uF002'
145 | text: 'Search'
146 | )
147 |
148 | mut item3 := ui.NavPaneItem.new(
149 | icon: '\uEAE7'
150 | text: 'Git'
151 | )
152 |
153 | mut item4 := ui.NavPaneItem.new(
154 | icon: '\uE713'
155 | text: 'Settings'
156 | )
157 |
158 | item1.subscribe_event('mouse_up', app.calb_click)
159 | item2.subscribe_event('mouse_up', app.serb_click)
160 | item3.subscribe_event('mouse_up', app.calb_click)
161 | item4.subscribe_event('mouse_up', app.settings_btn_click)
162 |
163 | np.add_child(item1)
164 | np.add_child(item2)
165 | np.add_child(item3)
166 | np.add_child(item4)
167 |
168 | return np
169 | }
170 |
171 | fn (mut app App) settings_btn_click(e &ui.MouseEvent) {
172 | mut tar := e.target
173 | if mut tar is ui.NavPaneItem {
174 | tar.unselect()
175 | }
176 |
177 | app.show_settings()
178 | }
179 |
180 | @[deprecated: 'Replaced by ui.NavPane']
181 | fn (mut app App) make_activity_bar_old() &ui.Panel {
182 | mut activity_bar := ui.Panel.new(
183 | layout: ui.BoxLayout.new(
184 | ori: 1
185 | hgap: 4
186 | )
187 | )
188 | activity_bar.set_bounds(0, 0, 40, 200)
189 |
190 | activity_bar.subscribe_event('draw', fn (mut e ui.DrawEvent) {
191 | hei := e.ctx.gg.window_size().height
192 | e.ctx.theme.menu_bar_fill_fn(e.target.x, e.target.y, e.target.width, hei, e.ctx)
193 | })
194 |
195 | // Explore Button
196 | img_wide_file := $embed_file('assets/explore.png')
197 | mut calb := app.icon_btn(img_wide_file.to_bytes(), app.win)
198 |
199 | activity_bar.add_child(calb)
200 |
201 | calb.subscribe_event('mouse_up', app.calb_click)
202 |
203 | // Search Button
204 | img_search_file := $embed_file('assets/search.png')
205 | mut serb := app.icon_btn(img_search_file.to_bytes(), app.win)
206 |
207 | activity_bar.add_child(serb)
208 |
209 | serb.subscribe_event('mouse_up', app.serb_click)
210 |
211 | // Git Commit satus Button
212 | img_gitm_file := $embed_file('assets/merge.png')
213 | mut gitb := app.icon_btn(img_gitm_file.to_bytes(), app.win)
214 |
215 | activity_bar.add_child(gitb)
216 |
217 | gitb.subscribe_event('mouse_up', app.calb_click)
218 |
219 | return activity_bar
220 | }
221 |
222 | fn (mut app App) icon_btn(data []u8, win &ui.Window) &ui.Button {
223 | mut ggc := win.gg
224 | gg_im := ggc.create_image_from_byte_array(data) or { return ui.Button.new(text: 'NO IMG') }
225 | cim := ggc.cache_image(gg_im)
226 | mut btn := ui.Button.new(icon: cim)
227 |
228 | btn.set_bounds(0, 5, 33, 46)
229 | btn.z_index = 5
230 |
231 | // btn.border_radius = -1
232 | btn.set_area_filled(false)
233 | btn.icon_height = 32
234 | return btn
235 | }
236 |
237 | fn (mut app App) setup_tree(mut window ui.Window, folder string) &ui.ScrollView {
238 | mut tree2 := ui.tree('My Workspace')
239 | tree2.set_bounds(0, 0, 250, 200)
240 | tree2.needs_pack = true
241 |
242 | files := os.ls(folder) or { [] }
243 | tree2.click_event_fn = tree2_click
244 |
245 | for fi in files {
246 | mut node := make_tree2(os.join_path(folder, fi))
247 | tree2.add_child(node)
248 | }
249 |
250 | mut sv := ui.ScrollView.new(
251 | view: tree2
252 | bounds: ui.Bounds{1, 3, 250, 200}
253 | padding: 0
254 | )
255 | sv.subscribe_event('draw', app.proj_tree_draw)
256 | tree2.subscribe_event('draw', fn (mut e ui.DrawEvent) {
257 | e.target.width = e.target.parent.width
258 | })
259 |
260 | tree2.set_id(mut window, 'proj-tree')
261 | return sv // tree2
262 | }
263 |
264 | fn (mut app App) setup_search(mut window ui.Window, folder string) &ui.ScrollView {
265 | mut search_box := ui.Panel.new(
266 | layout: ui.BoxLayout.new(
267 | ori: 1
268 | )
269 | )
270 |
271 | search_box.subscribe_event('draw', fn (mut e ui.DrawEvent) {
272 | // e.ctx.gg.draw_rect_empty(e.target.x, e.target.y, e.target.width, e.target.height,
273 | // gx.blue)
274 | e.target.width = 200
275 | e.target.height = 100 + e.target.children[1].height
276 | })
277 |
278 | mut search_field := ui.text_field(
279 | text: 'Search ...'
280 | bounds: ui.Bounds{1, 1, 190, 25}
281 | )
282 | search_box.add_child(search_field)
283 |
284 | mut search_out := ui.Panel.new(layout: ui.BoxLayout.new(ori: 1))
285 | search_box.add_child(search_out)
286 | search_out.set_bounds(0, 0, 200, 0)
287 |
288 | search_field.subscribe_event('before_text_change', fn [mut app, mut search_field, mut search_out] (mut e ui.TextChangeEvent) {
289 | if search_field.last_letter != 'enter' {
290 | return
291 | }
292 | search_out.children.clear()
293 |
294 | txt := e.target.text
295 | dir := app.confg.workspace_dir
296 | read_files(dir, txt, mut search_out, e.ctx)
297 |
298 | dump(e.target.text)
299 | })
300 |
301 | mut stb := ui.Titlebox.new(text: 'Search', children: [search_box])
302 | stb.set_bounds(4, 4, 200, 250)
303 |
304 | // hbox.add_child(stb)
305 | mut sv := ui.ScrollView.new(
306 | view: stb
307 | bounds: ui.Bounds{1, 4, 240, 200}
308 | padding: 0
309 | )
310 | sv.subscribe_event('draw', app.search_pane_draw)
311 | stb.subscribe_event('draw', fn (mut e ui.DrawEvent) {
312 | e.target.width = e.target.parent.width - 7
313 | })
314 |
315 | stb.set_id(mut window, 'stb')
316 | return sv // tree2
317 | }
318 |
319 | fn read_files(dir string, txt string, mut search_out ui.Panel, ctx &ui.GraphicsContext) {
320 | ls := os.ls(dir) or { [] }
321 |
322 | for file in ls {
323 | jp := os.join_path(dir, file)
324 | if os.is_dir(jp) {
325 | read_files(jp, txt, mut search_out, ctx)
326 | continue
327 | }
328 |
329 | if !(file.ends_with('.v') || file.ends_with('.md') || file.ends_with('.c')) {
330 | continue
331 | }
332 |
333 | lines := os.read_lines(jp) or { [] }
334 | for i, line in lines {
335 | if line.contains(txt) {
336 | mut btn := ui.Label.new(text: '${file}: ${i + 1}:\n${line.trim_space()}')
337 | btn.pack_do(ctx)
338 | search_out.add_child(btn)
339 | }
340 | }
341 | }
342 | if search_out.children.len > 0 {
343 | dump(search_out.children.len)
344 | search_out.set_bounds(0, 0, 200, search_out.children.len * (search_out.children[0].height +
345 | 5))
346 | }
347 | }
348 |
349 | fn get_v_exe() string {
350 | mut saved := '' // config.get_value('v_exe').replace('\{user_home}', '~')
351 | dump(saved)
352 | saved = saved.replace('~', os.home_dir().replace('\\', '/'))
353 |
354 | if saved.len <= 0 {
355 | mut vexe := 'v'
356 | $if windows {
357 | vexe = 'v.exe'
358 | }
359 | if 'VEXE' in os.environ() {
360 | vexe = os.environ()['VEXE'].replace('\\', '/')
361 | }
362 | vexe = vexe.replace(os.home_dir().replace('\\', '/'), '~')
363 |
364 | // config.set('v_exe', vexe)
365 | // config.save()
366 | return vexe
367 | } else {
368 | return saved
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/src/vcreate.v:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
2 | // Use of this source code is governed by an MIT license that can be found in the LICENSE file.
3 | module main
4 |
5 | import os
6 |
7 | // Note: this program follows a similar convention to Rust: `init` makes the
8 | // structure of the program in the _current_ directory, while `new`
9 | // makes the program structure in a _sub_ directory. Besides that, the
10 | // functionality is essentially the same.
11 |
12 | // Note: here are the currently supported invokations so far:
13 | // 1) `v init` -> create a new project in the current folder
14 | // 2) `v new abc` -> create a new project in the new folder `abc`, by default a "hello world" project.
15 | // 3) `v new abcd web` -> create a new project in the new folder `abcd`, using the vweb template.
16 | // 4) `v new abcde hello_world` -> create a new project in the new folder `abcde`, using the hello_world template.
17 |
18 | // Note: run `v cmd/tools/vcreate_test.v` after changes to this program, to avoid regressions.
19 | struct Create {
20 | mut:
21 | name string
22 | description string
23 | version string
24 | license string
25 | files []ProjectFiles
26 | app &App
27 | }
28 |
29 | struct ProjectFiles {
30 | path string
31 | content string
32 | }
33 |
34 | @[params]
35 | struct CreateConfig {
36 | name string
37 | description string
38 | version string
39 | license string
40 | template string
41 | app &App
42 | }
43 |
44 | // fn new_project(args []string) {
45 | fn new_project(cfg CreateConfig) {
46 | mut c := Create{
47 | app: cfg.app
48 | }
49 |
50 | // project name
51 | c.name = check_name(cfg.name)
52 |
53 | if c.name == '' {
54 | cerror('project name cannot be empty')
55 | exit(1)
56 | }
57 |
58 | if c.name.contains('-') {
59 | cerror('"${c.name}" should not contain hyphens')
60 | exit(1)
61 | }
62 |
63 | if os.is_dir(c.name) {
64 | cerror('${c.name} folder already exists')
65 | exit(3)
66 | }
67 |
68 | c.description = cfg.description
69 |
70 | default_version := '0.0.0'
71 |
72 | c.version = cfg.version
73 | if c.version == '' {
74 | c.version = default_version
75 | }
76 |
77 | default_license := os.getenv_opt('VLICENSE') or { 'MIT' }
78 |
79 | c.license = cfg.license
80 | if c.license == '' {
81 | c.license = default_license
82 | }
83 |
84 | println('Initialising ...')
85 |
86 | // `v new abcde hello_world`
87 | templ := cfg.template
88 |
89 | // if args.len == 2 {
90 | if templ.len > 0 {
91 | // match os.args.last() {
92 | match templ {
93 | 'web' {
94 | c.set_web_project_files()
95 | }
96 | 'hello_world' {
97 | c.set_hello_world_project_files()
98 | }
99 | 'basic_window' {
100 | c.set_basic_window_files()
101 | }
102 | 'border_layout' {
103 | c.set_border_layout_files()
104 | }
105 | else {
106 | eprintln('${templ} model not exist')
107 | exit(1)
108 | }
109 | }
110 | } else {
111 | // `v new abc`
112 | c.set_hello_world_project_files()
113 | }
114 |
115 | // gen project based in the `Create.files` info
116 | c.create_files_and_directories()
117 |
118 | c.write_vmod(true)
119 | c.write_gitattributes(true)
120 | c.write_editorconfig(true)
121 |
122 | mut gdir := os.join_path(c.app.confg.workspace_dir, c.name)
123 |
124 | c.create_git_repo(gdir)
125 | }
126 |
127 | @[deprecated: 'Not used in Vide']
128 | fn init_project() {
129 | }
130 |
131 | fn cerror(e string) {
132 | eprintln('\nerror: ${e}')
133 | }
134 |
135 | fn check_name(name string) string {
136 | if name.trim_space().len == 0 {
137 | cerror('project name cannot be empty')
138 | exit(1)
139 | }
140 | if name.is_title() {
141 | mut cname := name.to_lower()
142 | if cname.contains(' ') {
143 | cname = cname.replace(' ', '_')
144 | }
145 | eprintln('warning: the project name cannot be capitalized, the name will be changed to `${cname}`')
146 | return cname
147 | }
148 | if name.contains(' ') {
149 | cname := name.replace(' ', '_')
150 | eprintln('warning: the project name cannot contain spaces, the name will be changed to `${cname}`')
151 | return cname
152 | }
153 | return name
154 | }
155 |
156 | fn vmod_content(c Create) string {
157 | return "Module {
158 | name: '${c.name}'
159 | description: '${c.description}'
160 | version: '${c.version}'
161 | license: '${c.license}'
162 | dependencies: []
163 | }
164 | "
165 | }
166 |
167 | fn hello_world_content() string {
168 | return "module main
169 |
170 | fn main() {
171 | println('Hello World!')
172 | }
173 | "
174 | }
175 |
176 | fn basic_window_content() string {
177 | return "module main
178 |
179 | import iui as ui // // v install https://github.com/pisaiah/ui
180 |
181 | fn main() {
182 | // Create Window
183 | mut window := ui.Window.new(
184 | title: 'My Window'
185 | width: 520
186 | height: 400
187 | theme: ui.theme_default()
188 | )
189 |
190 | // A content pane
191 | mut p := ui.Panel.new()
192 |
193 | window.add_child(p)
194 |
195 | // Run window
196 | window.run()
197 | }
198 | "
199 | }
200 |
201 | fn border_layout_content() string {
202 | return "module main
203 |
204 | import iui as ui // v install https://github.com/pisaiah/ui
205 |
206 | struct App {
207 | mut:
208 | p &ui.Panel
209 | }
210 |
211 | fn main() {
212 | mut win := ui.Window.new(
213 | title: 'BorderLayoutDemo'
214 | width: 450
215 | height: 295
216 | )
217 |
218 | mut pan := ui.Panel.new(layout: ui.BorderLayout.new())
219 |
220 | mut app := &App{
221 | p: pan
222 | }
223 |
224 | app.make_button('1 (NORTH)', ui.borderlayout_north)
225 | app.make_button('2 (WEST)', ui.borderlayout_west)
226 | app.make_button('3 (EAST)', ui.borderlayout_east)
227 | app.make_button('4 (SOUTH)', ui.borderlayout_south)
228 | app.make_button('5 (CENTER)', ui.borderlayout_center)
229 |
230 | win.add_child(pan)
231 | win.gg.run()
232 | }
233 |
234 | fn (mut app App) make_button(id string, constrain int) &ui.Button {
235 | mut btn := ui.Button.new(
236 | text: 'Button ' + id
237 | )
238 | app.p.add_child_with_flag(btn, constrain)
239 | return btn
240 | }
241 | "
242 | }
243 |
244 | fn gen_gitignore(name string) string {
245 | return '# Binaries for programs and plugins
246 | main
247 | ${name}
248 | *.exe
249 | *.exe~
250 | *.so
251 | *.dylib
252 | *.dll
253 |
254 | # Ignore binary output folders
255 | bin/
256 |
257 | # Ignore common editor/system specific metadata
258 | .DS_Store
259 | .idea/
260 | .vscode/
261 | *.iml
262 |
263 | # ENV
264 | .env
265 |
266 | # vweb and database
267 | *.db
268 | *.js
269 | '
270 | }
271 |
272 | fn gitattributes_content() string {
273 | return '* text=auto eol=lf
274 | *.bat eol=crlf
275 |
276 | **/*.v linguist-language=V
277 | **/*.vv linguist-language=V
278 | **/*.vsh linguist-language=V
279 | **/v.mod linguist-language=V
280 | '
281 | }
282 |
283 | fn editorconfig_content() string {
284 | return '[*]
285 | charset = utf-8
286 | end_of_line = lf
287 | insert_final_newline = true
288 | trim_trailing_whitespace = true
289 |
290 | [*.v]
291 | indent_style = tab
292 | indent_size = 4
293 | '
294 | }
295 |
296 | fn (c &Create) write_vmod(new bool) {
297 | mut vmod_path := if new { '${c.name}/v.mod' } else { 'v.mod' }
298 | vmod_path = os.join_path(c.app.confg.workspace_dir, vmod_path)
299 | write_file(vmod_path, vmod_content(c)) or { panic(err) }
300 | }
301 |
302 | fn (c &Create) write_gitattributes(new bool) {
303 | mut gitattributes_path := if new { '${c.name}/.gitattributes' } else { '.gitattributes' }
304 | gitattributes_path = os.join_path(c.app.confg.workspace_dir, gitattributes_path)
305 | if !new && os.exists(gitattributes_path) {
306 | return
307 | }
308 | write_file(gitattributes_path, gitattributes_content()) or { panic(err) }
309 | }
310 |
311 | fn (c &Create) write_editorconfig(new bool) {
312 | mut editorconfig_path := if new { '${c.name}/.editorconfig' } else { '.editorconfig' }
313 | editorconfig_path = os.join_path(c.app.confg.workspace_dir, editorconfig_path)
314 | if !new && os.exists(editorconfig_path) {
315 | return
316 | }
317 | write_file(editorconfig_path, editorconfig_content()) or { panic(err) }
318 | }
319 |
320 | fn (c &Create) create_git_repo(dir string) {
321 | // Create Git Repo and .gitignore file
322 | if !os.is_dir('${dir}/.git') {
323 | res := os.execute('git init ${dir}')
324 | if res.exit_code != 0 {
325 | $if emscripten ? {
326 | println('Unable to run "git init"')
327 | } $else {
328 | cerror('Unable to create git repo')
329 | exit(4)
330 | }
331 | }
332 | }
333 | gitignore_path := '${dir}/.gitignore'
334 | if !os.exists(gitignore_path) {
335 | write_file(gitignore_path, gen_gitignore(c.name)) or {}
336 | }
337 | }
338 |
339 | fn (mut c Create) create_files_and_directories() {
340 | for file in c.files {
341 | // get dir and convert path separator
342 | mut dir := file.path.split('/')#[..-1].join(os.path_separator)
343 |
344 | dir = os.join_path(c.app.confg.workspace_dir, dir)
345 |
346 | // create all directories, if not exist
347 | os.mkdir_all(dir) or { panic(err) }
348 |
349 | fp := os.join_path(c.app.confg.workspace_dir, file.path)
350 |
351 | write_file(fp, file.content) or { panic(err) }
352 | }
353 | }
354 |
355 | // ####################################### PROJECTS CONTENT AND PATH #######################################
356 | fn (mut c Create) set_hello_world_project_files() {
357 | c.files << ProjectFiles{
358 | path: '${c.name}/src/main.v'
359 | content: hello_world_content()
360 | }
361 | }
362 |
363 | fn (mut c Create) set_basic_window_files() {
364 | c.files << ProjectFiles{
365 | path: '${c.name}/src/main.v'
366 | content: basic_window_content()
367 | }
368 | }
369 |
370 | fn (mut c Create) set_border_layout_files() {
371 | c.files << ProjectFiles{
372 | path: '${c.name}/src/main.v'
373 | content: border_layout_content()
374 | }
375 | }
376 |
377 | fn (mut c Create) set_web_project_files() {
378 | c.files << ProjectFiles{
379 | path: '${c.name}/src/databases/config_databases_sqlite.v'
380 | content: "module databases
381 |
382 | import db.sqlite // can change to 'db.mysql', 'db.pg'
383 |
384 | pub fn create_db_connection() !sqlite.DB {
385 | mut db := sqlite.connect('app.db')!
386 | return db
387 | }
388 | "
389 | }
390 | c.files << ProjectFiles{
391 | path: '${c.name}/src/templates/header_component.html'
392 | content: "
407 | "
408 | }
409 | c.files << ProjectFiles{
410 | path: '${c.name}/src/templates/products.css'
411 | content: 'h1.title {
412 | font-family: Arial, Helvetica, sans-serif;
413 | color: #3b7bbf;
414 | }
415 |
416 | div.products-table {
417 | border: 1px solid;
418 | max-width: 720px;
419 | padding: 10px;
420 | margin: 10px;
421 | }'
422 | }
423 | c.files << ProjectFiles{
424 | path: '${c.name}/src/templates/products.html'
425 | content: "
426 |
| ID | 500 |Name | 501 |Created date | 502 |
|---|---|---|
| \${product.id} | 509 |\${product.name} | 510 |\${product.created_at} | 511 |