├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── Makefile.am
├── PKGBUILD
├── README.md
├── autogen.sh
├── cano
├── cano.png
├── cleanup.sh
├── config.log
├── config.status
├── configure.ac
├── docs
├── cano.1
└── help
│ ├── cmds
│ ├── general
│ └── keys
├── flake.lock
├── flake.nix
└── src
├── .dirstamp
├── buffer.c
├── buffer.h
├── cgetopt.c
├── cgetopt.h
├── colors.h
├── commands.c
├── commands.h
├── defs.h
├── frontend.c
├── frontend.h
├── keys.c
├── keys.h
├── lex.c
├── lex.h
├── main.c
├── main.h
├── tools.c
├── tools.h
├── view.c
└── view.h
/.gitignore:
--------------------------------------------------------------------------------
1 | main
2 | out.txt
3 | build/
4 | .cache/
5 | compile_commands.json
6 | autom4te.cache/
7 | aclocal.m4
8 | compile
9 | config.guess
10 | config.sub
11 | configure
12 | depcomp
13 | install-sh
14 | Makefile.in
15 | missing
16 | configure~
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All changes to Cano from v0.1.0 onward will be documented here.
3 |
4 | ## [0.1.0] - 04/04/2024
5 | Initial release of Cano.
6 |
7 | ### Features
8 | * Open and close files.
9 | * Write current buffer to file.
10 | * File explorer.
11 | * Basic syntax highlighting.
12 | * Basic motions (i.e. h, j, k, and l)
13 | * Five modes, Normal, Insert, Command, Search, and Visual.
14 | * A few basic commands for simple customization.
15 | * Auto-load config and syntax file support.
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributing
3 | Cano is open to contributors! That does not mean that your pull request will be merged. \
4 | Please structure your pull request as something reasonably small, \
5 | so that it is possible to review in a reasonable amount of time. \
6 | For the style, try to keep it as close to the code written as possible. \
7 | A pull request will not be rejected for a style conflict.
8 |
9 | ## How to contribute to Cano
10 | 1. Fork the repository
11 | https://github.com/CobbCoding1/Cano/fork
12 |
13 | 2. Clone the repository to your local machine
14 | ```sh
15 | git clone https://github.com/YourUserName/Cano.git
16 | ```
17 |
18 | 3. Create a new branch
19 |
20 | Name the branch something that is relevant to the changes you are making.
21 | A good example would be to prefix the branch name with `feature/` or `bug/` depending on the changes you are making. Additionally if there is an issue associated with the changes, you can also use the issue number as a suffix in the branch name but this is not required. See examples below.
22 | ```sh
23 | git checkout -b feature/add-new-command
24 | # or
25 | git checkout -b bug/crash-when-in-search-mode
26 | # or
27 | git checkout -b bug/unable-to-save-file-16
28 | ```
29 |
30 | 4. Make your changes
31 | 5. Commit and push your changes
32 | 6. Create a pull request
33 |
34 | When creating your pull request, add a brief description of the changes you made. If there is an issue associated with the changes, reference the issue in the pull request description. See example below.
35 | ```md
36 | This pull request contains the following changes:
37 |
38 | - Added new command to NORMAL mode
39 | - Fixed bug where the program would crash when in SEARCH mode
40 | - Fixed bug where the program would crash when saving to a .js file
41 |
42 | - Closes #16
43 | ```
44 |
45 | 7. Wait for the pull request to be reviewed and merged
46 |
47 |
48 | Written by [SchoolyB](https://github.com/SchoolyB)
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [2024] [CobbCoding]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # --- The custom cano Makefile ---
2 | # Cano is switching to autotools, this Makefile will no longer
3 | # be maintained but left right here for backwards-compatibility.
4 | BUILD_DIR = build
5 |
6 | CC = gcc
7 | CFLAGS = -Wall -Wextra
8 | CFLAGS += -pedantic -Wpedantic
9 |
10 | LDLIBS = -lncurses -lm
11 |
12 | VPATH += src
13 | SRC += commands.c
14 | SRC += lex.c
15 | SRC += view.c
16 | SRC += main.c
17 | SRC += cgetopt.c
18 | SRC += frontend.c
19 | SRC += keys.c
20 | SRC += buffer.c
21 | SRC += tools.c
22 |
23 | OBJ := $(SRC:%.c=$(BUILD_DIR)/release-objs/%.o)
24 | OBJ_DEBUG := $(SRC:%.c=$(BUILD_DIR)/debug-objs/%.o)
25 |
26 | PREFIX ?= /usr
27 |
28 | BINDIR ?= $(PREFIX)/bin/
29 | HELP_DIR ?= $(PREFIX)/share/cano/help
30 |
31 | ABS_HELP_DIR = $(shell realpath --canonicalize-missing $(HELP_DIR))
32 |
33 | CFLAGS_main += -DHELP_DIR='"$(ABS_HELP_DIR)"'
34 |
35 | all: cano
36 | cano: $(BUILD_DIR)/cano
37 | debug: $(BUILD_DIR)/debug
38 |
39 | .PHONY: all cano debug
40 |
41 | $(BUILD_DIR)/release-objs/%.o: %.c
42 | @ mkdir -p $(dir $@)
43 | $(CC) $(CFLAGS) $(CFLAGS_$(notdir $(@:.o=))) -o $@ -c $< || exit 1
44 |
45 | $(BUILD_DIR)/debug-objs/%.o: %.c
46 | @ mkdir -p $(dir $@)
47 | $(CC) $(CFLAGS) $(CFLAGS_$(notdir $(@:.o=))) -o $@ -c $< || exit 1
48 |
49 | $(BUILD_DIR)/cano: $(OBJ)
50 | @ mkdir -p $(dir $@)
51 | $(CC) -o $@ $(CFLAGS) $^ $(LDFLAGS) $(LDLIBS)
52 |
53 | $(BUILD_DIR)/debug: CFLAGS += -ggdb2
54 | $(BUILD_DIR)/debug: $(OBJ_DEBUG)
55 | @ mkdir -p $(dir $@)
56 | $(CC) -o $@ $(CFLAGS) $^ $(LDFLAGS) $(LDLIBS)
57 |
58 | clean:
59 | $(RM) $(OBJS) $(OBJ_DEBUG)
60 |
61 | fclean:
62 | $(RM) -r $(BUILD_DIR)
63 |
64 | .NOTPARALLEL: re
65 | re: fclean all
66 |
67 | .PHONY: clean fclean re
68 |
69 | install: all
70 | @ install -Dv ./docs/help/* -t $(HELP_DIR)
71 | @ install -Dv ./build/cano -t $(BINDIR)
72 |
73 | uninstall:
74 | @ $(RM) -r ~/$(HELP_DIR)
75 | @ $(RM) $(BINDIR)
76 |
77 | .PHONY: install uninstall
78 |
--------------------------------------------------------------------------------
/Makefile.am:
--------------------------------------------------------------------------------
1 | AUTOMAKE_OPTIONS = subdir-objects
2 |
3 | bin_PROGRAMS = cano
4 | cano_SOURCES = src/colors.h \
5 | src/defs.h \
6 | src/lex.h \
7 | src/main.h \
8 | src/view.h \
9 | src/commands.h \
10 | src/cgetopt.h \
11 | src/frontend.h \
12 | src/keys.h \
13 | src/tools.h \
14 | src/buffer.h \
15 | src/commands.c \
16 | src/lex.c \
17 | src/main.c \
18 | src/view.c \
19 | src/frontend.c \
20 | src/cgetopt.c \
21 | src/keys.c \
22 | src/tools.c \
23 | src/buffer.c
24 |
25 | cano_CFLAGS = @NCURSES_CFLAGS@
26 | cano_LDADD = @NCURSES_LIBS@ -lm -lpthread
27 |
28 | man1_MANS = docs/cano.1
29 |
--------------------------------------------------------------------------------
/PKGBUILD:
--------------------------------------------------------------------------------
1 | # Maintainer: CobbCoding
2 | pkgname=cano-git
3 | _pkgname=cano
4 | pkgver=r340.b5f4b53
5 | pkgrel=1
6 | pkgdesc="Terminal-based modal text editor"
7 | arch=('x86_64')
8 | url="https://github.com/CobbCoding1/cano"
9 | license=('APACHE')
10 | conflicts=('cano')
11 | depends=('ncurses' 'glibc')
12 | makedepends=('git' 'make' 'gcc' 'autoconf')
13 | source=("$_pkgname::git+https://github.com/CobbCoding1/$_pkgname.git")
14 | md5sums=('SKIP')
15 |
16 | pkgver() {
17 | cd "$_pkgname"
18 | printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
19 | }
20 |
21 | build() {
22 | cd "$_pkgname"
23 | autoreconf -vi
24 | ./configure
25 | make
26 | }
27 |
28 | package() {
29 | cd "$_pkgname"
30 | make DESTDIR="$pkdir" install
31 | install -Dm755 ./README.md "$pkgdir/usr/share/doc/$_pkgname"
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cano
2 | Cano (kay-no) is a VIM inspired modal-based text editor written in C using the [ncurses](https://opensource.apple.com/source/old_ncurses/old_ncurses-1/ncurses/test/ncurses.c.auto.html) library.
3 |
4 |  \
5 | Icon made by [LocalTexan](https://github.com/LocalTexan)
6 |
7 | # Demo
8 | [](https://asciinema.org/a/655184)
9 |
10 | ## Quick Start
11 | Cano has the following dependencies:
12 | - [GCC](https://gcc.gnu.org/)
13 | - [Make](https://www.gnu.org/software/make/)
14 | - [ncurses](https://opensource.apple.com/source/old_ncurses/old_ncurses-1/ncurses/test/ncurses.c.auto.html)
15 | - [Autotools](https://en.wikipedia.org/wiki/GNU_Autotools)
16 |
17 | 1. Navigate to the Cano directory
18 | ```sh
19 | cd path/to/cano
20 | ```
21 |
22 | 2. Make the generator script executable
23 | ```sh
24 | chmod +x autogen.sh
25 | ```
26 |
27 | 3. Run the generator script
28 | ```sh
29 | ./autogen.sh
30 | ```
31 |
32 | The build files will be stored at `path/to/cano/build`.
33 |
34 | 4. Compile cano
35 | ```sh
36 | make -C build
37 | ```
38 |
39 | 5. Run cano
40 | ```sh
41 | ./build/cano
42 | ```
43 |
44 | ## Modes
45 | Normal - For motions and deletion \
46 | Insert - For inserting text \
47 | Visual - For selecting text and performing actions on them \
48 | Search - For searching of text in the current buffer \
49 | Command - For executing commands
50 |
51 | ## Keybinds
52 | |Mode | Keybind | Action |
53 | |------|----------------|-------------------------------------------------|
54 | |Global| Ctrl + Q | Quit (regardless of mode) |
55 | |Global| Esc | Enter Normal Mode |
56 | |Normal| h | Move cursor left |
57 | |Normal| j | Move cursor down |
58 | |Normal| k | Move cursor up |
59 | |Normal| l | Move cursor right |
60 | |Normal| x | Delete character |
61 | |Normal| g | Go to first line |
62 | |Normal| G | Go to last line |
63 | |Normal| 0 | Go to beginning of line |
64 | |Normal| $ | Go to end of line |
65 | |Normal| w | Go to next word |
66 | |Normal| b | Go to last word |
67 | |Normal| e | Go to end of next word |
68 | |Normal| o | Create line below current |
69 | |Normal| O | Create line above current |
70 | |Normal| Ctrl + o | Create line below current without changing mode |
71 | |Normal| % | Go to corresponding brace |
72 | |Normal| i | Enter insert mode |
73 | |Normal| I | Go to beginning of line |
74 | |Normal| a | Insert mode on next char |
75 | |Normal| A | Insert mode at end of line |
76 | |Normal| v | Enter visual mode |
77 | |Normal| V | Enter visual mode by line |
78 | |Normal| u | Undo |
79 | |Normal| U | Redo |
80 | |Normal| / | Enter Search mode |
81 | |Normal| n | Jump to next search |
82 | |Normal| Ctrl + S | Save and exit |
83 | |Normal| r | Replace current char with next char inputted |
84 | |Normal| (n) + motion | Repeat next motion n times |
85 | |Normal| (d) + motion | Delete characters of next motion n times |
86 | |Normal| Ctrl + n | Open file explorer |
87 |
88 | ## Visual
89 | Visual mode works the same as Normal mode, except it works on the entire selection, instead of character by character.
90 | | Keybind | Action |
91 | |----------------|-------------------------------------------------|
92 | | > | Indent current selection |
93 | | < | Unindent current selection |
94 |
95 | ## Search
96 | Search mode takes a string and finds it in the file.
97 | if prepended with 's/' then it will replace the first substring with the second.
98 |
99 | Example: Using the following command
100 | ```sh
101 | s/hello/goodbye
102 | ```
103 | Will replace hello with goodbye.
104 |
105 | ## Commands
106 | | Command | Action |
107 | |-----------------------|-----------------------------------------------------------|
108 | | set-output | change output file |
109 | | echo (v) | echo value (v) where v is either an ident or a literal |
110 | | we | Write and exit |
111 | | e | Write without exiting |
112 | | set-var (var) (value) | Change a config variable |
113 | | set-map (a) "(b)" | Map key a to any combination of keys b |
114 | | let (n) (v) | Create variable (n) with value (v) |
115 | | !(command) | Execute a shell command |
116 |
117 | ### Special Keys
118 | There are a couple special keys for the key remaps.
119 | | Key |
120 | |--------------|
121 | | \ |
122 | | \ |
123 | | \ |
124 | | \ |
125 | | \ |
126 |
127 | ## Config file
128 | The config file is stored in ~/.config/cano/config.cano by default, or can be set at runtime like so:
129 | ```sh
130 | ./cano --config
131 | ```
132 |
133 | The format of the file is the same as commands, it is line separated, which is important.
134 | Example:
135 | ```sh
136 | set-var syntax 1
137 | set-var indent 2
138 | ```
139 |
140 | There is a secondary config file, which is for custom syntax highlighting. It is stored in the same folder as the regular config, but uses a different naming format.
141 | An example is ~/.config/cano/c.cyntax (spelled cyntax, with a c). The c can be replaced with whatever the file extension of your language is, such as go.cyntax for Golang.
142 | Here is an example of a cyntax file:
143 | ```sh
144 | k,170,68,68,
145 | auto,struct,break,else,switch,case,
146 | enum,register,typedef,extern,return,
147 | union,continue,for,signed,void,do,
148 | if,static,while,default,goto,sizeof,
149 | volatile,const,unsigned.
150 | t,255,165,0,
151 | double,size_t,int,
152 | long,char,float,short.
153 | w,128,160,255.
154 | ```
155 | There's a bit to unpack here, basically the single characters represent the type of the keywords:
156 | k - Keyword
157 | t - Type
158 | w - Word
159 | The type is then followed by the RGB values, all comma separated without spaces. After the RGB values, there is the actual keywords. End each type with a dot '.' as seen above, to indicate to Cano that the list is finished. The words are meant to be left blank, as it will highlight any words not found in the keywords above with the chosen RGB color.
160 | If you wish to only set the color, you can provide no keywords to any, and it will fill in the keywords with C keywords by default.
161 |
162 | ## Config Variables
163 | ```sh
164 | relative # toggle relative line numbers
165 | auto-indent # toggle auto indentation on-off
166 | syntax # toggle syntax highlighting on-off
167 | indent # set indent
168 | undo-size # size of undo history
169 | ```
170 |
171 | # Installation
172 |
173 | ## Arch
174 | [](https://repology.org/project/cano/versions) \
175 | A package is provided within this [AUR](https://aur.archlinux.org/packages/cano).
176 | You can install it using your preferred aur helper:
177 |
178 | For instance, if using yay, do the following:
179 | ```sh
180 | yay -S cano-git
181 | ```
182 |
183 | ## Nix / NixOS
184 |
185 | Cano's build system recently changed, Nix installation is not supported for now.
186 |
187 | ## Debian/Ubuntu
188 |
189 | You may build from source and install cano directly to `/usr/local/bin`. You must have a basic C compiler, autotools, pkg-config and the ncurses library installed (install shown below).
190 |
191 | ```sh
192 | sudo apt install gcc autoconf automake libtool pkg-config make libncurses-dev
193 | git clone https://github.com/CobbCoding1/Cano && cd Cano
194 | chmod +x autogen.sh && ./autogen.sh
195 | cd build
196 | make
197 | sudo make install
198 | ```
199 |
200 | ## Canoon (Beta)
201 | The official Cano installer and manager, currently in Beta.
202 | [canoon](https://github.com/kul-sudo/canoon)
203 |
--------------------------------------------------------------------------------
/autogen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -xe
4 |
5 | autoreconf -vi
6 | mkdir -p build
7 | cd build
8 | ../configure
9 |
--------------------------------------------------------------------------------
/cano:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cano-Projects/Cano/e8a6314511101d86628e5346436d25e61b2f1cbe/cano
--------------------------------------------------------------------------------
/cano.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cano-Projects/Cano/e8a6314511101d86628e5346436d25e61b2f1cbe/cano.png
--------------------------------------------------------------------------------
/cleanup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | rm -rf autom4te.cache build
4 | rm -f aclocal.m4 compile config.guess config.sub configure depcomp install-sh Makefile.in missing configure~ ar-lib ltmain.sh
5 |
--------------------------------------------------------------------------------
/config.log:
--------------------------------------------------------------------------------
1 | This file contains any messages produced by compilers while
2 | running configure, to aid debugging if configure makes a mistake.
3 |
4 | It was created by cano configure 0.1.0, which was
5 | generated by GNU Autoconf 2.72. Invocation command line was
6 |
7 | $ ./configure
8 |
9 | ## --------- ##
10 | ## Platform. ##
11 | ## --------- ##
12 |
13 | hostname = arch
14 | uname -m = x86_64
15 | uname -r = 6.10.6-arch1-1
16 | uname -s = Linux
17 | uname -v = #1 SMP PREEMPT_DYNAMIC Mon, 19 Aug 2024 17:02:39 +0000
18 |
19 | /usr/bin/uname -p = unknown
20 | /bin/uname -X = unknown
21 |
22 | /bin/arch = unknown
23 | /usr/bin/arch -k = unknown
24 | /usr/convex/getsysinfo = unknown
25 | /usr/bin/hostinfo = unknown
26 | /bin/machine = unknown
27 | /usr/bin/oslevel = unknown
28 | /bin/universe = unknown
29 |
30 | PATH: /home/ic/.opam/default/bin/
31 | PATH: /home/ic/.opam/default/bin/
32 | PATH: /home/ic/.nix-profile/bin/
33 | PATH: /nix/var/nix/profiles/default/bin/
34 | PATH: /home/ic/.nvm/versions/node/v16.13.0/bin/
35 | PATH: /home/ic/.cargo/bin/
36 | PATH: /home/ic/.nix-profile/bin/
37 | PATH: /nix/var/nix/profiles/default/bin/
38 | PATH: /usr/local/bin/
39 | PATH: /usr/bin/
40 | PATH: /bin/
41 | PATH: /usr/local/sbin/
42 | PATH: /opt/android-sdk/cmdline-tools/latest/bin/
43 | PATH: /opt/android-sdk/platform-tools/
44 | PATH: /opt/android-sdk/tools/
45 | PATH: /opt/android-sdk/tools/bin/
46 | PATH: /var/lib/flatpak/exports/bin/
47 | PATH: /usr/lib/jvm/default/bin/
48 | PATH: /usr/bin/site_perl/
49 | PATH: /usr/bin/vendor_perl/
50 | PATH: /usr/bin/core_perl/
51 | PATH: /home/ic/bin/run-jami/
52 | PATH: /opt/flutter/bin/
53 | PATH: /home/ic/.local/bin/
54 | PATH: /home/ic/Projects/Porth/porth/
55 | PATH: /opt/android-sdk/cmdline-tools/latest/bin/
56 | PATH: /opt/android-sdk/platform-tools/
57 | PATH: /opt/android-sdk/tools/
58 | PATH: /opt/android-sdk/tools/bin/
59 | PATH: /home/ic/bin/run-jami/
60 | PATH: /opt/flutter/bin/
61 | PATH: /home/ic/.local/bin/
62 | PATH: /home/ic/Projects/Porth/porth/
63 |
64 |
65 | ## ----------- ##
66 | ## Core tests. ##
67 | ## ----------- ##
68 |
69 | configure:2318: looking for aux files: config.guess config.sub compile missing install-sh
70 | configure:2331: trying ./
71 | configure:2360: ./config.guess found
72 | configure:2360: ./config.sub found
73 | configure:2360: ./compile found
74 | configure:2360: ./missing found
75 | configure:2342: ./install-sh found
76 | configure:2490: checking for a BSD-compatible install
77 | configure:2564: result: /usr/bin/install -c
78 | configure:2575: checking whether sleep supports fractional seconds
79 | configure:2591: result: yes
80 | configure:2594: checking filesystem timestamp resolution
81 | configure:2729: result: 0.01
82 | configure:2734: checking whether build environment is sane
83 | configure:2775: result: yes
84 | configure:2946: checking for a race-free mkdir -p
85 | configure:2989: result: /usr/bin/mkdir -p
86 | configure:2996: checking for gawk
87 | configure:3017: found /usr/bin/gawk
88 | configure:3029: result: gawk
89 | configure:3040: checking whether make sets $(MAKE)
90 | configure:3064: result: yes
91 | configure:3090: checking whether make supports nested variables
92 | configure:3109: result: yes
93 | configure:3123: checking xargs -n works
94 | configure:3139: result: yes
95 | configure:3301: checking for gcc
96 | configure:3322: found /usr/bin/gcc
97 | configure:3334: result: gcc
98 | configure:3693: checking for C compiler version
99 | configure:3702: gcc --version >&5
100 | gcc (GCC) 14.2.1 20240805
101 | Copyright (C) 2024 Free Software Foundation, Inc.
102 | This is free software; see the source for copying conditions. There is NO
103 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
104 |
105 | configure:3713: $? = 0
106 | configure:3702: gcc -v >&5
107 | Using built-in specs.
108 | COLLECT_GCC=gcc
109 | COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/lto-wrapper
110 | Target: x86_64-pc-linux-gnu
111 | Configured with: /build/gcc/src/gcc/configure --enable-languages=ada,c,c++,d,fortran,go,lto,m2,objc,obj-c++,rust --enable-bootstrap --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://gitlab.archlinux.org/archlinux/packaging/packages/gcc/-/issues --with-build-config=bootstrap-lto --with-linker-hash-style=gnu --with-system-zlib --enable-__cxa_atexit --enable-cet=auto --enable-checking=release --enable-clocale=gnu --enable-default-pie --enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object --enable-libstdcxx-backtrace --enable-link-serialization=1 --enable-linker-build-id --enable-lto --enable-multilib --enable-plugin --enable-shared --enable-threads=posix --disable-libssp --disable-libstdcxx-pch --disable-werror
112 | Thread model: posix
113 | Supported LTO compression algorithms: zlib zstd
114 | gcc version 14.2.1 20240805 (GCC)
115 | configure:3713: $? = 0
116 | configure:3702: gcc -V >&5
117 | gcc: error: unrecognized command-line option '-V'
118 | gcc: fatal error: no input files
119 | compilation terminated.
120 | configure:3713: $? = 1
121 | configure:3702: gcc -qversion >&5
122 | gcc: error: unrecognized command-line option '-qversion'; did you mean '--version'?
123 | gcc: fatal error: no input files
124 | compilation terminated.
125 | configure:3713: $? = 1
126 | configure:3702: gcc -version >&5
127 | gcc: error: unrecognized command-line option '-version'
128 | gcc: fatal error: no input files
129 | compilation terminated.
130 | configure:3713: $? = 1
131 | configure:3733: checking whether the C compiler works
132 | configure:3755: gcc conftest.c >&5
133 | configure:3759: $? = 0
134 | configure:3810: result: yes
135 | configure:3814: checking for C compiler default output file name
136 | configure:3816: result: a.out
137 | configure:3822: checking for suffix of executables
138 | configure:3829: gcc -o conftest conftest.c >&5
139 | configure:3833: $? = 0
140 | configure:3857: result:
141 | configure:3881: checking whether we are cross compiling
142 | configure:3889: gcc -o conftest conftest.c >&5
143 | configure:3893: $? = 0
144 | configure:3900: ./conftest
145 | configure:3904: $? = 0
146 | configure:3919: result: no
147 | configure:3925: checking for suffix of object files
148 | configure:3948: gcc -c conftest.c >&5
149 | configure:3952: $? = 0
150 | configure:3976: result: o
151 | configure:3980: checking whether the compiler supports GNU C
152 | configure:4000: gcc -c conftest.c >&5
153 | configure:4000: $? = 0
154 | configure:4012: result: yes
155 | configure:4023: checking whether gcc accepts -g
156 | configure:4044: gcc -c -g conftest.c >&5
157 | configure:4044: $? = 0
158 | configure:4091: result: yes
159 | configure:4111: checking for gcc option to enable C11 features
160 | configure:4126: gcc -c -g -O2 conftest.c >&5
161 | configure:4126: $? = 0
162 | configure:4145: result: none needed
163 | configure:4269: checking whether gcc understands -c and -o together
164 | configure:4292: gcc -c conftest.c -o conftest2.o
165 | configure:4295: $? = 0
166 | configure:4292: gcc -c conftest.c -o conftest2.o
167 | configure:4295: $? = 0
168 | configure:4308: result: yes
169 | configure:4328: checking whether make supports the include directive
170 | configure:4343: make -f confmf.GNU && cat confinc.out
171 | this is the am__doit target
172 | configure:4346: $? = 0
173 | configure:4365: result: yes (GNU style)
174 | configure:4391: checking dependency style of gcc
175 | configure:4504: result: gcc3
176 | configure:4581: checking for pkg-config
177 | configure:4604: found /usr/bin/pkg-config
178 | configure:4617: result: /usr/bin/pkg-config
179 | configure:4642: checking pkg-config is at least version 0.9.0
180 | configure:4645: result: yes
181 | configure:4655: checking for ncurses
182 | configure:4662: $PKG_CONFIG --exists --print-errors "ncurses"
183 | configure:4665: $? = 0
184 | configure:4679: $PKG_CONFIG --exists --print-errors "ncurses"
185 | configure:4682: $? = 0
186 | configure:4720: result: yes
187 | configure:4731: checking for stdio.h
188 | configure:4731: gcc -c -g -O2 conftest.c >&5
189 | configure:4731: $? = 0
190 | configure:4731: result: yes
191 | configure:4731: checking for stdlib.h
192 | configure:4731: gcc -c -g -O2 conftest.c >&5
193 | configure:4731: $? = 0
194 | configure:4731: result: yes
195 | configure:4731: checking for string.h
196 | configure:4731: gcc -c -g -O2 conftest.c >&5
197 | configure:4731: $? = 0
198 | configure:4731: result: yes
199 | configure:4731: checking for inttypes.h
200 | configure:4731: gcc -c -g -O2 conftest.c >&5
201 | configure:4731: $? = 0
202 | configure:4731: result: yes
203 | configure:4731: checking for stdint.h
204 | configure:4731: gcc -c -g -O2 conftest.c >&5
205 | configure:4731: $? = 0
206 | configure:4731: result: yes
207 | configure:4731: checking for strings.h
208 | configure:4731: gcc -c -g -O2 conftest.c >&5
209 | configure:4731: $? = 0
210 | configure:4731: result: yes
211 | configure:4731: checking for sys/stat.h
212 | configure:4731: gcc -c -g -O2 conftest.c >&5
213 | configure:4731: $? = 0
214 | configure:4731: result: yes
215 | configure:4731: checking for sys/types.h
216 | configure:4731: gcc -c -g -O2 conftest.c >&5
217 | configure:4731: $? = 0
218 | configure:4731: result: yes
219 | configure:4731: checking for unistd.h
220 | configure:4731: gcc -c -g -O2 conftest.c >&5
221 | configure:4731: $? = 0
222 | configure:4731: result: yes
223 | configure:4758: checking for locale.h
224 | configure:4758: gcc -c -g -O2 conftest.c >&5
225 | configure:4758: $? = 0
226 | configure:4758: result: yes
227 | configure:4770: checking for _Bool
228 | configure:4770: gcc -c -g -O2 conftest.c >&5
229 | configure:4770: $? = 0
230 | configure:4770: gcc -c -g -O2 conftest.c >&5
231 | conftest.c: In function 'main':
232 | conftest.c:53:20: error: expected expression before ')' token
233 | 53 | if (sizeof ((_Bool)))
234 | | ^
235 | configure:4770: $? = 1
236 | configure: failed program was:
237 | | /* confdefs.h */
238 | | #define PACKAGE_NAME "cano"
239 | | #define PACKAGE_TARNAME "cano"
240 | | #define PACKAGE_VERSION "0.1.0"
241 | | #define PACKAGE_STRING "cano 0.1.0"
242 | | #define PACKAGE_BUGREPORT ""
243 | | #define PACKAGE_URL "https://github.com/CobbCoding1/Cano"
244 | | #define PACKAGE "cano"
245 | | #define VERSION "0.1.0"
246 | | #define HAVE_STDIO_H 1
247 | | #define HAVE_STDLIB_H 1
248 | | #define HAVE_STRING_H 1
249 | | #define HAVE_INTTYPES_H 1
250 | | #define HAVE_STDINT_H 1
251 | | #define HAVE_STRINGS_H 1
252 | | #define HAVE_SYS_STAT_H 1
253 | | #define HAVE_SYS_TYPES_H 1
254 | | #define HAVE_UNISTD_H 1
255 | | #define STDC_HEADERS 1
256 | | #define HAVE_LOCALE_H 1
257 | | /* end confdefs.h. */
258 | | #include
259 | | #ifdef HAVE_STDIO_H
260 | | # include
261 | | #endif
262 | | #ifdef HAVE_STDLIB_H
263 | | # include
264 | | #endif
265 | | #ifdef HAVE_STRING_H
266 | | # include
267 | | #endif
268 | | #ifdef HAVE_INTTYPES_H
269 | | # include
270 | | #endif
271 | | #ifdef HAVE_STDINT_H
272 | | # include
273 | | #endif
274 | | #ifdef HAVE_STRINGS_H
275 | | # include
276 | | #endif
277 | | #ifdef HAVE_SYS_TYPES_H
278 | | # include
279 | | #endif
280 | | #ifdef HAVE_SYS_STAT_H
281 | | # include
282 | | #endif
283 | | #ifdef HAVE_UNISTD_H
284 | | # include
285 | | #endif
286 | | int
287 | | main (void)
288 | | {
289 | | if (sizeof ((_Bool)))
290 | | return 0;
291 | | ;
292 | | return 0;
293 | | }
294 | configure:4770: result: yes
295 | configure:4779: checking for stdbool.h that conforms to C99 or later
296 | configure:4854: gcc -c -g -O2 conftest.c >&5
297 | configure:4854: $? = 0
298 | configure:4864: result: yes
299 | configure:4867: checking for size_t
300 | configure:4867: gcc -c -g -O2 conftest.c >&5
301 | configure:4867: $? = 0
302 | configure:4867: gcc -c -g -O2 conftest.c >&5
303 | conftest.c: In function 'main':
304 | conftest.c:54:21: error: expected expression before ')' token
305 | 54 | if (sizeof ((size_t)))
306 | | ^
307 | configure:4867: $? = 1
308 | configure: failed program was:
309 | | /* confdefs.h */
310 | | #define PACKAGE_NAME "cano"
311 | | #define PACKAGE_TARNAME "cano"
312 | | #define PACKAGE_VERSION "0.1.0"
313 | | #define PACKAGE_STRING "cano 0.1.0"
314 | | #define PACKAGE_BUGREPORT ""
315 | | #define PACKAGE_URL "https://github.com/CobbCoding1/Cano"
316 | | #define PACKAGE "cano"
317 | | #define VERSION "0.1.0"
318 | | #define HAVE_STDIO_H 1
319 | | #define HAVE_STDLIB_H 1
320 | | #define HAVE_STRING_H 1
321 | | #define HAVE_INTTYPES_H 1
322 | | #define HAVE_STDINT_H 1
323 | | #define HAVE_STRINGS_H 1
324 | | #define HAVE_SYS_STAT_H 1
325 | | #define HAVE_SYS_TYPES_H 1
326 | | #define HAVE_UNISTD_H 1
327 | | #define STDC_HEADERS 1
328 | | #define HAVE_LOCALE_H 1
329 | | #define HAVE__BOOL 1
330 | | /* end confdefs.h. */
331 | | #include
332 | | #ifdef HAVE_STDIO_H
333 | | # include
334 | | #endif
335 | | #ifdef HAVE_STDLIB_H
336 | | # include
337 | | #endif
338 | | #ifdef HAVE_STRING_H
339 | | # include
340 | | #endif
341 | | #ifdef HAVE_INTTYPES_H
342 | | # include
343 | | #endif
344 | | #ifdef HAVE_STDINT_H
345 | | # include
346 | | #endif
347 | | #ifdef HAVE_STRINGS_H
348 | | # include
349 | | #endif
350 | | #ifdef HAVE_SYS_TYPES_H
351 | | # include
352 | | #endif
353 | | #ifdef HAVE_SYS_STAT_H
354 | | # include
355 | | #endif
356 | | #ifdef HAVE_UNISTD_H
357 | | # include
358 | | #endif
359 | | int
360 | | main (void)
361 | | {
362 | | if (sizeof ((size_t)))
363 | | return 0;
364 | | ;
365 | | return 0;
366 | | }
367 | configure:4867: result: yes
368 | configure:4885: checking build system type
369 | configure:4901: result: x86_64-pc-linux-gnu
370 | configure:4921: checking host system type
371 | configure:4936: result: x86_64-pc-linux-gnu
372 | configure:4956: checking for GNU libc compatible malloc
373 | configure:4988: gcc -o conftest -g -O2 conftest.c >&5
374 | configure:4988: $? = 0
375 | configure:4988: ./conftest
376 | configure:4988: $? = 0
377 | configure:5002: result: yes
378 | configure:5025: checking for GNU libc compatible realloc
379 | configure:5057: gcc -o conftest -g -O2 conftest.c >&5
380 | configure:5057: $? = 0
381 | configure:5057: ./conftest
382 | configure:5057: $? = 0
383 | configure:5071: result: yes
384 | configure:5234: checking that generated files are newer than configure
385 | configure:5240: result: done
386 | configure:5275: creating ./config.status
387 |
388 | ## ---------------------- ##
389 | ## Running config.status. ##
390 | ## ---------------------- ##
391 |
392 | This file was extended by cano config.status 0.1.0, which was
393 | generated by GNU Autoconf 2.72. Invocation command line was
394 |
395 | CONFIG_FILES =
396 | CONFIG_HEADERS =
397 | CONFIG_LINKS =
398 | CONFIG_COMMANDS =
399 | $ ./config.status
400 |
401 | on arch
402 |
403 | config.status:790: creating Makefile
404 | config.status:962: executing depfiles commands
405 | config.status:1039: cd . && sed -e '/# am--include-marker/d' Makefile | make -f - am--depfiles
406 | config.status:1044: $? = 0
407 |
408 | ## ---------------- ##
409 | ## Cache variables. ##
410 | ## ---------------- ##
411 |
412 | ac_cv_build=x86_64-pc-linux-gnu
413 | ac_cv_c_compiler_gnu=yes
414 | ac_cv_env_CC_set=
415 | ac_cv_env_CC_value=
416 | ac_cv_env_CFLAGS_set=
417 | ac_cv_env_CFLAGS_value=
418 | ac_cv_env_CPPFLAGS_set=
419 | ac_cv_env_CPPFLAGS_value=
420 | ac_cv_env_LDFLAGS_set=
421 | ac_cv_env_LDFLAGS_value=
422 | ac_cv_env_LIBS_set=
423 | ac_cv_env_LIBS_value=
424 | ac_cv_env_NCURSES_CFLAGS_set=
425 | ac_cv_env_NCURSES_CFLAGS_value=
426 | ac_cv_env_NCURSES_LIBS_set=
427 | ac_cv_env_NCURSES_LIBS_value=
428 | ac_cv_env_PKG_CONFIG_LIBDIR_set=
429 | ac_cv_env_PKG_CONFIG_LIBDIR_value=
430 | ac_cv_env_PKG_CONFIG_PATH_set=
431 | ac_cv_env_PKG_CONFIG_PATH_value=
432 | ac_cv_env_PKG_CONFIG_set=
433 | ac_cv_env_PKG_CONFIG_value=
434 | ac_cv_env_build_alias_set=
435 | ac_cv_env_build_alias_value=
436 | ac_cv_env_host_alias_set=
437 | ac_cv_env_host_alias_value=
438 | ac_cv_env_target_alias_set=
439 | ac_cv_env_target_alias_value=
440 | ac_cv_func_malloc_0_nonnull=yes
441 | ac_cv_func_realloc_0_nonnull=yes
442 | ac_cv_header_inttypes_h=yes
443 | ac_cv_header_locale_h=yes
444 | ac_cv_header_stdbool_h=yes
445 | ac_cv_header_stdint_h=yes
446 | ac_cv_header_stdio_h=yes
447 | ac_cv_header_stdlib_h=yes
448 | ac_cv_header_string_h=yes
449 | ac_cv_header_strings_h=yes
450 | ac_cv_header_sys_stat_h=yes
451 | ac_cv_header_sys_types_h=yes
452 | ac_cv_header_unistd_h=yes
453 | ac_cv_host=x86_64-pc-linux-gnu
454 | ac_cv_objext=o
455 | ac_cv_path_ac_pt_PKG_CONFIG=/usr/bin/pkg-config
456 | ac_cv_path_install='/usr/bin/install -c'
457 | ac_cv_path_mkdir=/usr/bin/mkdir
458 | ac_cv_prog_AWK=gawk
459 | ac_cv_prog_ac_ct_CC=gcc
460 | ac_cv_prog_cc_c11=
461 | ac_cv_prog_cc_g=yes
462 | ac_cv_prog_cc_stdc=
463 | ac_cv_prog_make_make_set=yes
464 | ac_cv_type__Bool=yes
465 | ac_cv_type_size_t=yes
466 | am_cv_CC_dependencies_compiler_type=gcc3
467 | am_cv_filesystem_timestamp_resolution=0.01
468 | am_cv_make_support_nested_variables=yes
469 | am_cv_prog_cc_c_o=yes
470 | am_cv_sleep_fractional_seconds=yes
471 | am_cv_xargs_n_works=yes
472 | pkg_cv_NCURSES_CFLAGS='-D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600'
473 | pkg_cv_NCURSES_LIBS=-lncursesw
474 |
475 | ## ----------------- ##
476 | ## Output variables. ##
477 | ## ----------------- ##
478 |
479 | ACLOCAL='${SHELL} '\''/home/ic/Projects/C/cano/missing'\'' aclocal-1.17'
480 | AMDEPBACKSLASH='\'
481 | AMDEP_FALSE='#'
482 | AMDEP_TRUE=''
483 | AMTAR='$${TAR-tar}'
484 | AM_BACKSLASH='\'
485 | AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
486 | AM_DEFAULT_VERBOSITY='1'
487 | AM_V='$(V)'
488 | AUTOCONF='${SHELL} '\''/home/ic/Projects/C/cano/missing'\'' autoconf'
489 | AUTOHEADER='${SHELL} '\''/home/ic/Projects/C/cano/missing'\'' autoheader'
490 | AUTOMAKE='${SHELL} '\''/home/ic/Projects/C/cano/missing'\'' automake-1.17'
491 | AWK='gawk'
492 | CC='gcc'
493 | CCDEPMODE='depmode=gcc3'
494 | CFLAGS='-g -O2'
495 | CPPFLAGS=''
496 | CSCOPE='cscope'
497 | CTAGS='ctags'
498 | CYGPATH_W='echo'
499 | DEFS='-DPACKAGE_NAME=\"cano\" -DPACKAGE_TARNAME=\"cano\" -DPACKAGE_VERSION=\"0.1.0\" -DPACKAGE_STRING=\"cano\ 0.1.0\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"https://github.com/CobbCoding1/Cano\" -DPACKAGE=\"cano\" -DVERSION=\"0.1.0\" -DHAVE_STDIO_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_STRINGS_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_UNISTD_H=1 -DSTDC_HEADERS=1 -DHAVE_LOCALE_H=1 -DHAVE__BOOL=1 -DHAVE_MALLOC=1 -DHAVE_REALLOC=1'
500 | DEPDIR='.deps'
501 | ECHO_C=''
502 | ECHO_N='-n'
503 | ECHO_T=''
504 | ETAGS='etags'
505 | EXEEXT=''
506 | INSTALL_DATA='${INSTALL} -m 644'
507 | INSTALL_PROGRAM='${INSTALL}'
508 | INSTALL_SCRIPT='${INSTALL}'
509 | INSTALL_STRIP_PROGRAM='$(install_sh) -c -s'
510 | LDFLAGS=''
511 | LIBOBJS=''
512 | LIBS=''
513 | LTLIBOBJS=''
514 | MAKEINFO='${SHELL} '\''/home/ic/Projects/C/cano/missing'\'' makeinfo'
515 | MKDIR_P='/usr/bin/mkdir -p'
516 | NCURSES_CFLAGS='-D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600'
517 | NCURSES_LIBS='-lncursesw'
518 | OBJEXT='o'
519 | PACKAGE='cano'
520 | PACKAGE_BUGREPORT=''
521 | PACKAGE_NAME='cano'
522 | PACKAGE_STRING='cano 0.1.0'
523 | PACKAGE_TARNAME='cano'
524 | PACKAGE_URL='https://github.com/CobbCoding1/Cano'
525 | PACKAGE_VERSION='0.1.0'
526 | PATH_SEPARATOR=':'
527 | PKG_CONFIG='/usr/bin/pkg-config'
528 | PKG_CONFIG_LIBDIR=''
529 | PKG_CONFIG_PATH=''
530 | SET_MAKE=''
531 | SHELL='/bin/sh'
532 | STRIP=''
533 | VERSION='0.1.0'
534 | ac_ct_CC='gcc'
535 | am__EXEEXT_FALSE=''
536 | am__EXEEXT_TRUE='#'
537 | am__fastdepCC_FALSE='#'
538 | am__fastdepCC_TRUE=''
539 | am__include='include'
540 | am__isrc=''
541 | am__leading_dot='.'
542 | am__nodep='_no'
543 | am__quote=''
544 | am__rm_f_notfound=''
545 | am__tar='$${TAR-tar} chof - "$$tardir"'
546 | am__untar='$${TAR-tar} xf -'
547 | am__xargs_n='xargs -n'
548 | bindir='${exec_prefix}/bin'
549 | build='x86_64-pc-linux-gnu'
550 | build_alias=''
551 | build_cpu='x86_64'
552 | build_os='linux-gnu'
553 | build_vendor='pc'
554 | datadir='${datarootdir}'
555 | datarootdir='${prefix}/share'
556 | docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
557 | dvidir='${docdir}'
558 | exec_prefix='${prefix}'
559 | host='x86_64-pc-linux-gnu'
560 | host_alias=''
561 | host_cpu='x86_64'
562 | host_os='linux-gnu'
563 | host_vendor='pc'
564 | htmldir='${docdir}'
565 | includedir='${prefix}/include'
566 | infodir='${datarootdir}/info'
567 | install_sh='${SHELL} /home/ic/Projects/C/cano/install-sh'
568 | libdir='${exec_prefix}/lib'
569 | libexecdir='${exec_prefix}/libexec'
570 | localedir='${datarootdir}/locale'
571 | localstatedir='${prefix}/var'
572 | mandir='${datarootdir}/man'
573 | mkdir_p='$(MKDIR_P)'
574 | oldincludedir='/usr/include'
575 | pdfdir='${docdir}'
576 | prefix='/usr/local'
577 | program_transform_name='s,x,x,'
578 | psdir='${docdir}'
579 | runstatedir='${localstatedir}/run'
580 | sbindir='${exec_prefix}/sbin'
581 | sharedstatedir='${prefix}/com'
582 | sysconfdir='${prefix}/etc'
583 | target_alias=''
584 |
585 | ## ----------- ##
586 | ## confdefs.h. ##
587 | ## ----------- ##
588 |
589 | /* confdefs.h */
590 | #define PACKAGE_NAME "cano"
591 | #define PACKAGE_TARNAME "cano"
592 | #define PACKAGE_VERSION "0.1.0"
593 | #define PACKAGE_STRING "cano 0.1.0"
594 | #define PACKAGE_BUGREPORT ""
595 | #define PACKAGE_URL "https://github.com/CobbCoding1/Cano"
596 | #define PACKAGE "cano"
597 | #define VERSION "0.1.0"
598 | #define HAVE_STDIO_H 1
599 | #define HAVE_STDLIB_H 1
600 | #define HAVE_STRING_H 1
601 | #define HAVE_INTTYPES_H 1
602 | #define HAVE_STDINT_H 1
603 | #define HAVE_STRINGS_H 1
604 | #define HAVE_SYS_STAT_H 1
605 | #define HAVE_SYS_TYPES_H 1
606 | #define HAVE_UNISTD_H 1
607 | #define STDC_HEADERS 1
608 | #define HAVE_LOCALE_H 1
609 | #define HAVE__BOOL 1
610 | #define HAVE_MALLOC 1
611 | #define HAVE_REALLOC 1
612 |
613 | configure: exit 0
614 |
--------------------------------------------------------------------------------
/configure.ac:
--------------------------------------------------------------------------------
1 | AC_INIT([cano], [0.1.0], [], [], [https://github.com/CobbCoding1/Cano])
2 | AM_INIT_AUTOMAKE([-Wall -Wextra foreign])
3 |
4 | AC_PROG_CC
5 | AC_PROG_INSTALL
6 |
7 | PKG_CHECK_MODULES([NCURSES], ncurses, [], AC_MSG_ERROR([Failed to find ncurses]))
8 | # AC_CHECK_LIB([m], [floor], [], AC_MSG_ERROR([Failed to find 'floor' in libm]))
9 | # AC_CHECK_LIB([m], [log10], [], AC_MSG_ERROR([Failed to find 'log10' in libm]))
10 | AC_CHECK_HEADERS([locale.h], [], AC_MSG_ERROR([Failed to find locale.h]))
11 |
12 | AC_CHECK_HEADER_STDBOOL
13 | AC_TYPE_SIZE_T
14 |
15 | AC_FUNC_MALLOC
16 | AC_FUNC_REALLOC
17 |
18 | # AC_CHECK_FUNCS([setlocale])
19 |
20 | AC_CONFIG_FILES([Makefile])
21 | AC_OUTPUT
22 |
--------------------------------------------------------------------------------
/docs/cano.1:
--------------------------------------------------------------------------------
1 | .TH cano 1 2024-1-24 LINUX
2 |
3 | .SH NAME
4 | cano - is a VIM inspired modal-based text editor written in C using the ncurses library.
5 |
6 | .SH SYNOPSIS
7 | .B cano
8 | [\fI--options\fR] [\fIfile\fR]
9 |
10 | .SH DESCRIPTION
11 | .B cano
12 | is a small text editor based on kilo that edits file.
13 |
14 | .SH OPTIONS
15 | .TP
16 | .BR \-v ", " \-\-version
17 | outputs version information and exits.
18 |
19 | .TP
20 | .BR \-\-config " \fIconfig-file\fR
21 | uses the specified config-file instead of the default config.
22 |
23 | .TP
24 | .BR \-\-help " [\fIhelp-page\fR]
25 | opens the specified help-page or the "general" page if no page was specified.
26 |
27 | .SH EDITING
28 | Editing a file is fairly easy; you start the editor and start typing the letters that you want.
29 | You can move around with the arrow keys and delete characters with backspace or delete key.
30 |
31 | .SH KEY BINDINGS
32 | .B Ctrl+Q
33 | .br
34 | Exit cano.
35 | .br
36 | .B Esc
37 | .br
38 | Enter Normal Mode
39 | .br
40 | .B h
41 | .br
42 | Move cursor left
43 | .br
44 | .B j
45 | .br
46 | Move cursor down
47 | .br
48 | .B k
49 | .br
50 | Move cursor up
51 | .br
52 | .B l
53 | .br
54 | Move Cursor right
55 | .br
56 | .B x
57 | .br
58 | Delete character
59 | .br
60 | .B g
61 | .br
62 | Go to first line
63 | .br
64 | .B G
65 | .br
66 | Go to last line
67 | .br
68 | .B 0
69 | .br
70 | Go to beginning of line
71 | .br
72 | .B $
73 | .br
74 | Go to end of line
75 | .br
76 | .B w
77 | .br
78 | Go to next word
79 | .br
80 | .B b
81 | .br
82 | Go to last word
83 | .br
84 | .B e
85 | .br
86 | Go to end of word
87 | .br
88 | .B o
89 | .br
90 | Create line below current
91 | .br
92 | .B O
93 | .br
94 | Create line above current
95 | .br
96 | .B Ctrl+o
97 | .br
98 | Create line below current without changing mode
99 | .br
100 | .B %
101 | .br
102 | Move to corresponding brace
103 | .br
104 | .B i
105 | .br
106 | Enter insert mode
107 | .br
108 | .B I
109 | .br
110 | Go to beginning of line and enter insert mode
111 | .br
112 | .B a
113 | .br
114 | Insert mode on next char
115 | .br
116 | .B A
117 | .br
118 | Go to end of line and enter insert mode
119 | .br
120 | .B v
121 | .br
122 | Enter visual mode
123 | .br
124 | .B V
125 | .br
126 | Enter visual-line mode
127 | .br
128 | .B u
129 | .br
130 | Undo
131 | .br
132 | .B U
133 | .br
134 | Redo
135 | .br
136 | .B /
137 | .br
138 | Enter search mode
139 | .br
140 | .B n
141 | .br
142 | Jump to next
143 | .br
144 | .B Ctrl+s
145 | .br
146 | Save and exitr
147 | .br
148 | .B r
149 | .br
150 | Replace current char with next character
151 | .br
152 | .B (n) + motion
153 | .br
154 | Repeat next motion (n) times
155 | .br
156 | .B Ctrl+n
157 | .br
158 | Open file explorer
159 | .br
160 |
161 | .SH FILES
162 | .TP
163 | .BR ~/.config/cano/config.cano
164 | The default config file.
165 |
166 | .TP
167 | .BR ~/.local/share/cano/help
168 | The directory, where all help-pages are stored.
169 |
170 | .SH BUGS
171 | .TP
172 | You can report any bugs to \fIhttps://github.com/CobbCoding1/Cano/issues\fR.
173 |
174 |
175 | .SH AUTHOUR
176 | .TP
177 | Mainly developed by \fICobbCoding\fR. You can see other contributers in the github page \fIhttps://github.com/CobbCoding1/cano\fR.
178 |
179 | .SH COPYRIGHT
180 | .TP
181 | Apache-2.0 License
182 |
183 | .SH HOMEPAGE
184 | .TP
185 | .I https://github.com/CobbCoding1/cano
186 |
--------------------------------------------------------------------------------
/docs/help/cmds:
--------------------------------------------------------------------------------
1 | Cano commands:
2 |
3 | Command | Action
4 | -------------------------------------------------------------------------------
5 | set-output | change output file
6 | -------------------------------------------------------------------------------
7 | echo (v) | echo value (v) where v is either an ident or literal
8 | -------------------------------------------------------------------------------
9 | we | Write and exit
10 | -------------------------------------------------------------------------------
11 | e | Write without exiting
12 | -------------------------------------------------------------------------------
13 | set-var (var) (value) | Change a config variable
14 | -------------------------------------------------------------------------------
15 | set-map (a) "(b)" | Map key (a) to any combination of keys (b)
16 | -------------------------------------------------------------------------------
17 | let (n) (v) | Create variable (n) with value (v)
18 | -------------------------------------------------------------------------------
19 | !(command) | Execute a shell command
20 |
21 |
22 |
--------------------------------------------------------------------------------
/docs/help/general:
--------------------------------------------------------------------------------
1 |
2 | ((((((((((((((((((
3 | ((((((((((( (((((((((((
4 | (((((((((( ((((((((((
5 | (((((((( ((((#%%%
6 | ((((((/ (((((( (#%%%#%%%
7 | (((((( ((((((((((((%%%%%%%%%#%%%
8 | ((((( (((((((((%%.#% %%,.%%#%%%
9 | ((((( ((((%%%%%% %%# % %(#%%#%%%
10 | (((((( %%%%%%%%%%%%/#%#%#%#%#%#%
11 | ((((((( %%%%%% %%%%#%%%
12 | (((%%%%% %%%%%%%%
13 | #%%%%%%%%% %%%%%%%%%(
14 | %%%%%%%%%%%/ /%%%%%%%%%%%
15 |
16 |
17 | Cano (kay-no) is a VIM inspired modal-based text editor
18 | written in C using the ncurses library.
19 |
20 | _______________________________________________________________________________
21 | Usage |
22 | |
23 | Opening files: |
24 | $ cano |
25 | |
26 | Getting help: |
27 | $ cano --help |
28 | |
29 | Help-pages: |
30 | - general |
31 | - keys |
32 | - cmds |
33 | |
34 | The command: |
35 | $ cano --help |
36 | is a shortcut to: |
37 | $ cano --help general |
38 | |
39 | -------------------------------------------------------------------------------
40 |
41 | _______________________________________________________________________________
42 | Modes |
43 | |
44 | 1. Normal - For motions and deletion |
45 | 2. Insert - For inserting text |
46 | 3. Visual - For selecting text and performing actions on them |
47 | 4. Search - For searching of text in the current buffer |
48 | 5. Command - For executing commands |
49 | |
50 | -------------------------------------------------------------------------------
51 |
52 | _______________________________________________________________________________
53 | Visual |
54 | |
55 | Visual mode works the same as Normal mode, |
56 | except it works on the entire selection, instead of character by character. |
57 | |
58 | Keybind | Action |
59 | ------------------------------------------------ |
60 | > | Indent current selection |
61 | ------------------------------------------------ |
62 | < | Unindent current selection |
63 | |
64 | -------------------------------------------------------------------------------
65 |
66 | _______________________________________________________________________________
67 | Search |
68 | |
69 | Search mode takes a string and finds it in the file. |
70 | if prepended with 's/' then it will replace the first substring |
71 | with the second. |
72 | |
73 | This example command: |
74 | 's/hello/goodbye' |
75 | |
76 | Will replace hello with goodbye. |
77 | |
78 | -------------------------------------------------------------------------------
79 |
80 | _______________________________________________________________________________
81 | Configuration |
82 | |
83 | The config file is stored in ~/.config/cano/config.cano by default, |
84 | or can be set at runtime like so: |
85 | $ cano --config |
86 | |
87 | The format of the file is the same as commands, |
88 | it is line separated, which is important. Example: |
89 | |
90 | set-var sytnax 1 |
91 | set-var indent 2 |
92 | |
93 | There is a secondary config file, custom syntax highlighting. It is stored in |
94 | the same folder as the regular config, but uses a different naming format. |
95 | An example is ~/.config/cano/c.cyntax (spelled cyntax, with a c). |
96 | |
97 | The c can be replaced with whatever the file extension of your language is, |
98 | such as go.cyntax for Golang. |
99 | |
100 | Here is an example of a cyntax file: |
101 | k,170,68,68, |
102 | auto,struct,break,else,switch,case, |
103 | enum,register,typedef,extern,return, |
104 | union,continue,for,signed,void,do, |
105 | if,static,while,default,goto,sizeof, |
106 | volatile,const,unsigned. |
107 | t,255,165,0, |
108 | double,size_t,int, |
109 | long,char,float,short. |
110 | w,128,160,255. |
111 | |
112 | There's a bit to unpack here, basically the single characters represent |
113 | the type of the keywords: |
114 | k - Keyword |
115 | t - Type |
116 | w - Word |
117 | The type is then followed by RGB values, all comma separated without spaces. |
118 | After the RGB values, there is the actual keywords. End each type with a |
119 | dot '.' as seen above, to indicate to Cano that the list is finished. |
120 | |
121 | The words are meant to be left blank, as it will highlight any words not |
122 | found in the keywords above with the chosen RGB color. If you wish to only |
123 | set the color, you can provide no keywords to any, and it will fill in the |
124 | keywords with C keywords by default. |
125 | |
126 | Config variables: |
127 | |
128 | Variable | Purpose |
129 | ------------------------------------------------------- |
130 | relative | toggle relative line numbers |
131 | ------------------------------------------------------- |
132 | auto-indent | toggle auto indentation on-off |
133 | ------------------------------------------------------- |
134 | syntax | toggle syntax highlighting on-off |
135 | ------------------------------------------------------- |
136 | indent | set indent |
137 | ------------------------------------------------------- |
138 | undo-size | size of undo history |
139 | |
140 | -------------------------------------------------------------------------------
141 |
142 |
143 |
--------------------------------------------------------------------------------
/docs/help/keys:
--------------------------------------------------------------------------------
1 | Cano keybinds:
2 |
3 | Mode | Keybind | Action
4 | ------------------------------------------------------------------------------
5 | Any | Ctrl+Q | Quit
6 | ------------------------------------------------------------------------------
7 | Any | Esc | Enter normal mode
8 | ------------------------------------------------------------------------------
9 | Normal | h | Move cursor left
10 | ------------------------------------------------------------------------------
11 | Normal | j | Move cursor down
12 | ------------------------------------------------------------------------------
13 | Normal | k | Move cursor up
14 | ------------------------------------------------------------------------------
15 | Normal | l | Move cursor right
16 | ------------------------------------------------------------------------------
17 | Normal | x | Delete character
18 | ------------------------------------------------------------------------------
19 | Normal | g | Go to first line
20 | ------------------------------------------------------------------------------
21 | Normal | G | Go to last line
22 | ------------------------------------------------------------------------------
23 | Normal | 0 | Go to start of line
24 | ------------------------------------------------------------------------------
25 | Normal | $ | Go to end of line
26 | ------------------------------------------------------------------------------
27 | Normal | w | Go to next word
28 | ------------------------------------------------------------------------------
29 | Normal | b | Go to last word
30 | ------------------------------------------------------------------------------
31 | Normal | e | Go to end of next word
32 | ------------------------------------------------------------------------------
33 | Normal | o | Create line below current
34 | ------------------------------------------------------------------------------
35 | Normal | O | Create line above current
36 | ------------------------------------------------------------------------------
37 | Normal | Ctrl+o | 'o' without changing mode
38 | ------------------------------------------------------------------------------
39 | Normal | % | Go to curresponding brace
40 | ------------------------------------------------------------------------------
41 | Normal | i | Enter insert mode
42 | ------------------------------------------------------------------------------
43 | Normal | I | Go to beginning of line
44 | ------------------------------------------------------------------------------
45 | Normal | a | Insert mode on next char
46 | ------------------------------------------------------------------------------
47 | Normal | A | Insert mode at end of line
48 | ------------------------------------------------------------------------------
49 | Normal | v | Enter visual mode
50 | ------------------------------------------------------------------------------
51 | Normal | V | Enter visual mode by line
52 | ------------------------------------------------------------------------------
53 | Normal | u | Undo
54 | ------------------------------------------------------------------------------
55 | Normal | U | Redo
56 | ------------------------------------------------------------------------------
57 | Normal | / | Enter search mode
58 | ------------------------------------------------------------------------------
59 | Normal | n | Jump to next search
60 | ------------------------------------------------------------------------------
61 | Normal | Ctrl+s | Save and exit
62 | ------------------------------------------------------------------------------
63 | Normal | r | current char --> next input
64 | ------------------------------------------------------------------------------
65 | Normal | (n)+motion | Repeat next motion n times
66 | ------------------------------------------------------------------------------
67 | Normal | (d)+motion | Del chars of next motion n times
68 | ------------------------------------------------------------------------------
69 | Normal | Ctrl+n | Open file explorer
70 |
71 |
72 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "nixpkgs": {
4 | "locked": {
5 | "lastModified": 1720338227,
6 | "narHash": "sha256-PlL/yXNX/C87S2N8lF/HfOJhHForf/l7E6lVWZ4jMrw=",
7 | "owner": "nixos",
8 | "repo": "nixpkgs",
9 | "rev": "b60793b86201040d9dee019a05089a9150d08b5b",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "nixos",
14 | "ref": "nixpkgs-unstable",
15 | "repo": "nixpkgs",
16 | "type": "github"
17 | }
18 | },
19 | "root": {
20 | "inputs": {
21 | "nixpkgs": "nixpkgs"
22 | }
23 | }
24 | },
25 | "root": "root",
26 | "version": 7
27 | }
28 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
3 |
4 | outputs = {
5 | self,
6 | nixpkgs,
7 | }: let
8 | system = "x86_64-linux";
9 | pkgs = nixpkgs.legacyPackages.${system};
10 |
11 | hardeningDisable = ["format" "fortify"];
12 | in {
13 | devShells.${system}.default = pkgs.mkShell {
14 | inherit hardeningDisable;
15 |
16 | inputsFrom = [self.packages.${system}.cano];
17 | packages = with pkgs; [gcc bear valgrind];
18 | env.HELP_DIR = "docs/help";
19 | };
20 |
21 | formatter.${system} = pkgs.alejandra;
22 |
23 | packages.${system} = {
24 | default = self.packages.${system}.cano;
25 | cano = pkgs.stdenv.mkDerivation {
26 | inherit hardeningDisable;
27 |
28 | name = "cano";
29 | src = ./.;
30 |
31 | buildInputs = [pkgs.ncurses];
32 |
33 | makeFlags = "PREFIX=${placeholder "out"}";
34 |
35 | meta = {
36 | description = "Text Editor Written In C Using ncurses";
37 | license = pkgs.lib.licenses.asl20;
38 | maintainers = with pkgs.lib.maintainers; [ sigmanificient ];
39 | platforms = pkgs.lib.platforms.linux;
40 | mainProgram = "cano";
41 | };
42 | };
43 | };
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/src/.dirstamp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cano-Projects/Cano/e8a6314511101d86628e5346436d25e61b2f1cbe/src/.dirstamp
--------------------------------------------------------------------------------
/src/buffer.c:
--------------------------------------------------------------------------------
1 | #include "buffer.h"
2 | #include "main.h"
3 |
4 | // updates the rows to be consistent with the buffer data
5 | void buffer_calculate_rows(Buffer *buffer) {
6 | buffer->rows.count = 0;
7 | size_t start = 0;
8 | for(size_t i = 0; i < buffer->data.count; i++) {
9 | if(buffer->data.data[i] == '\n') {
10 | DA_APPEND(&buffer->rows, ((Row){.start = start, .end = i}));
11 | start = i + 1;
12 | }
13 | }
14 |
15 | DA_APPEND(&buffer->rows, ((Row){.start = start, .end = buffer->data.count}));
16 | }
17 |
18 | void buffer_insert_char(State *state, Buffer *buffer, char ch) {
19 | ASSERT(buffer != NULL, "buffer exists");
20 | ASSERT(state != NULL, "state exists");
21 | // constrain the cursor
22 | if(buffer->cursor > buffer->data.count) buffer->cursor = buffer->data.count;
23 | DA_APPEND(&buffer->data, ch);
24 | // shift the data over one
25 | memmove(&buffer->data.data[buffer->cursor + 1], &buffer->data.data[buffer->cursor], buffer->data.count - 1 - buffer->cursor);
26 | buffer->data.data[buffer->cursor++] = ch;
27 | // shift the end of the current undo
28 | state->cur_undo.end = buffer->cursor;
29 | buffer_calculate_rows(buffer);
30 | }
31 |
32 | void buffer_delete_char(Buffer *buffer, State *state) {
33 | (void)state;
34 | if(buffer->cursor < buffer->data.count) {
35 | // shift data left by 1
36 | memmove(&buffer->data.data[buffer->cursor], &buffer->data.data[buffer->cursor+1], buffer->data.count - buffer->cursor - 1);
37 | buffer->data.count--;
38 | buffer_calculate_rows(buffer);
39 | }
40 | }
41 |
42 | void buffer_delete_ch(Buffer *buffer, State *state) {
43 | CREATE_UNDO(INSERT_CHARS, buffer->cursor);
44 | reset_command(state->clipboard.str, &state->clipboard.len);
45 | buffer_yank_char(buffer, state);
46 | buffer_delete_char(buffer, state);
47 | state->cur_undo.end = buffer->cursor;
48 | undo_push(state, &state->undo_stack, state->cur_undo);
49 | }
50 |
51 | void buffer_delete_row(Buffer *buffer, State *state) {
52 | size_t repeat = state->repeating.repeating_count;
53 | if(repeat == 0) repeat = 1;
54 | if(repeat > buffer->rows.count - buffer_get_row(buffer)) repeat = buffer->rows.count - buffer_get_row(buffer);
55 | for(size_t i = 0; i < repeat; i++) {
56 | reset_command(state->clipboard.str, &state->clipboard.len);
57 | buffer_yank_line(buffer, state, 0);
58 | size_t row = buffer_get_row(buffer);
59 | Row cur = buffer->rows.data[row];
60 | size_t offset = buffer->cursor - cur.start;
61 | CREATE_UNDO(INSERT_CHARS, cur.start);
62 | if(row == 0) {
63 | size_t end = (buffer->rows.count > 1) ? cur.end+1 : cur.end;
64 | buffer_delete_selection(buffer, state, cur.start, end);
65 | } else {
66 | state->cur_undo.start -= 1;
67 | buffer_delete_selection(buffer, state, cur.start-1, cur.end);
68 | }
69 | undo_push(state, &state->undo_stack, state->cur_undo);
70 | buffer_calculate_rows(buffer);
71 | if(row >= buffer->rows.count) row = buffer->rows.count-1;
72 | cur = buffer->rows.data[row];
73 | size_t pos = cur.start + offset;
74 | if(pos > cur.end) pos = cur.end;
75 | buffer->cursor = pos;
76 | }
77 | state->repeating.repeating_count = 0;
78 | }
79 |
80 | void buffer_replace_ch(Buffer *buffer, State *state) {
81 | CREATE_UNDO(REPLACE_CHAR, buffer->cursor);
82 | DA_APPEND(&state->cur_undo.data, buffer->data.data[buffer->cursor]);
83 | state->ch = frontend_getch(state->main_win);
84 | buffer->data.data[buffer->cursor] = state->ch;
85 | undo_push(state, &state->undo_stack, state->cur_undo);
86 | }
87 |
88 | // get current row information based on cursor
89 | size_t buffer_get_row(const Buffer *buffer) {
90 | ASSERT(buffer->cursor <= buffer->data.count, "cursor: %zu", buffer->cursor);
91 | ASSERT(buffer->rows.count >= 1, "there must be at least one line");
92 | for(size_t i = 0; i < buffer->rows.count; i++) {
93 | if(buffer->rows.data[i].start <= buffer->cursor && buffer->cursor <= buffer->rows.data[i].end) {
94 | return i;
95 | }
96 | }
97 | return 0;
98 | }
99 |
100 | // get current row information based on index
101 | size_t index_get_row(Buffer *buffer, size_t index) {
102 | ASSERT(index <= buffer->data.count, "index: %zu", index);
103 | ASSERT(buffer->rows.count >= 1, "there must be at least one line");
104 | for(size_t i = 0; i < buffer->rows.count; i++) {
105 | if(buffer->rows.data[i].start <= index && index <= buffer->rows.data[i].end) {
106 | return i;
107 | }
108 | }
109 | return 0;
110 | }
111 |
112 | void buffer_yank_line(Buffer *buffer, State *state, size_t offset) {
113 | size_t row = buffer_get_row(buffer);
114 | // check boundaries
115 | if(offset > index_get_row(buffer, buffer->data.count)) return;
116 | Row cur = buffer->rows.data[row+offset];
117 | int line_offset = 0;
118 | size_t initial_s = state->clipboard.len;
119 | state->clipboard.len = cur.end - cur.start + 1; // account for new line
120 | // resize the clipboard as necessary
121 | state->clipboard.str = realloc(state->clipboard.str,
122 | initial_s+state->clipboard.len*sizeof(char));
123 | if(row > 0) line_offset = -1;
124 | else {
125 | state->clipboard.len--;
126 | initial_s++;
127 | state->clipboard.str[0] = '\n';
128 | }
129 | ASSERT(state->clipboard.str != NULL, "clipboard was null");
130 | strncpy(state->clipboard.str+initial_s, buffer->data.data+cur.start+line_offset, state->clipboard.len);
131 | state->clipboard.len += initial_s;
132 | }
133 |
134 | void buffer_yank_char(Buffer *buffer, State *state) {
135 | reset_command(state->clipboard.str, &state->clipboard.len);
136 | state->clipboard.len = 2;
137 | // resize the clipboard as necessary
138 | state->clipboard.str = realloc(state->clipboard.str,
139 | state->clipboard.len*sizeof(char));
140 | ASSERT(state->clipboard.str != NULL, "clipboard was null");
141 | strncpy(state->clipboard.str, buffer->data.data+buffer->cursor, state->clipboard.len);
142 | }
143 |
144 | void buffer_yank_selection(Buffer *buffer, State *state, size_t start, size_t end) {
145 | state->clipboard.len = end-start+1;
146 | state->clipboard.str = realloc(state->clipboard.str,
147 | state->clipboard.len*sizeof(char)+1);
148 | ASSERT(state->clipboard.str != NULL, "clipboard was null %zu", state->clipboard.len);
149 | strncpy(state->clipboard.str, buffer->data.data+start, state->clipboard.len);
150 | }
151 |
152 | void buffer_delete_selection(Buffer *buffer, State *state, size_t start, size_t end) {
153 | buffer_yank_selection(buffer, state, start, end);
154 | size_t size = end-start;
155 | if(size >= buffer->data.count) size = buffer->data.count;
156 | buffer->cursor = start;
157 |
158 | // constrain size to be within the buffer
159 | if(buffer->cursor+size > buffer->data.count) return;
160 | //ASSERT(buffer->cursor+size <= buffer->data.count, "size is too great %zu", buffer->cursor+size);
161 |
162 | // resize undo as necessary
163 | if(state->cur_undo.data.capacity < size) {
164 | state->cur_undo.data.capacity = size;
165 | state->cur_undo.data.data = realloc(state->cur_undo.data.data, sizeof(char)*size);
166 | ASSERT(state->cur_undo.data.data != NULL, "could not alloc");
167 | }
168 | strncpy(state->cur_undo.data.data, &buffer->data.data[buffer->cursor], size);
169 | state->cur_undo.data.count = size;
170 |
171 | memmove(&buffer->data.data[buffer->cursor],
172 | &buffer->data.data[buffer->cursor+size],
173 | buffer->data.count - (end));
174 | buffer->data.count -= size;
175 | buffer_calculate_rows(buffer);
176 | }
177 |
178 | void buffer_insert_selection(Buffer *buffer, Data *selection, size_t start) {
179 | buffer->cursor = start;
180 |
181 | size_t size = selection->count;
182 |
183 | // resize buffer as necessary
184 | if(buffer->data.count + size >= buffer->data.capacity) {
185 | buffer->data.capacity += size*2;
186 | buffer->data.data = realloc(buffer->data.data, sizeof(char)*buffer->data.capacity+1);
187 | ASSERT(buffer->data.data != NULL, "could not alloc");
188 | }
189 | memmove(&buffer->data.data[buffer->cursor+size],
190 | &buffer->data.data[buffer->cursor],
191 | buffer->data.count - buffer->cursor);
192 | strncpy(&buffer->data.data[buffer->cursor], selection->data, size);
193 |
194 | buffer->data.count += size;
195 | buffer_calculate_rows(buffer);
196 | }
197 |
198 | void buffer_move_up(Buffer *buffer) {
199 | size_t row = buffer_get_row(buffer);
200 | size_t col = buffer->cursor - buffer->rows.data[row].start;
201 | if(row > 0) {
202 | // set to previous row on current column
203 | buffer->cursor = buffer->rows.data[row-1].start + col;
204 | // clamp the cursor position to the end of the row
205 | if(buffer->cursor > buffer->rows.data[row-1].end) {
206 | buffer->cursor = buffer->rows.data[row-1].end;
207 | }
208 | }
209 | }
210 |
211 | void buffer_move_down(Buffer *buffer) {
212 | size_t row = buffer_get_row(buffer);
213 | size_t col = buffer->cursor - buffer->rows.data[row].start;
214 | if(row < buffer->rows.count - 1) {
215 | // set to next row on current column
216 | buffer->cursor = buffer->rows.data[row+1].start + col;
217 | // clamp the cursor position to the end of the row
218 | if(buffer->cursor > buffer->rows.data[row+1].end) {
219 | buffer->cursor = buffer->rows.data[row+1].end;
220 | }
221 | }
222 | }
223 |
224 | void buffer_move_right(Buffer *buffer) {
225 | if(buffer->cursor < buffer->data.count) buffer->cursor++;
226 | }
227 |
228 | void buffer_move_left(Buffer *buffer) {
229 | if(buffer->cursor > 0) buffer->cursor--;
230 | }
231 |
232 | int skip_to_char(Buffer *buffer, int cur_pos, int direction, char c) {
233 | // check if currently on c
234 | if(buffer->data.data[cur_pos] == c) {
235 | // increment by the direciton, can be positive or negative
236 | cur_pos += direction;
237 | // search for the next instance of c
238 | while(cur_pos > 0 && cur_pos <= (int)buffer->data.count && buffer->data.data[cur_pos] != c) {
239 | if(cur_pos > 1 && cur_pos < (int)buffer->data.count && buffer->data.data[cur_pos] == '\\') {
240 | cur_pos += direction;
241 | }
242 | cur_pos += direction;
243 | }
244 | }
245 | return cur_pos;
246 | }
247 |
248 | void buffer_next_brace(Buffer *buffer) {
249 | int cur_pos = buffer->cursor;
250 | Brace initial_brace = find_opposite_brace(buffer->data.data[cur_pos]);
251 | size_t brace_stack = 0;
252 | // if not currently on a brace, exit
253 | if(initial_brace.brace == '0') return;
254 | // check if going forward or backward
255 | int direction = (initial_brace.closing) ? -1 : 1;
256 | while(cur_pos >= 0 && cur_pos <= (int)buffer->data.count) {
257 | cur_pos += direction;
258 | // skip over quotes if necessary to avoid strings containing braces
259 | cur_pos = skip_to_char(buffer, cur_pos, direction, '"');
260 | cur_pos = skip_to_char(buffer, cur_pos, direction, '\'');
261 | Brace cur_brace = find_opposite_brace(buffer->data.data[cur_pos]);
262 | // if not currently on a brace, continue
263 | if(cur_brace.brace == '0') continue;
264 | if((cur_brace.closing && direction == -1) || (!cur_brace.closing && direction == 1)) {
265 | brace_stack++;
266 | } else {
267 | if(brace_stack-- == 0 && cur_brace.brace == find_opposite_brace(initial_brace.brace).brace) {
268 | // set cursor to brace if found
269 | buffer->cursor = cur_pos;
270 | break;
271 | }
272 | }
273 | }
274 | }
275 |
276 | int isword(char ch) {
277 | if(isalnum(ch) || ch == '_') return 1;
278 | return 0;
279 | }
280 |
281 | void buffer_create_indent(Buffer *buffer, State *state, size_t indent_count) {
282 | // if indent is 0, then use tabs, otherwise spaces
283 | if(state->config.indent > 0) {
284 | for(size_t i = 0; i < state->config.indent*indent_count; i++) {
285 | buffer_insert_char(state, buffer, ' ');
286 | }
287 | } else {
288 | for(size_t i = 0; i < indent_count; i++) {
289 | buffer_insert_char(state, buffer, '\t');
290 | }
291 | }
292 | }
293 |
294 | void buffer_delete_indent(State *state, size_t indent_count) {
295 | Buffer *buffer = state->buffer;
296 | for(size_t i = 0; i < indent_count; i++) {
297 | if(isspace(buffer->data.data[buffer->cursor])) {
298 | buffer_delete_char(buffer, state);
299 | buffer_calculate_rows(buffer);
300 | }
301 | }
302 | }
303 |
304 | // insert newline, then indent
305 | void buffer_newline_indent(Buffer *buffer, State *state) {
306 | buffer_insert_char(state, buffer, '\n');
307 | buffer_create_indent(buffer, state, state->num_of_braces);
308 | }
309 |
310 | void buffer_brace_indent(State *state, Brace brace) {
311 | Buffer *buffer = state->buffer;
312 | if(brace.brace != '0' && brace.closing) {
313 | buffer_insert_char(state, buffer, '\n');
314 | if(state->num_of_braces == 0) state->num_of_braces = 1;
315 | if(state->config.indent > 0) {
316 | for(size_t i = 0; i < state->config.indent*(state->num_of_braces-1); i++) {
317 | buffer_insert_char(state, buffer, ' ');
318 | }
319 | handle_move_left(state, state->config.indent*(state->num_of_braces-1));
320 | } else {
321 | for(size_t i = 0; i < state->num_of_braces-1; i++) {
322 | buffer_insert_char(state, buffer, '\t');
323 | }
324 | handle_move_left(state, state->num_of_braces-1);
325 | }
326 | handle_move_left(state, 1);
327 | } else {
328 | undo_push(state, &state->undo_stack, state->cur_undo);
329 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
330 | }
331 | }
332 |
333 |
334 | State init_state(void) {
335 | State state = {0};
336 | state.config = (Config){0};
337 | state.config.relative_nums = 1;
338 | state.config.auto_indent = 1;
339 | state.config.syntax = 1;
340 | state.config.indent = 0;
341 | state.config.undo_size = 16;
342 | state.config.lang = " ";
343 | // Control variables
344 | state.config.QUIT = 0;
345 | state.config.mode = NORMAL;
346 | // Colors
347 | state.config.background_color = -1; // -1 for terminal background color.
348 | state.config.leaders[0] = ' ';
349 | state.config.leaders[1] = 'r';
350 | state.config.leaders[2] = 'd';
351 | state.config.leaders[3] = 'y';
352 | state.config.key_maps = (Maps){0};
353 | state.config.vars[0] = (Config_Vars){{"syntax", sizeof("syntax")-1}, &state.config.syntax};
354 | state.config.vars[1] = (Config_Vars){{"indent", sizeof("indent")-1}, &state.config.indent};
355 | state.config.vars[2] = (Config_Vars){{"auto-indent", sizeof("auto-indent")-1}, &state.config.auto_indent};
356 | state.config.vars[3] = (Config_Vars){{"undo-size", sizeof("undo-size")-1}, &state.config.undo_size};
357 | state.config.vars[4] = (Config_Vars){{"relative", sizeof("relative")-1}, &state.config.relative_nums};
358 | return state;
359 | }
360 |
--------------------------------------------------------------------------------
/src/buffer.h:
--------------------------------------------------------------------------------
1 | #ifndef BUFFER_H
2 | #define BUFFER_H
3 |
4 | #include "defs.h"
5 |
6 | void buffer_calculate_rows(Buffer *buffer);
7 | void buffer_insert_char(State *state, Buffer *buffer, char ch);
8 | void buffer_delete_char(Buffer *buffer, State *state);
9 | void buffer_delete_ch(Buffer *buffer, State *state);
10 | void buffer_replace_ch(Buffer *buffer, State *state);
11 | void buffer_delete_row(Buffer *buffer, State *state);
12 | size_t buffer_get_row(const Buffer *buffer);
13 | size_t index_get_row(Buffer *buffer, size_t index);
14 | void buffer_yank_line(Buffer *buffer, State *state, size_t offset);
15 | void buffer_yank_char(Buffer *buffer, State *state);
16 | void buffer_yank_selection(Buffer *buffer, State *state, size_t start, size_t end);
17 | void buffer_delete_selection(Buffer *buffer, State *state, size_t start, size_t end);
18 | void buffer_insert_selection(Buffer *buffer, Data *selection, size_t start);
19 | void buffer_move_up(Buffer *buffer);
20 | void buffer_move_down(Buffer *buffer);
21 | void buffer_move_right(Buffer *buffer);
22 | void buffer_move_left(Buffer *buffer);
23 | int skip_to_char(Buffer *buffer, int cur_pos, int direction, char c);
24 | void buffer_next_brace(Buffer *buffer);
25 | int isword(char ch);
26 | void buffer_create_indent(Buffer *buffer, State *state, size_t indent_count);
27 | void buffer_delete_indent(State *state, size_t indent_count);
28 | void buffer_newline_indent(Buffer *buffer, State *state);
29 | void buffer_brace_indent(State *state, Brace brace);
30 | State init_state(void);
31 |
32 | #endif
33 |
--------------------------------------------------------------------------------
/src/cgetopt.c:
--------------------------------------------------------------------------------
1 | // Reimplementation of GNU getopt by proh14 and cobbcoding
2 |
3 | #include "./cgetopt.h"
4 | #include
5 | #include
6 | #include
7 |
8 | char *optarg = NULL;
9 | int optind = 1, opterr = 0, optopt = '\0';
10 | static int end_of_args = 0;
11 |
12 | #define EXCHANGE(coropt) \
13 | do { \
14 | int i = (coropt); \
15 | int j = (coropt)-1; \
16 | while (j >= 0 && argv[j][0] != '-') { \
17 | getopt_exchange(argv, i, j); \
18 | i--; \
19 | j--; \
20 | } \
21 | } while (0)
22 |
23 | static void getopt_printerr(const char *msg) {
24 | if (opterr) {
25 | fprintf(stderr, "%s", msg);
26 | }
27 | }
28 |
29 | static int getopt_in(char d, const char *str) {
30 | int i = 0;
31 | while (str[i] != '\0') {
32 | if (d == str[i] && str[i] != ':') {
33 | return i;
34 | }
35 | i++;
36 | }
37 | return -1;
38 | }
39 |
40 | static void getopt_exchange(char *argv[], int i, int j) {
41 | char *tmp = argv[i];
42 | argv[i] = argv[j];
43 | argv[j] = tmp;
44 | }
45 |
46 | int cgetopt(int argc, char *argv[], const char *optstring) {
47 | int c;
48 | static char *nextchar = NULL;
49 | static int coropt = 1;
50 | if (!coropt) {
51 | coropt = 1;
52 | }
53 |
54 | if (coropt >= argc || argv[coropt] == NULL) {
55 | return -1;
56 | }
57 |
58 | while (argv[coropt] && argv[coropt][0] != '-') {
59 | coropt++;
60 | }
61 |
62 | if (coropt >= argc) {
63 | return -1;
64 | }
65 |
66 | nextchar = &argv[coropt][1];
67 |
68 | if (strcmp(argv[coropt], "--") == 0) {
69 | EXCHANGE(coropt);
70 | end_of_args = 1;
71 | optind++;
72 | return -1;
73 | }
74 |
75 | int idx = idx = getopt_in(*nextchar, optstring);
76 | if (!(idx >= 0)) {
77 | getopt_printerr("invalid option\n");
78 | optopt = (unsigned)*nextchar;
79 | if (*(nextchar + 1) == '\0') {
80 | nextchar = NULL;
81 | optind++;
82 | }
83 | optind++;
84 | c = '?';
85 | goto exit;
86 | }
87 |
88 | c = (unsigned)*nextchar++;
89 | if (*nextchar == '\0') {
90 | coropt++;
91 | optind++;
92 | }
93 |
94 | if (optstring[idx + 1] != ':') {
95 | coropt--;
96 | goto exit;
97 | }
98 | EXCHANGE(coropt - 1);
99 |
100 | if (nextchar != NULL && *nextchar != '\0') {
101 | coropt++;
102 | }
103 |
104 | if (coropt >= argc || argv[coropt][0] == '-') {
105 | getopt_printerr("option requires an argument\n");
106 | optopt = (unsigned)*nextchar;
107 | c = '?';
108 | optind++;
109 | goto exit;
110 | }
111 |
112 | optarg = argv[coropt];
113 | optind++;
114 |
115 | exit: { EXCHANGE(coropt); }
116 | return c;
117 | }
118 |
119 | int cgetopt_long(int argc, char *argv[], const char *optstring,
120 | const struct option *longopts, int *longindex) {
121 | int c;
122 | static char *nextchar = NULL;
123 | static int coropt = 1;
124 | if (!coropt) {
125 | coropt = 1;
126 | }
127 |
128 | if (coropt >= argc || argv[coropt] == NULL) {
129 | return -1;
130 | }
131 |
132 | while (argv[coropt] && argv[coropt][0] != '-') {
133 | coropt++;
134 | }
135 |
136 | if (nextchar == NULL || *nextchar == '\0') {
137 | if (coropt >= argc) {
138 | return -1;
139 | }
140 | nextchar = &argv[coropt][1];
141 | }
142 |
143 | if (*nextchar == '-') {
144 | nextchar++;
145 | const struct option *curlong = longopts;
146 | while (curlong->name != NULL) {
147 | size_t name_len = strlen(curlong->name);
148 | if (strncmp(curlong->name, nextchar, name_len) == 0) {
149 | if (longindex != NULL)
150 | *longindex = curlong - longopts;
151 | optind++;
152 | if (longopts->flag) {
153 | *curlong->flag = curlong->val;
154 | c = 0;
155 | } else {
156 | c = curlong->val;
157 | }
158 | if (curlong->has_arg == optional_argument &&
159 | ((coropt + 1) >= argc || argv[(coropt + 1)][0] == '-'))
160 | goto exit;
161 | if (curlong->has_arg) {
162 | if (*(nextchar + name_len) != '=') {
163 | nextchar = NULL;
164 | EXCHANGE(coropt);
165 | coropt++;
166 | if (coropt >= argc || argv[coropt][0] == '-') {
167 | getopt_printerr("option requires an argument\n");
168 | optopt = curlong->val;
169 | c = '?';
170 | goto exit;
171 | }
172 | optarg = argv[coropt];
173 | optind++;
174 | } else {
175 | optarg = nextchar + (name_len + 1);
176 | }
177 | }
178 | nextchar = NULL;
179 | goto exit;
180 | }
181 | curlong++;
182 | }
183 | optind++;
184 | optopt = longopts->val;
185 | nextchar = NULL;
186 | getopt_printerr("invalid option\n");
187 | c = '?';
188 | goto exit;
189 | }
190 |
191 | int idx = getopt_in(*nextchar, optstring);
192 | if (!(idx >= 0)) {
193 | getopt_printerr("invalid option\n");
194 | optopt = (unsigned)*nextchar;
195 | if (*(nextchar + 1) == '\0') {
196 | nextchar = NULL;
197 | optind++;
198 | }
199 | optind++;
200 | c = '?';
201 | goto exit;
202 | }
203 |
204 | c = (unsigned)*nextchar++;
205 | if (*nextchar == '\0') {
206 | coropt++;
207 | optind++;
208 | }
209 |
210 | if (optstring[idx + 1] != ':') {
211 | coropt--;
212 | goto exit;
213 | }
214 | EXCHANGE(coropt - 1);
215 |
216 | if (nextchar != NULL && *nextchar != '\0') {
217 | coropt++;
218 | }
219 |
220 | if (coropt >= argc || argv[coropt][0] == '-') {
221 | getopt_printerr("option requires an argument\n");
222 | optopt = (unsigned)*nextchar;
223 | optind++;
224 | c = '?';
225 | goto exit;
226 | }
227 |
228 | optarg = argv[coropt];
229 | optind++;
230 |
231 | exit: { EXCHANGE(coropt); }
232 | return c;
233 | }
234 |
--------------------------------------------------------------------------------
/src/cgetopt.h:
--------------------------------------------------------------------------------
1 | // Reimplementation of GNU getopt by proh14 and cobbcoding
2 | #ifndef _CGETOPT_H_
3 | #define _CGETOPT_H_
4 |
5 | #define no_argument 0
6 | #define required_argument 1
7 | #define optional_argument 2
8 |
9 | struct option {
10 | const char *name;
11 | int has_arg;
12 | int *flag;
13 | int val;
14 | };
15 |
16 | extern char *optarg;
17 | extern int optind, opterr, optopt;
18 |
19 | int cgetopt(int argc, char **argv, const char *optstring);
20 |
21 | int cgetopt_long(int argc, char *argv[], const char *optstring,
22 | const struct option *longopts, int *longindex);
23 |
24 | int cgetopt_long_only(int argc, char *argv[], const char *optstring,
25 | const struct option *longopts, int *longindex);
26 |
27 | #endif // _GETOPT_H_
28 |
--------------------------------------------------------------------------------
/src/colors.h:
--------------------------------------------------------------------------------
1 | #ifndef COLORS_H
2 | #define COLORS_H
3 |
4 | #include
5 |
6 | // allows for custom colors to be defined throughout multipe files
7 | // and share data between the two.
8 |
9 | typedef enum {
10 | YELLOW_COLOR = 1,
11 | BLUE_COLOR,
12 | GREEN_COLOR,
13 | RED_COLOR,
14 | CYAN_COLOR,
15 | MAGENTA_COLOR,
16 | } Color_Pairs;
17 |
18 | typedef struct {
19 | Color_Pairs custom_slot;
20 | int custom_id;
21 | int custom_r;
22 | int custom_g;
23 | int custom_b;
24 | } Custom_Color;
25 |
26 | typedef struct {
27 | Custom_Color *arr;
28 | size_t arr_s;
29 | } Color_Arr;
30 |
31 | #endif // COLORS_H
32 |
--------------------------------------------------------------------------------
/src/commands.c:
--------------------------------------------------------------------------------
1 | #include "commands.h"
2 | #include "tools.h"
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | static char *tt_string[TT_COUNT] = {"set_var", "set_output", "set_map", "let", "plus", "minus",
9 | "mult", "div", "echo", "w", "e", "we", "ident", "special key", "string", "config var", "int", "float"};
10 |
11 | static Ctrl_Key ctrl_keys[] = {
12 | {"", ctrl('a')},
13 | {"", ctrl('b')},
14 | {"", ctrl('c')},
15 | {"", ctrl('d')},
16 | {"", ctrl('e')},
17 | {"", ctrl('f')},
18 | {"", ctrl('g')},
19 | {"", ctrl('h')},
20 | {"", ctrl('i')},
21 | {"", ctrl('j')},
22 | {"", ctrl('k')},
23 | {"", ctrl('l')},
24 | {"", ctrl('m')},
25 | {"", ctrl('n')},
26 | {"", ctrl('o')},
27 | {"", ctrl('p')},
28 | {"", ctrl('q')},
29 | {"", ctrl('r')},
30 | {"", ctrl('s')},
31 | {"", ctrl('t')},
32 | {"", ctrl('u')},
33 | {"", ctrl('v')},
34 | {"", ctrl('w')},
35 | {"", ctrl('x')},
36 | {"", ctrl('y')},
37 | {"", ctrl('z')},
38 | };
39 | #define NUM_OF_CTRL_KEYS sizeof(ctrl_keys)/sizeof(*ctrl_keys)
40 |
41 | static String_View view_chop_left(String_View view, size_t amount) {
42 | if(view.len < amount) {
43 | view.data += view.len;
44 | view.len = 0;
45 | return view;
46 | }
47 |
48 | view.data += amount;
49 | view.len -= amount;
50 | return view;
51 | }
52 |
53 | static String_View view_chop_right(String_View view) {
54 | if(view.len > 0) {
55 | view.len -= 1;
56 | }
57 | return view;
58 | }
59 |
60 |
61 | static String_View view_string_internals(String_View view) {
62 | view = view_chop_left(view, 1);
63 | view = view_chop_right(view);
64 | return view;
65 | }
66 |
67 | Command_Type get_token_type(State *state, String_View view) {
68 | if(isdigit(*view.data)) {
69 | for(size_t i = 0; i < view.len; i++) {
70 | if(view.data[i] == ' ') {
71 | break;
72 | }
73 | if(view.data[i] == '.') {
74 | return TT_FLOAT_LIT;
75 | }
76 | }
77 | return TT_INT_LIT;
78 | } else if(*view.data == '"') {
79 | return TT_STRING;
80 | } else if(*view.data == '<') {
81 | return TT_SPECIAL_CHAR;
82 | } else if(*view.data == '+') {
83 | return TT_PLUS;
84 | } else if(*view.data == '-') {
85 | return TT_MINUS;
86 | } else if(*view.data == '*') {
87 | return TT_MULT;
88 | } else if(*view.data == '/') {
89 | return TT_DIV;
90 | } else if(view_cmp(view, LITERAL_CREATE("let"))) {
91 | return TT_LET;
92 | } else if(view_cmp(view, LITERAL_CREATE("echo"))) {
93 | return TT_ECHO;
94 | } else if(view_cmp(view, LITERAL_CREATE("set-var"))) {
95 | return TT_SET_VAR;
96 | } else if(view_cmp(view, LITERAL_CREATE("w"))) {
97 | return TT_SAVE;
98 | } else if(view_cmp(view, LITERAL_CREATE("e"))) {
99 | return TT_EXIT;
100 | } else if(view_cmp(view, LITERAL_CREATE("we"))) {
101 | return TT_SAVE_EXIT;
102 | } else if(view_cmp(view, LITERAL_CREATE("set-output"))) {
103 | return TT_SET_OUTPUT;
104 | } else if(view_cmp(view, LITERAL_CREATE("set-map"))) {
105 | return TT_SET_MAP;
106 | } else { // TODO: Add help command
107 | for(size_t i = 0; i < CONFIG_VARS; i++) {
108 | if(view_cmp(view, state->config.vars[i].label)) {
109 | return TT_CONFIG_IDENT;
110 | }
111 | }
112 | return TT_IDENT;
113 | }
114 | }
115 |
116 | Command_Token create_token(State *state, String_View command) {
117 | String_View starting = command;
118 | while(command.len > 0 && *command.data != ' ') {
119 | if(*command.data == '"') {
120 | command = view_chop_left(command, 1);
121 | while(command.len > 0 && *command.data != '"') {
122 | command = view_chop_left(command, 1);
123 | }
124 | }
125 | if(*command.data == '<') {
126 | command = view_chop_left(command, 1);
127 | while(command.len > 0 && *command.data != '>') {
128 | command = view_chop_left(command, 1);
129 | }
130 | }
131 | command = view_chop_left(command, 1);
132 | }
133 | String_View result = {
134 | .data = starting.data,
135 | .len = starting.len-command.len
136 | };
137 | Command_Token token = {.value = result};
138 | token.type = get_token_type(state, result);
139 | return token;
140 | }
141 |
142 | Command_Token *lex_command(State *state, String_View command, size_t *token_s) {
143 | size_t count = 0;
144 | for(size_t i = 0; i < command.len; i++) {
145 | if(command.data[i] == '"') {
146 | i++;
147 | while(i < command.len && command.data[i] != '"') {
148 | i++;
149 | }
150 | }
151 | if(command.data[i] == ' ') {
152 | count++;
153 | }
154 | }
155 | *token_s = count + 1;
156 | Command_Token *result = malloc(sizeof(Command_Token)*(*token_s));
157 | size_t result_s = 0;
158 | String_View starting = command;
159 | while(command.len > 0) {
160 | assert(result_s <= *token_s);
161 | size_t loc = command.data - starting.data;
162 | Command_Token token = create_token(state, command);
163 | token.location = loc;
164 | command = view_chop_left(command, token.value.len+1);
165 | view_chop_left(command, 1);
166 | result[result_s++] = token;
167 | }
168 | return result;
169 | }
170 |
171 | void print_token(Command_Token token) {
172 | printf("location: %zu, type: %d, value: "View_Print"\n", token.location, token.type, View_Arg(token.value));
173 | }
174 |
175 | int expect_token(State *state, Command_Token token, Command_Type type) {
176 | if(token.type != type) {
177 | sprintf(state->status_bar_msg, "Invalid arg, expected %s but found %s", tt_string[type], tt_string[token.type]);
178 | state->is_print_msg = 1;
179 | }
180 | return(token.type == type);
181 | }
182 |
183 | Node *create_node(Node_Type type, Node_Val value) {
184 | Node *node = malloc(sizeof(Node));
185 | node->type = type;
186 | node->value = value;
187 | node->left = NULL;
188 | node->right = NULL;
189 | return node;
190 | }
191 |
192 | Operator get_operator(Command_Token token) {
193 | switch(token.type) {
194 | case TT_PLUS:
195 | return OP_PLUS;
196 | case TT_MINUS:
197 | return OP_MINUS;
198 | case TT_MULT:
199 | return OP_MULT;
200 | case TT_DIV:
201 | return OP_DIV;
202 | default:
203 | return OP_NONE;
204 | }
205 | }
206 |
207 | int get_special_char(String_View view) {
208 | if(view_cmp(view, LITERAL_CREATE(""))) {
209 | return SPACE;
210 | } else if(view_cmp(view, LITERAL_CREATE(""))) {
211 | return ESCAPE;
212 | } else if(view_cmp(view, LITERAL_CREATE(""))) {
213 | return KEY_BACKSPACE;
214 | } else if(view_cmp(view, LITERAL_CREATE(""))) {
215 | return ENTER;
216 | } else {
217 | for(size_t i = 0; i < NUM_OF_CTRL_KEYS; i++) {
218 | if(view_cmp(view, view_create(ctrl_keys[i].name, strlen(ctrl_keys[i].name)))) {
219 | return ctrl_keys[i].value;
220 | }
221 | }
222 | return -1;
223 | }
224 | }
225 |
226 | Bin_Expr *parse_bin_expr(State *state, Command_Token *command, size_t command_s) {
227 | if(command_s == 0) return NULL;
228 | Bin_Expr *expr = calloc(1, sizeof(Bin_Expr));
229 | expr->lvalue = (Expr){view_to_int(command[0].value)};
230 | if(command_s <= 2) return expr;
231 | expr->operator = get_operator(command[1]);
232 |
233 | if(expr->operator == OP_NONE) return NULL;
234 | if(!expect_token(state, command[2], TT_INT_LIT)) return NULL;
235 |
236 | expr->rvalue = (Expr){view_to_int(command[2].value)};
237 |
238 | if(command_s > 3) {
239 | expr->right = parse_bin_expr(state, command+4, command_s-4);
240 | expr->right->operator = get_operator(command[3]);
241 | }
242 | return expr;
243 | }
244 |
245 | Node *parse_command(State *state, Command_Token *command, size_t command_s) {
246 | Node *root = NULL;
247 | Node_Val val;
248 | val.as_keyword = command[0].type;
249 | root = create_node(NODE_KEYWORD, val);
250 | switch(command[0].type) {
251 | case TT_SET_VAR:
252 | if(command_s < 3) {
253 | sprintf(state->status_bar_msg, "Not enough args");
254 | state->is_print_msg = 1;
255 | return NULL;
256 | }
257 |
258 | if(!expect_token(state, command[1], TT_CONFIG_IDENT) || !expect_token(state, command[2], TT_INT_LIT)) return NULL;
259 |
260 | for(size_t i = 0; i < CONFIG_VARS; i++) {
261 | if(view_cmp(command[1].value, state->config.vars[i].label)) {
262 | val.as_config = &state->config.vars[i];
263 | }
264 | }
265 |
266 | Node *left = create_node(NODE_CONFIG, val);
267 | root->left = left;
268 | if(command_s == 3) {
269 | int value = view_to_int(command[2].value);
270 | val.as_expr = (Expr){.value = value};
271 | Node *right = create_node(NODE_EXPR, val);
272 | root->right = right;
273 | break;
274 | } else {
275 | Bin_Expr *expr = parse_bin_expr(state, command+2, command_s-2);
276 | val.as_bin = *expr;
277 | root->right = create_node(NODE_BIN, val);
278 | }
279 | break;
280 | case TT_SET_OUTPUT:
281 | if(command_s < 2) {
282 | sprintf(state->status_bar_msg, "Not enough args");
283 | state->is_print_msg = 1;
284 | return NULL;
285 | } else if(command_s > 2) {
286 | sprintf(state->status_bar_msg, "Too many args");
287 | state->is_print_msg = 1;
288 | return NULL;
289 | }
290 | val.as_str = (Str_Literal){.value = view_string_internals(command[1].value)};
291 | root->right = create_node(NODE_STR, val);
292 | break;
293 | case TT_SET_MAP:
294 | if(command_s < 3) {
295 | sprintf(state->status_bar_msg, "Not enough args");
296 | state->is_print_msg = 1;
297 | return NULL;
298 | } else if(command_s > 3) {
299 | sprintf(state->status_bar_msg, "Not enough args");
300 | state->is_print_msg = 1;
301 | return NULL;
302 | }
303 | if(!expect_token(state, command[2], TT_STRING)) return NULL;
304 | if(command[1].type == TT_SPECIAL_CHAR) {
305 | int special = get_special_char(command[1].value);
306 | if(special == -1) {
307 | sprintf(state->status_bar_msg, "Invalid special key");
308 | state->is_print_msg = 1;
309 | return NULL;
310 | }
311 | val.as_int = special;
312 | root->left = create_node(NODE_INT, val);
313 | } else {
314 | val.as_ident = (Identifier){.name = command[1].value};
315 | root->left = create_node(NODE_IDENT, val);
316 | }
317 | val.as_str = (Str_Literal){view_string_internals(command[2].value)};
318 | root->right = create_node(NODE_STR, val);
319 | break;
320 | case TT_LET:
321 | if(command_s < 3) {
322 | sprintf(state->status_bar_msg, "Not enough args");
323 | state->is_print_msg = 1;
324 | return NULL;
325 | }
326 | if(!expect_token(state, command[1], TT_IDENT) || !expect_token(state, command[2], TT_INT_LIT)) return NULL;
327 | val.as_ident = (Identifier){.name = command[1].value};
328 | root->left = create_node(NODE_IDENT, val);
329 | if(command_s == 3) {
330 | int value = view_to_int(command[2].value);
331 | val.as_expr = (Expr){.value = value};
332 | Node *right = create_node(NODE_EXPR, val);
333 | root->right = right;
334 | break;
335 | } else {
336 | Bin_Expr *expr = parse_bin_expr(state, command+2, command_s-2);
337 | val.as_bin = *expr;
338 | root->right = create_node(NODE_BIN, val);
339 | }
340 | break;
341 | case TT_ECHO:
342 | if(command_s > 2) {
343 | sprintf(state->status_bar_msg, "Too many args");
344 | state->is_print_msg = 1;
345 | return NULL;
346 | }
347 | if(command_s < 2) {
348 | sprintf(state->status_bar_msg, "Not enough args");
349 | state->is_print_msg = 1;
350 | return NULL;
351 | }
352 |
353 | if(!expect_token(state, command[1], TT_IDENT) && !expect_token(state, command[1], TT_STRING)) return NULL;
354 | if(command[1].type == TT_STRING) {
355 | val.as_str = (Str_Literal){view_string_internals(command[1].value)};
356 | root->right = create_node(NODE_STR, val);
357 | } else {
358 | val.as_ident = (Identifier){.name = command[1].value};
359 | root->right = create_node(NODE_IDENT, val);
360 | }
361 | break;
362 | case TT_PLUS:
363 | case TT_MINUS:
364 | case TT_MULT:
365 | case TT_DIV:
366 | case TT_SAVE:
367 | case TT_EXIT:
368 | case TT_SAVE_EXIT:
369 | case TT_IDENT:
370 | case TT_STRING:
371 | case TT_CONFIG_IDENT:
372 | case TT_INT_LIT:
373 | case TT_SPECIAL_CHAR:
374 | case TT_FLOAT_LIT:
375 | break;
376 | case TT_COUNT:
377 | assert(0 && "UNREACHABLE");
378 | }
379 | return root;
380 | }
381 |
382 | int interpret_expr(Bin_Expr *expr) {
383 | int value = expr->lvalue.value;
384 | if(expr->rvalue.value != 0 && expr->operator != OP_NONE) {
385 | switch(expr->operator) {
386 | case OP_PLUS:
387 | value += expr->rvalue.value;
388 | break;
389 | case OP_MINUS:
390 | value -= expr->rvalue.value;
391 | break;
392 | case OP_MULT:
393 | value *= expr->rvalue.value;
394 | break;
395 | case OP_DIV:
396 | value /= expr->rvalue.value;
397 | break;
398 | default:
399 | assert(0 && "unreachable");
400 | }
401 | }
402 |
403 | if(expr->right != NULL) {
404 | switch(expr->right->operator) {
405 | case OP_PLUS:
406 | value += interpret_expr(expr->right);
407 | break;
408 | case OP_MINUS:
409 | value -= interpret_expr(expr->right);
410 | break;
411 | case OP_MULT:
412 | value *= interpret_expr(expr->right);
413 | break;
414 | case OP_DIV:
415 | value /= interpret_expr(expr->right);
416 | break;
417 | default:
418 | assert(0 && "unreachable");
419 | }
420 | }
421 | return value;
422 | }
423 |
424 | void interpret_command(Buffer *buffer, State *state, Node *root) {
425 | if(root == NULL) return;
426 |
427 | switch(root->type) {
428 | case NODE_EXPR:
429 | break;
430 | case NODE_BIN:
431 | break;
432 | case NODE_KEYWORD:
433 | switch(root->value.as_keyword) {
434 | case TT_SET_VAR: {
435 | Config_Vars *var = root->left->value.as_config;
436 | if(root->right->type == NODE_EXPR) {
437 | *var->val = root->right->value.as_expr.value;
438 | } else {
439 | Node *node = root->right;
440 | int value = interpret_expr(&node->value.as_bin);
441 | *var->val = value;
442 | }
443 | return;
444 | } break;
445 | case TT_SET_OUTPUT:
446 | buffer->filename = view_to_cstr(root->right->value.as_str.value);
447 | break;
448 | case TT_SET_MAP: {
449 | char *str = view_to_cstr(root->right->value.as_str.value);
450 | Map map = {.b = str, .b_s = root->right->value.as_str.value.len+1};
451 | if(root->left->type == NODE_IDENT) {
452 | map.a = root->left->value.as_ident.name.data[0];
453 | } else {
454 | map.a = root->left->value.as_int;
455 | }
456 | DA_APPEND(&state->config.key_maps, map);
457 | } break;
458 | case TT_LET: {
459 | Variable var = {0};
460 | var.name = view_to_cstr(root->left->value.as_ident.name);
461 | var.type = VAR_INT;
462 | if(root->right->type == NODE_EXPR) {
463 | var.value.as_int = root->right->value.as_expr.value;
464 | } else {
465 | Node *node = root->right;
466 | int value = interpret_expr(&node->value.as_bin);
467 | var.value.as_int = value;
468 | }
469 | DA_APPEND(&state->variables, var);
470 | } break;
471 | case TT_ECHO:
472 | state->is_print_msg = 1;
473 | if(root->right->type == NODE_STR) {
474 | String_View str = root->right->value.as_str.value;
475 | state->status_bar_msg = view_to_cstr(str);
476 | } else {
477 | for(size_t i = 0; i < state->variables.count; i++) {
478 | String_View var = view_create(state->variables.data[i].name, strlen(state->variables.data[i].name));
479 | if(view_cmp(root->right->value.as_ident.name, var)) {
480 | if(state->variables.data[i].type == VAR_INT) sprintf(state->status_bar_msg, "%d", state->variables.data[i].value.as_int);
481 | else if(state->variables.data[i].type == VAR_FLOAT) sprintf(state->status_bar_msg, "%f", state->variables.data[i].value.as_float);
482 | return;
483 | }
484 | }
485 | }
486 | break;
487 | case TT_SAVE:
488 | handle_save(buffer);
489 | break;
490 | case TT_EXIT:
491 | state->config.QUIT = 1;
492 | break;
493 | case TT_SAVE_EXIT:
494 | handle_save(buffer);
495 | state->config.QUIT = 1;
496 | break;
497 | default:
498 | return;
499 | }
500 | break;
501 | case NODE_STR:
502 | case NODE_IDENT:
503 | case NODE_INT:
504 | case NODE_CONFIG:
505 | break;
506 | }
507 |
508 | interpret_command(buffer, state, root->left);
509 | interpret_command(buffer, state, root->right);
510 | }
511 |
512 | void print_tree(Node *node, size_t depth) {
513 | if(node == NULL) return;
514 | if(node->right != NULL) WRITE_LOG("NODE->RIGHT");
515 | if(node->left != NULL) WRITE_LOG("NODE->LEFT");
516 | switch(node->type) {
517 | case NODE_EXPR:
518 | WRITE_LOG("EXPR");
519 | break;
520 | case NODE_BIN:
521 | WRITE_LOG("BIN");
522 | break;
523 | case NODE_KEYWORD:
524 | WRITE_LOG("KEYWORD");
525 | break;
526 | case NODE_STR:
527 | WRITE_LOG("STR");
528 | break;
529 | case NODE_IDENT:
530 | WRITE_LOG("IDENT");
531 | break;
532 | case NODE_CONFIG:
533 | WRITE_LOG("CONFIG");
534 | break;
535 | case NODE_INT:
536 | WRITE_LOG("INT");
537 | break;
538 | }
539 | print_tree(node->left, depth+1);
540 | print_tree(node->right, depth+1);
541 | }
542 |
543 | int execute_command(Buffer *buffer, State *state, Command_Token *command, size_t command_s) {
544 | ASSERT(command_s > 0, "Invalid command size");
545 | Node *root = parse_command(state, command, command_s);
546 | if(root == NULL) return 1;
547 | print_tree(root, 0);
548 | interpret_command(buffer, state, root);
549 | return 0;
550 | }
551 |
--------------------------------------------------------------------------------
/src/commands.h:
--------------------------------------------------------------------------------
1 | #ifndef COMMANDS_H
2 | #define COMMANDS_H
3 |
4 | #include "frontend.h"
5 | #include "defs.h"
6 |
7 | typedef enum {
8 | TT_SET_VAR,
9 | TT_SET_OUTPUT,
10 | TT_SET_MAP,
11 | TT_LET,
12 | TT_PLUS,
13 | TT_MINUS,
14 | TT_MULT,
15 | TT_DIV,
16 | TT_ECHO,
17 | TT_SAVE,
18 | TT_EXIT,
19 | TT_SAVE_EXIT,
20 | TT_IDENT,
21 | TT_SPECIAL_CHAR,
22 | TT_STRING,
23 | TT_CONFIG_IDENT,
24 | TT_INT_LIT,
25 | TT_FLOAT_LIT,
26 | TT_COUNT,
27 | } Command_Type;
28 |
29 | typedef struct {
30 | Command_Type type;
31 | String_View value;
32 | size_t location;
33 | } Command_Token;
34 |
35 | typedef struct {
36 | String_View name;
37 | int value;
38 | } Identifier;
39 |
40 | typedef struct {
41 | String_View value;
42 | } Str_Literal;
43 |
44 | typedef struct {
45 | int value;
46 | } Expr;
47 |
48 | typedef enum {
49 | OP_NONE = 0,
50 | OP_PLUS,
51 | OP_MINUS,
52 | OP_MULT,
53 | OP_DIV,
54 | } Operator;
55 |
56 | typedef struct Bin_Expr {
57 | Expr lvalue;
58 | struct Bin_Expr *right;
59 | Expr rvalue;
60 | Operator operator;
61 | } Bin_Expr;
62 |
63 | typedef union {
64 | Expr as_expr;
65 | Bin_Expr as_bin;
66 | Command_Type as_keyword;
67 | Str_Literal as_str;
68 | Identifier as_ident;
69 | Config_Vars *as_config;
70 | int as_int;
71 | } Node_Val;
72 |
73 | typedef enum {
74 | NODE_EXPR,
75 | NODE_BIN,
76 | NODE_KEYWORD,
77 | NODE_STR,
78 | NODE_IDENT,
79 | NODE_CONFIG,
80 | NODE_INT,
81 | } Node_Type;
82 |
83 | typedef struct Node {
84 | Node_Val value;
85 | Node_Type type;
86 | struct Node *left;
87 | struct Node *right;
88 | } Node;
89 |
90 | typedef struct {
91 | char *name;
92 | int value;
93 | } Ctrl_Key;
94 |
95 | Command_Type get_token_type(State *state, String_View view);
96 | Command_Token create_token(State *state, String_View command);
97 | Command_Token *lex_command(State *state, String_View command, size_t *token_s);
98 | void print_token(Command_Token token);
99 | int expect_token(State *state, Command_Token token, Command_Type type);
100 | Node *create_node(Node_Type type, Node_Val value);
101 | Operator get_operator(Command_Token token);
102 | int get_special_char(String_View view);
103 | Bin_Expr *parse_bin_expr(State *state, Command_Token *command, size_t command_s);
104 | Node *parse_command(State *state, Command_Token *command, size_t command_s);
105 | int interpret_expr(Bin_Expr *expr);
106 | void interpret_command(Buffer *buffer, State *state, Node *root);
107 | void print_tree(Node *node, size_t depth);
108 | int execute_command(Buffer *buffer, State *state, Command_Token *command, size_t command_s);
109 |
110 | #endif // COMMANDS_H
111 |
--------------------------------------------------------------------------------
/src/defs.h:
--------------------------------------------------------------------------------
1 | #ifndef DEFS_H
2 | #define DEFS_H
3 |
4 | #include
5 | #include
6 |
7 | #include "view.h"
8 |
9 | #define DATA_START_CAPACITY 1024
10 |
11 | #define CRASH(str) \
12 | do { \
13 | frontend_end(); \
14 | fprintf(stderr, str"\n"); \
15 | exit(1); \
16 | } while(0)
17 |
18 | #define ASSERT(cond, ...) \
19 | do { \
20 | if (!(cond)) { \
21 | frontend_end(); \
22 | fprintf(stderr, "%s:%d: ASSERTION FAILED: ", __FILE__, __LINE__); \
23 | fprintf(stderr, __VA_ARGS__); \
24 | fprintf(stderr, "\n"); \
25 | exit(1); \
26 | } \
27 | } while (0)
28 |
29 | #define WRITE_LOG(message, ...) \
30 | do { \
31 | FILE *file = fopen("logs/cano.log", "a"); \
32 | if (file != NULL) { \
33 | fprintf(file, "%s:%d: " message "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
34 | fclose(file); \
35 | } \
36 | } while(0)
37 |
38 |
39 | #define DA_APPEND(da, item) do { \
40 | if ((da)->count >= (da)->capacity) { \
41 | (da)->capacity = (da)->capacity == 0 ? DATA_START_CAPACITY : (da)->capacity*2; \
42 | void *new = calloc(((da)->capacity+1), sizeof(*(da)->data)); \
43 | ASSERT(new,"outta ram"); \
44 | memcpy(new, (da)->data, (da)->count); \
45 | free((da)->data); \
46 | (da)->data = new; \
47 | } \
48 | (da)->data[(da)->count++] = (item); \
49 | } while (0)
50 |
51 | #define ctrl(x) ((x) & 0x1f)
52 |
53 | #define ESCAPE 27
54 | #define SPACE 32
55 | #define ENTER 10
56 | #define KEY_TAB 9
57 | #define DOWN_ARROW 258
58 | #define UP_ARROW 259
59 | #define LEFT_ARROW 260
60 | #define RIGHT_ARROW 261
61 |
62 | #define STARTING_ROWS_SIZE 128
63 | #define STARTING_ROW_SIZE 64
64 |
65 | typedef enum {
66 | NORMAL,
67 | INSERT,
68 | SEARCH,
69 | COMMAND,
70 | VISUAL,
71 | MODE_COUNT,
72 | } Mode;
73 |
74 | typedef enum {
75 | LEADER_NONE,
76 | LEADER_R,
77 | LEADER_D,
78 | LEADER_Y,
79 | LEADER_COUNT,
80 | } Leader;
81 |
82 | typedef enum {
83 | NONE = 0,
84 | INSERT_CHARS,
85 | DELETE_CHAR,
86 | DELETE_MULT_CHAR,
87 | REPLACE_CHAR,
88 | } Undo_Type;
89 |
90 | typedef enum {
91 | NO_ERROR = 0,
92 | NOT_ENOUGH_ARGS,
93 | INVALID_ARGS,
94 | UNKNOWN_COMMAND,
95 | INVALID_IDENT,
96 | } Command_Error;
97 |
98 | typedef struct {
99 | const char* path_to_file;
100 | const char* filename; /* maybe will get used? */
101 | const char* lang;
102 | } ThreadArgs;
103 |
104 | typedef struct {
105 | char color_name[20];
106 | bool is_custom_line_row;
107 | bool is_custom;
108 | int slot;
109 | int id;
110 | int red;
111 | int green;
112 | int blue;
113 | } Color;
114 |
115 | typedef struct {
116 | size_t x;
117 | size_t y;
118 | } Point;
119 |
120 | // Visual selection range
121 | typedef struct {
122 | size_t start;
123 | size_t end;
124 | int is_line;
125 | } Visual;
126 |
127 | // Index in buffer which indicate the start and end of a row
128 | typedef struct {
129 | size_t start;
130 | size_t end;
131 | } Row;
132 |
133 | typedef struct {
134 | Row *data;
135 | size_t count;
136 | size_t capacity;
137 | } Rows;
138 |
139 | // Data is the actual buffer which stores the text
140 | typedef struct {
141 | char *data;
142 | size_t count;
143 | size_t capacity;
144 | } Data;
145 |
146 | typedef struct {
147 | size_t *data;
148 | size_t count;
149 | size_t capacity;
150 | } Positions;
151 |
152 | typedef struct {
153 | Data data;
154 | Rows rows;
155 | size_t cursor;
156 | size_t row;
157 | size_t col;
158 |
159 | char *filename;
160 | Visual visual;
161 | } Buffer;
162 |
163 | typedef struct {
164 | size_t size;
165 | char *arg;
166 | } Arg;
167 |
168 | typedef struct {
169 | Undo_Type type;
170 | Data data;
171 | size_t start;
172 | size_t end;
173 | } Undo;
174 |
175 | typedef struct {
176 | Undo *data;
177 | size_t count;
178 | size_t capacity;
179 | } Undo_Stack;
180 |
181 | typedef struct {
182 | bool repeating;
183 | size_t repeating_count;
184 | } Repeating;
185 |
186 | typedef struct {
187 | char *str;
188 | size_t len;
189 | } Sized_Str;
190 |
191 | // For mapping keys
192 | typedef struct {
193 | int a;
194 | char *b;
195 | size_t b_s;
196 | } Map;
197 |
198 | typedef struct {
199 | Map *data;
200 | size_t count;
201 | size_t capacity;
202 | } Maps;
203 |
204 | typedef union {
205 | int as_int;
206 | float as_float;
207 | void *as_ptr;
208 | } Var_Value;
209 |
210 | typedef enum {
211 | VAR_INT,
212 | VAR_FLOAT,
213 | VAR_PTR,
214 | } Var_Type;
215 |
216 | typedef struct {
217 | char *name;
218 | Var_Value value;
219 | Var_Type type;
220 | } Variable;
221 |
222 | typedef struct {
223 | Variable *data;
224 | size_t count;
225 | size_t capacity;
226 | } Variables;
227 |
228 | typedef struct {
229 | char *name;
230 | char *path;
231 | // Including directories may be useful for making the explorer prettier
232 | bool is_directory;
233 | } File;
234 |
235 | typedef struct {
236 | File *data;
237 | size_t count;
238 | size_t capacity;
239 | } Files;
240 |
241 | typedef struct {
242 | String_View label;
243 | int *val;
244 | } Config_Vars;
245 |
246 | #define CONFIG_VARS 5
247 |
248 | typedef struct _Config_ {
249 | // Config variables
250 | int relative_nums;
251 | int auto_indent;
252 | int syntax;
253 | int indent;
254 | int undo_size;
255 | char *lang;
256 |
257 | // Control variables
258 | int QUIT;
259 | Mode mode;
260 |
261 | // Colors
262 | int background_color; // -1 for terminal background color.
263 |
264 | char leaders[LEADER_COUNT];
265 | Maps key_maps;
266 |
267 | Config_Vars vars[CONFIG_VARS];
268 | } Config;
269 |
270 | typedef struct State {
271 | Undo_Stack undo_stack;
272 | Undo_Stack redo_stack;
273 | Undo cur_undo;
274 | size_t num_of_braces; // braces that preceed the cursor
275 | int ch; // current character
276 | char *env; // home folder
277 |
278 | char *command; // most recent command entered by user
279 | size_t command_s; // size of command
280 |
281 | Variables variables;
282 |
283 | Repeating repeating;
284 | Data num;
285 | Leader leader; // current leader key
286 |
287 | bool is_print_msg;
288 | char *status_bar_msg;
289 |
290 | // rendering positions
291 | size_t x;
292 | size_t y;
293 | size_t normal_pos;
294 |
295 | // key functions to be called on each keypress
296 | void(**key_func)(Buffer *buffer, Buffer **modify_buffer, struct State *state);
297 |
298 | Sized_Str clipboard;
299 |
300 | // files for file explorer (ctrl+n)
301 | Files* files;
302 | bool is_exploring;
303 | size_t explore_cursor;
304 |
305 | // text buffer
306 | Buffer* buffer;
307 |
308 | // window sizes
309 | int grow;
310 | int gcol;
311 | int main_row;
312 | int main_col;
313 | int line_num_row;
314 | int line_num_col;
315 | int status_bar_row;
316 | int status_bar_col;
317 |
318 | // windows
319 | WINDOW *line_num_win;
320 | WINDOW *main_win;
321 | WINDOW *status_bar;
322 |
323 | Config config;
324 | } State;
325 |
326 | typedef struct {
327 | char brace;
328 | int closing;
329 | } Brace;
330 |
331 | typedef struct {
332 | int r;
333 | int g;
334 | int b;
335 | } Ncurses_Color;
336 |
337 | // positions for syntax highlighting
338 | typedef struct {
339 | size_t row;
340 | size_t col;
341 | size_t size;
342 | } Syntax_Highlighting;
343 |
344 | extern char *string_modes[MODE_COUNT];
345 |
346 | #endif // DEFS_H
347 |
--------------------------------------------------------------------------------
/src/frontend.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "buffer.h"
6 | #include "frontend.h"
7 | #include "tools.h"
8 |
9 | size_t num_of_open_braces(Buffer *buffer) {
10 | size_t index = buffer->cursor;
11 | int count = 0;
12 | int in_str = 0;
13 | while(index > 0) {
14 | index--;
15 | if(buffer->data.data[index] == '"' || buffer->data.data[index] == '\'') in_str = !in_str;
16 | Brace brace = find_opposite_brace(buffer->data.data[index]);
17 | if(brace.brace != '0' && !in_str) {
18 | if(!brace.closing) count++;
19 | if(brace.closing) count--;
20 | }
21 | }
22 | if(count < 0) return 0;
23 | return count;
24 | }
25 |
26 | size_t count_num_tabs(Buffer *buffer, size_t row) {
27 | buffer_calculate_rows(buffer);
28 | Row cur = buffer->rows.data[row];
29 | size_t start = cur.start;
30 | size_t count = 0;
31 | for(size_t i = start; i < buffer->cursor; i++) {
32 | if(buffer->data.data[i] == '\t') count++;
33 | }
34 | return count;
35 | }
36 |
37 | int is_between(size_t a, size_t b, size_t c) {
38 | if(a <= c && c <= b) return 1;
39 | return 0;
40 | }
41 |
42 | void init_colors(State *state) {
43 | if(has_colors() == FALSE) {
44 | CRASH("your terminal does not support colors");
45 | }
46 |
47 | start_color();
48 | if (state->config.background_color == -1){
49 | use_default_colors();
50 | }
51 | init_pair(YELLOW_COLOR, COLOR_YELLOW, state->config.background_color);
52 | init_pair(BLUE_COLOR, COLOR_BLUE, state->config.background_color);
53 | init_pair(GREEN_COLOR, COLOR_GREEN, state->config.background_color);
54 | init_pair(RED_COLOR, COLOR_RED, state->config.background_color);
55 | init_pair(MAGENTA_COLOR, COLOR_MAGENTA, state->config.background_color);
56 | init_pair(CYAN_COLOR, COLOR_CYAN, state->config.background_color);
57 | }
58 |
59 | void state_render(State *state) {
60 | static size_t row_render_start = 0;
61 | static size_t col_render_start = 0;
62 | werase(state->main_win);
63 | werase(state->status_bar);
64 | werase(state->line_num_win);
65 | size_t cur_row = buffer_get_row(state->buffer);
66 | if(state->is_exploring) cur_row = state->explore_cursor;
67 | Row cur = state->buffer->rows.data[cur_row];
68 | size_t col = state->buffer->cursor - cur.start;
69 | if(state->is_exploring) col = 0;
70 | if(cur_row <= row_render_start) row_render_start = cur_row;
71 | if(cur_row >= row_render_start+state->main_row) row_render_start = cur_row-state->main_row+1;
72 | if(col <= col_render_start) col_render_start = col;
73 | if(col >= col_render_start+state->main_col) col_render_start = col-state->main_col+1;
74 | state->num_of_braces = num_of_open_braces(state->buffer);
75 |
76 | if(state->is_print_msg) {
77 | mvwprintw(state->status_bar, 1, 0, "%s", state->status_bar_msg);
78 | wrefresh(state->status_bar);
79 | sleep(1);
80 | wclear(state->status_bar);
81 | state->is_print_msg = 0;
82 | }
83 | if (is_term_resized(state->line_num_row, state->line_num_col)) {
84 | getmaxyx(stdscr, state->grow, state->gcol);
85 | getmaxyx(state->line_num_win, state->line_num_row, state->line_num_col);
86 | mvwin(state->status_bar, state->grow-2, 0);
87 | }
88 |
89 | mvwprintw(state->status_bar, 0, state->gcol/2, "%zu:%zu", cur_row+1, col+1);
90 | mvwprintw(state->status_bar, 0, state->main_col-11, "%c", state->config.leaders[state->leader]);
91 | mvwprintw(state->status_bar, 0, 0, "%.7s", string_modes[state->config.mode]);
92 | mvwprintw(state->status_bar, 0, state->main_col-5, "%.*s", (int)state->num.count, state->num.data);
93 |
94 | if(state->config.mode == COMMAND || state->config.mode == SEARCH) mvwprintw(state->status_bar, 1, 0, ":%.*s", (int)state->command_s, state->command);
95 | if(state->is_exploring) {
96 | wattron(state->main_win, COLOR_PAIR(BLUE_COLOR));
97 | for(size_t i = row_render_start; i <= row_render_start+state->main_row; i++) {
98 | if(i >= state->files->count) break;
99 | // TODO: All directories should be displayed first, afterwards files
100 | size_t print_index_y = i - row_render_start;
101 | mvwprintw(state->main_win, print_index_y, 0, "%s", state->files->data[i].name);
102 | }
103 | } else {
104 | for(size_t i = row_render_start; i <= row_render_start+state->main_row; i++) {
105 | if(i >= state->buffer->rows.count) break;
106 | size_t print_index_y = i - row_render_start;
107 | wattron(state->line_num_win, COLOR_PAIR(YELLOW_COLOR));
108 | if(state->config.relative_nums) {
109 | if(cur_row == i) {
110 | mvwprintw(state->line_num_win, print_index_y, 0, "%zu", i+1);
111 | } else {
112 | mvwprintw(state->line_num_win, print_index_y, 0, "%zu", (size_t)abs((int)i-(int)cur_row));
113 | }
114 | } else {
115 | mvwprintw(state->line_num_win, print_index_y, 0, "%zu", i+1);
116 | }
117 | wattroff(state->line_num_win, COLOR_PAIR(YELLOW_COLOR));
118 | size_t off_at = 0;
119 | size_t token_capacity = 32;
120 | Token *token_arr = calloc(token_capacity, sizeof(Token));
121 | size_t token_s = 0;
122 | if(state->config.syntax) {
123 | token_s = generate_tokens(state->buffer->data.data+state->buffer->rows.data[i].start,
124 | state->buffer->rows.data[i].end-state->buffer->rows.data[i].start,
125 | token_arr, &token_capacity);
126 | }
127 | Color_Pairs color = 0;
128 | for(size_t j = state->buffer->rows.data[i].start; j < state->buffer->rows.data[i].end; j++) {
129 | if(j < state->buffer->rows.data[i].start+col_render_start || j > state->buffer->rows.data[i].end+col+state->main_col) continue;
130 | size_t col = j-state->buffer->rows.data[i].start;
131 | size_t print_index_x = col-col_render_start;
132 |
133 | for(size_t chr = state->buffer->rows.data[i].start; chr < j; chr++) {
134 | if(state->buffer->data.data[chr] == '\t') print_index_x += 3;
135 | }
136 | size_t keyword_size = 0;
137 | if(state->config.syntax && is_in_tokens_index(token_arr, token_s, col, &keyword_size, &color)) {
138 | wattron(state->main_win, COLOR_PAIR(color));
139 | off_at = col+keyword_size;
140 | }
141 | if(col == off_at) wattroff(state->main_win, COLOR_PAIR(color));
142 | if(col > state->buffer->rows.data[i].end) break;
143 | int between = (state->buffer->visual.start > state->buffer->visual.end)
144 | ? is_between(state->buffer->visual.end, state->buffer->visual.start, state->buffer->rows.data[i].start+col)
145 | : is_between(state->buffer->visual.start, state->buffer->visual.end, state->buffer->rows.data[i].start+col);
146 | if(state->config.mode == VISUAL && between) wattron(state->main_win, A_STANDOUT);
147 | else wattroff(state->main_win, A_STANDOUT);
148 | mvwprintw(state->main_win, print_index_y, print_index_x, "%c", state->buffer->data.data[state->buffer->rows.data[i].start+col]);
149 | }
150 | free(token_arr);
151 | }
152 | }
153 |
154 | col += count_num_tabs(state->buffer, buffer_get_row(state->buffer))*3;
155 |
156 | wrefresh(state->main_win);
157 | wrefresh(state->line_num_win);
158 | wrefresh(state->status_bar);
159 | if(state->config.mode == COMMAND || state->config.mode == SEARCH) {
160 | frontend_move_cursor(state->status_bar, 1, state->x);
161 | wrefresh(state->status_bar);
162 | } else {
163 | frontend_move_cursor(state->main_win, cur_row-row_render_start, col-col_render_start);
164 | }
165 | }
166 |
167 | void frontend_init(State *state) {
168 | initscr();
169 | noecho();
170 | raw();
171 |
172 | init_colors(state);
173 |
174 | getmaxyx(stdscr, state->grow, state->gcol);
175 | int line_num_width = 5;
176 | WINDOW *main_win = newwin(state->grow-2, state->gcol-line_num_width, 0, line_num_width);
177 | WINDOW *status_bar = newwin(2, state->gcol, state->grow-2, 0);
178 | WINDOW *line_num_win = newwin(state->grow-2, line_num_width, 0, 0);
179 | state->main_win = main_win;
180 | state->status_bar = status_bar;
181 | state->line_num_win = line_num_win;
182 | getmaxyx(main_win, state->main_row, state->main_col);
183 | getmaxyx(line_num_win, state->line_num_row, state->line_num_col);
184 |
185 | keypad(status_bar, TRUE);
186 | keypad(main_win, TRUE);
187 |
188 | set_escdelay(0);
189 | }
190 |
191 | void frontend_move_cursor(WINDOW *window, size_t x_pos, size_t y_pos) {
192 | wmove(window, x_pos, y_pos);
193 | }
194 |
195 | void frontend_cursor_visible(int value) {
196 | curs_set(value);
197 | }
198 |
199 | int frontend_getch(WINDOW *window) {
200 | return wgetch(window);
201 | }
202 |
203 | void frontend_resize_window(State *state) {
204 | getmaxyx(stdscr, state->grow, state->gcol);
205 | wresize(state->main_win, state->grow-2, state->gcol-state->line_num_col);
206 | wresize(state->status_bar, 2, state->gcol);
207 | getmaxyx(state->main_win, state->main_row, state->main_col);
208 | mvwin(state->main_win, 0, state->line_num_col);
209 | wrefresh(state->main_win);
210 | }
211 |
212 | void frontend_end() {
213 | printf("\x1b[\x30 q");
214 | wrefresh(stdscr);
215 | endwin();
216 | }
217 |
--------------------------------------------------------------------------------
/src/frontend.h:
--------------------------------------------------------------------------------
1 | #ifndef FRONTEND_H
2 | #define FRONTEND_H
3 |
4 | #include
5 | #include
6 |
7 | #include "defs.h"
8 |
9 | void frontend_init(State *state);
10 | void state_render(State *state);
11 | void frontend_resize_window(State *state);
12 | int frontend_getch(WINDOW *window);
13 | void frontend_move_cursor(WINDOW *window, size_t pos_x, size_t pos_y);
14 | void frontend_cursor_visible(int value);
15 | void frontend_end(void);
16 |
17 | #endif // FRONTEND_H
18 |
--------------------------------------------------------------------------------
/src/keys.c:
--------------------------------------------------------------------------------
1 | #include "keys.h"
2 | #include "main.h"
3 |
4 | size_t search(Buffer *buffer, char *command, size_t command_s) {
5 | for(size_t i = buffer->cursor+1; i < buffer->data.count+buffer->cursor; i++) {
6 | size_t pos = i % buffer->data.count;
7 | if(strncmp(buffer->data.data+pos, command, command_s) == 0) {
8 | // result found and will return the location of the word.
9 | return pos;
10 | }
11 | }
12 | return buffer->cursor;
13 | }
14 |
15 | void replace(Buffer *buffer, State *state, char *new_str, size_t old_str_s, size_t new_str_s) {
16 | if (buffer == NULL || new_str == NULL) {
17 | WRITE_LOG("Error: null pointer");
18 | return;
19 | }
20 |
21 | for(size_t i = 0; i < old_str_s; i++) {
22 | buffer_delete_char(buffer, state);
23 | }
24 |
25 | for(size_t i = 0; i < new_str_s; i++) {
26 | buffer_insert_char(state, buffer, new_str[i]);
27 | }
28 | }
29 |
30 |
31 | void find_and_replace(Buffer *buffer, State *state, char *old_str, char *new_str) {
32 | size_t old_str_s = strlen(old_str);
33 | size_t new_str_s = strlen(new_str);
34 |
35 | // Search for the old string in the buffer
36 | size_t position = search(buffer, old_str, old_str_s);
37 | if (position != (buffer->cursor)) {
38 | buffer->cursor = position;
39 | // If the old string is found, replace it with the new string
40 | replace(buffer, state, new_str, old_str_s, new_str_s);
41 | }
42 | }
43 |
44 | void motion_g(State *state) {
45 | size_t row = buffer_get_row(state->buffer);
46 | if(state->repeating.repeating_count >= state->buffer->rows.count)
47 | state->repeating.repeating_count = state->buffer->rows.count;
48 | if(state->repeating.repeating_count == 0) state->repeating.repeating_count = 1;
49 | state->buffer->cursor = state->buffer->rows.data[state->repeating.repeating_count-1].start;
50 | state->repeating.repeating_count = 0;
51 | if(state->leader != LEADER_D) return;
52 | // TODO: this doens't work with row jumps
53 | size_t end = state->buffer->rows.data[row].end;
54 | size_t start = state->buffer->cursor;
55 | CREATE_UNDO(INSERT_CHARS, start);
56 | buffer_delete_selection(state->buffer, state, start, end);
57 | undo_push(state, &state->undo_stack, state->cur_undo);
58 | }
59 |
60 | void motion_G(State *state) {
61 | size_t row = buffer_get_row(state->buffer);
62 | size_t start = state->buffer->rows.data[row].start;
63 | if(state->repeating.repeating_count > 0) {
64 | if(state->repeating.repeating_count >= state->buffer->rows.count)
65 | state->repeating.repeating_count = state->buffer->rows.count;
66 | state->buffer->cursor = state->buffer->rows.data[state->repeating.repeating_count-1].start;
67 | state->repeating.repeating_count = 0;
68 | } else {
69 | state->buffer->cursor = state->buffer->data.count;
70 | }
71 | if(state->leader != LEADER_D) return;
72 | // TODO: this doesn't work with row jumps
73 | size_t end = state->buffer->cursor;
74 | CREATE_UNDO(INSERT_CHARS, start);
75 | buffer_delete_selection(state->buffer, state, start, end);
76 | undo_push(state, &state->undo_stack, state->cur_undo);
77 | }
78 |
79 | void motion_0(State *state) {
80 | size_t row = buffer_get_row(state->buffer);
81 | size_t end = state->buffer->cursor;
82 | state->buffer->cursor = state->buffer->rows.data[row].start;
83 | if(state->leader != LEADER_D) return;
84 | size_t start = state->buffer->cursor;
85 | CREATE_UNDO(INSERT_CHARS, start);
86 | buffer_delete_selection(state->buffer, state, start, end);
87 | undo_push(state, &state->undo_stack, state->cur_undo);
88 | }
89 |
90 | void motion_$(State *state) {
91 | size_t row = buffer_get_row(state->buffer);
92 | size_t start = state->buffer->cursor;
93 | state->buffer->cursor = state->buffer->rows.data[row].end;
94 | if(state->leader != LEADER_D) return;
95 | size_t end = state->buffer->cursor;
96 | CREATE_UNDO(INSERT_CHARS, start);
97 | buffer_delete_selection(state->buffer, state, start, end-1);
98 | undo_push(state, &state->undo_stack, state->cur_undo);
99 | }
100 |
101 | void motion_e(State *state) {
102 | size_t start = state->buffer->cursor;
103 | if(state->buffer->cursor+1 < state->buffer->data.count &&
104 | !isword(state->buffer->data.data[state->buffer->cursor+1])) state->buffer->cursor++;
105 | while(state->buffer->cursor+1 < state->buffer->data.count &&
106 | (isword(state->buffer->data.data[state->buffer->cursor+1]) ||
107 | isspace(state->buffer->data.data[state->buffer->cursor]))
108 | ) {
109 | state->buffer->cursor++;
110 | }
111 | if(state->leader != LEADER_D) return;
112 | size_t end = state->buffer->cursor;
113 | CREATE_UNDO(INSERT_CHARS, start);
114 | buffer_delete_selection(state->buffer, state, start, end);
115 | undo_push(state, &state->undo_stack, state->cur_undo);
116 | }
117 |
118 | void motion_b(State *state) {
119 | Buffer *buffer = state->buffer;
120 | if(buffer->cursor == 0) return;
121 | size_t end = buffer->cursor;
122 | if(buffer->cursor-1 > 0 && !isword(buffer->data.data[buffer->cursor-1])) buffer->cursor--;
123 | while(buffer->cursor-1 > 0 &&
124 | (isword(buffer->data.data[buffer->cursor-1]) || isspace(buffer->data.data[buffer->cursor+1]))
125 | ) {
126 | buffer->cursor--;
127 | }
128 | if(buffer->cursor-1 == 0) buffer->cursor--;
129 | if(state->leader != LEADER_D) return;
130 | size_t start = buffer->cursor;
131 | CREATE_UNDO(INSERT_CHARS, start);
132 | buffer_delete_selection(buffer, state, start, end);
133 | undo_push(state, &state->undo_stack, state->cur_undo);
134 | }
135 |
136 | void motion_w(State *state) {
137 | Buffer *buffer = state->buffer;
138 | size_t start = buffer->cursor;
139 | while(buffer->cursor < buffer->data.count &&
140 | (isword(buffer->data.data[buffer->cursor]) || isspace(buffer->data.data[buffer->cursor+1]))
141 | ) {
142 | buffer->cursor++;
143 | }
144 | if(buffer->cursor < buffer->data.count) buffer->cursor++;
145 | if(state->leader != LEADER_D) return;
146 | size_t end = buffer->cursor-1;
147 | CREATE_UNDO(INSERT_CHARS, start);
148 | buffer_delete_selection(buffer, state, start, end-1);
149 | undo_push(state, &state->undo_stack, state->cur_undo);
150 | }
151 |
152 | int handle_motion_keys(Buffer *buffer, State *state, int ch, size_t *repeating_count) {
153 | (void)repeating_count;
154 | switch(ch) {
155 | case 'g': { // Move to the start of the file or to the line specified by repeating_count
156 | motion_g(state);
157 | } break;
158 | case 'G': { // Move to the end of the file or to the line specified by repeating_count
159 | motion_G(state);
160 | } break;
161 | case '0': { // Move to the start of the line
162 | motion_0(state);
163 | } break;
164 | case '$': { // Move to the end of the line
165 | motion_$(state);
166 | } break;
167 | case 'e': { // Move to the end of the next word
168 | motion_e(state);
169 | } break;
170 | case 'b': { // Move to the start of the previous word
171 | motion_b(state);
172 | } break;
173 | case 'w': { // Move to the start of the next word
174 | motion_w(state);
175 | } break;
176 | case LEFT_ARROW:
177 | case 'h': // Move left
178 | buffer_move_left(buffer);
179 | break;
180 | case DOWN_ARROW:
181 | case 'j': // Move down
182 | buffer_move_down(buffer);
183 | break;
184 | case UP_ARROW:
185 | case 'k': // Move up
186 | buffer_move_up(buffer);
187 | break;
188 | case RIGHT_ARROW:
189 | case 'l': // Move right
190 | buffer_move_right(buffer);
191 | break;
192 | case '%': { // Move to the matching brace
193 | buffer_next_brace(buffer);
194 | break;
195 | }
196 | default: { // If the key is not a motion key, return 0
197 | return 0;
198 | }
199 | }
200 | return 1; // If the key is a motion key, return 1
201 | }
202 |
203 | int handle_leader_keys(State *state) {
204 | switch(state->ch) {
205 | case 'd':
206 | state->leader = LEADER_D;
207 | break;
208 | case 'y':
209 | state->leader = LEADER_Y;
210 | break;
211 | default:
212 | return 0;
213 | }
214 | return 1;
215 | }
216 |
217 | int handle_modifying_keys(Buffer *buffer, State *state) {
218 | switch(state->ch) {
219 | case 'x': {
220 | buffer_delete_ch(buffer, state);
221 | } break;
222 | case 'd': {
223 | switch(state->leader) {
224 | case LEADER_D: {
225 | buffer_delete_row(buffer, state);
226 | }
227 | default: break;
228 | }
229 | } break;
230 | case 'r': {
231 | buffer_replace_ch(buffer, state);
232 | } break;
233 | default: {
234 | return 0;
235 | }
236 | }
237 | return 1;
238 | }
239 |
240 | int handle_normal_to_insert_keys(Buffer *buffer, State *state) {
241 | switch(state->ch) {
242 | case 'i': {
243 | state->config.mode = INSERT;
244 | if(state->repeating.repeating_count) {
245 | state->ch = frontend_getch(state->main_win);
246 | }
247 | } break;
248 | case 'I': {
249 | size_t row = buffer_get_row(buffer);
250 | Row cur = buffer->rows.data[row];
251 | buffer->cursor = cur.start;
252 | while(buffer->cursor < cur.end && isspace(buffer->data.data[buffer->cursor])) buffer->cursor++;
253 | state->config.mode = INSERT;
254 | } break;
255 | case 'a':
256 | if(buffer->cursor < buffer->data.count) buffer->cursor++;
257 | state->config.mode = INSERT;
258 | break;
259 | case 'A': {
260 | size_t row = buffer_get_row(buffer);
261 | size_t end = buffer->rows.data[row].end;
262 | buffer->cursor = end;
263 | state->config.mode = INSERT;
264 | } break;
265 | case 'o': {
266 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
267 | size_t row = buffer_get_row(buffer);
268 | size_t end = buffer->rows.data[row].end;
269 | buffer->cursor = end;
270 | buffer_newline_indent(buffer, state);
271 | state->config.mode = INSERT;
272 | undo_push(state, &state->undo_stack, state->cur_undo);
273 | } break;
274 | case 'O': {
275 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
276 | size_t row = buffer_get_row(buffer);
277 | size_t start = buffer->rows.data[row].start;
278 | buffer->cursor = start;
279 | buffer_newline_indent(buffer, state);
280 | state->config.mode = INSERT;
281 | undo_push(state, &state->undo_stack, state->cur_undo);
282 | } break;
283 | default: {
284 | return 0;
285 | }
286 | }
287 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
288 | return 1;
289 | }
290 |
291 | void buffer_handle_undo(State *state, Undo *undo) {
292 | Buffer *buffer = state->buffer;
293 | Undo redo = {0};
294 | redo.start = undo->start;
295 | state->cur_undo = redo;
296 | switch(undo->type) {
297 | case NONE:
298 | break;
299 | case INSERT_CHARS:
300 | state->cur_undo.type = (undo->data.count > 1) ? DELETE_MULT_CHAR : DELETE_CHAR;
301 | state->cur_undo.end = undo->start + undo->data.count-1;
302 | buffer->cursor = undo->start;
303 | buffer_insert_selection(buffer, &undo->data, undo->start);
304 | break;
305 | case DELETE_CHAR:
306 | state->cur_undo.type = INSERT_CHARS;
307 | buffer->cursor = undo->start;
308 | buffer_delete_char(buffer, state);
309 | break;
310 | case DELETE_MULT_CHAR:
311 | state->cur_undo.type = INSERT_CHARS;
312 | state->cur_undo.end = undo->end;
313 | buffer->cursor = undo->start;
314 | WRITE_LOG("%zu %zu", undo->start, undo->end);
315 | buffer_delete_selection(buffer, state, undo->start, undo->end);
316 | break;
317 | case REPLACE_CHAR:
318 | state->cur_undo.type = REPLACE_CHAR;
319 | buffer->cursor = undo->start;
320 | DA_APPEND(&undo->data, buffer->data.data[buffer->cursor]);
321 | buffer->data.data[buffer->cursor] = undo->data.data[0];
322 | break;
323 | }
324 | }
325 |
326 | void handle_explore_keys(State *state) {
327 | switch(state->ch) {
328 | case DOWN_ARROW:
329 | case 'j': // Move down
330 | if (state->explore_cursor < state->files->count-1) {
331 | state->explore_cursor++;
332 | }
333 | break;
334 | case UP_ARROW:
335 | case 'k': // Move up
336 | if (state->explore_cursor > 0) {
337 | state->explore_cursor--;
338 | }
339 | break;
340 | case ENTER: {
341 | File f = state->files->data[state->explore_cursor];
342 | if (f.is_directory) {
343 | char str[256];
344 | strcpy(str, f.path);
345 | free_files(&state->files);
346 | state->files = calloc(32, sizeof(File));
347 | scan_files(state, str);
348 | state->explore_cursor = 0;
349 | } else {
350 | free_buffer(state->buffer);
351 | state->buffer = load_buffer_from_file(f.path);
352 | //char *config_filename = NULL;
353 | //char *syntax_filename = NULL;
354 | // TODO: Make this config work
355 | //load_config_from_file(state, state->buffer, config_filename, syntax_filename);
356 | state->is_exploring = false;
357 | }
358 | } break;
359 | }
360 | }
361 |
362 | void handle_normal_keys(Buffer *buffer, Buffer **modify_buffer, State *state) {
363 | (void)modify_buffer;
364 |
365 | if(check_keymaps(buffer, state)) return;
366 | if(state->leader == LEADER_NONE && handle_leader_keys(state)) return;
367 | if(isdigit(state->ch) && !(state->ch == '0' && state->num.count == 0)) {
368 | DA_APPEND(&state->num, state->ch);
369 | return;
370 | }
371 |
372 | if(!isdigit(state->ch) && state->num.count > 0) {
373 | ASSERT(state->num.data, "num is not initialized");
374 | state->repeating.repeating_count = atoi(state->num.data);
375 | if(state->repeating.repeating_count == 0) return;
376 | state->num.count = 0;
377 | for(size_t i = 0; i < state->repeating.repeating_count; i++) {
378 | state->key_func[state->config.mode](buffer, modify_buffer, state);
379 | }
380 | state->repeating.repeating_count = 0;
381 | memset(state->num.data, 0, state->num.capacity);
382 | return;
383 | }
384 |
385 | switch(state->ch) {
386 | case ':':
387 | state->x = 1;
388 | frontend_move_cursor(state->status_bar, state->x, 1);
389 | state->config.mode = COMMAND;
390 | break;
391 | case '/':
392 | if(state->is_exploring) break;
393 | reset_command(state->command, &state->command_s);
394 | state->x = state->command_s+1;
395 | frontend_move_cursor(state->status_bar, state->x, 1);
396 | state->config.mode = SEARCH;
397 | break;
398 | case 'v':
399 | if(state->is_exploring) break;
400 | buffer->visual.start = buffer->cursor;
401 | buffer->visual.end = buffer->cursor;
402 | buffer->visual.is_line = 0;
403 | state->config.mode = VISUAL;
404 | break;
405 | case 'V':
406 | if(state->is_exploring) break;
407 | buffer->visual.start = buffer->rows.data[buffer_get_row(buffer)].start;
408 | buffer->visual.end = buffer->rows.data[buffer_get_row(buffer)].end;
409 | buffer->visual.is_line = 1;
410 | state->config.mode = VISUAL;
411 | break;
412 | case ctrl('o'): {
413 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
414 | size_t row = buffer_get_row(buffer);
415 | size_t end = buffer->rows.data[row].end;
416 | buffer->cursor = end;
417 | buffer_newline_indent(buffer, state);
418 | undo_push(state, &state->undo_stack, state->cur_undo);
419 | break;
420 | } break;
421 | case 'n': {
422 | size_t index = search(buffer, state->command, state->command_s);
423 | buffer->cursor = index;
424 | } break;
425 | case 'u': {
426 | Undo undo = undo_pop(&state->undo_stack);
427 | buffer_handle_undo(state, &undo);
428 | undo_push(state, &state->redo_stack, state->cur_undo);
429 | free_undo(&undo);
430 | } break;
431 | case 'U': {
432 | Undo redo = undo_pop(&state->redo_stack);
433 | buffer_handle_undo(state, &redo);
434 | undo_push(state, &state->undo_stack, state->cur_undo);
435 | free_undo(&redo);
436 | } break;
437 | case ctrl('s'): {
438 | handle_save(buffer);
439 | state->config.QUIT = 1;
440 | } break;
441 | case ctrl('c'):
442 | case ESCAPE:
443 | state->repeating.repeating_count = 0;
444 | reset_command(state->command, &state->command_s);
445 | state->config.mode = NORMAL;
446 | break;
447 | case KEY_RESIZE: {
448 | frontend_resize_window(state);
449 | } break;
450 | case 'y': {
451 | switch(state->leader) {
452 | case LEADER_Y: {
453 | if(state->repeating.repeating_count == 0) state->repeating.repeating_count = 1;
454 | reset_command(state->clipboard.str, &state->clipboard.len);
455 | for(size_t i = 0; i < state->repeating.repeating_count; i++) {
456 | buffer_yank_line(buffer, state, i);
457 | }
458 | state->repeating.repeating_count = 0;
459 | } break;
460 | default:
461 | break;
462 | }
463 | } break;
464 | case 'p': {
465 | if(state->clipboard.len == 0) break;
466 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
467 | Data data = dynstr_to_data(state->clipboard);
468 | if(state->clipboard.len > 0 && state->clipboard.str[0] == '\n') {
469 | WRITE_LOG("newline");
470 | size_t row = buffer_get_row(buffer);
471 | size_t end = buffer->rows.data[row].end;
472 | buffer->cursor = end;
473 | }
474 | buffer_insert_selection(buffer, &data, buffer->cursor);
475 | state->cur_undo.end = buffer->cursor;
476 | undo_push(state, &state->undo_stack, state->cur_undo);
477 | if(state->clipboard.len > 0 && state->clipboard.str[0] == '\n' && buffer->cursor < buffer->data.count)
478 | buffer->cursor++;
479 | } break;
480 | case ctrl('n'): {
481 | state->is_exploring = !state->is_exploring;
482 | } break;
483 | default: {
484 | if(state->is_exploring) handle_explore_keys(state);
485 | if(handle_modifying_keys(buffer, state)) break;
486 | if(handle_motion_keys(buffer, state, state->ch, &state->repeating.repeating_count)) break;
487 | if(handle_normal_to_insert_keys(buffer, state)) break;
488 | } break;
489 | }
490 | if(state->repeating.repeating_count == 0) {
491 | state->leader = LEADER_NONE;
492 | }
493 | }
494 |
495 | void handle_move_left(State *state, size_t num) {
496 | state->cur_undo.end = state->buffer->cursor;
497 | if(state->cur_undo.end != state->cur_undo.start) undo_push(state, &state->undo_stack, state->cur_undo);
498 | for(size_t i = 0; i < num; i++) {
499 | buffer_move_left(state->buffer);
500 | }
501 | CREATE_UNDO(DELETE_MULT_CHAR, state->buffer->cursor);
502 | }
503 |
504 | void handle_move_down(State *state, size_t num) {
505 | state->cur_undo.end = state->buffer->cursor;
506 | if(state->cur_undo.end != state->cur_undo.start) undo_push(state, &state->undo_stack, state->cur_undo);
507 | for(size_t i = 0; i < num; i++) {
508 | buffer_move_down(state->buffer);
509 | }
510 | CREATE_UNDO(DELETE_MULT_CHAR, state->buffer->cursor);
511 | }
512 |
513 | void handle_move_up(State *state, size_t num) {
514 | state->cur_undo.end = state->buffer->cursor;
515 | if(state->cur_undo.end != state->cur_undo.start) undo_push(state, &state->undo_stack, state->cur_undo);
516 | for(size_t i = 0; i < num; i++) {
517 | buffer_move_up(state->buffer);
518 | }
519 | CREATE_UNDO(DELETE_MULT_CHAR, state->buffer->cursor);
520 | }
521 |
522 | void handle_move_right(State *state, size_t num) {
523 | state->cur_undo.end = state->buffer->cursor;
524 | if(state->cur_undo.end != state->cur_undo.start) undo_push(state, &state->undo_stack, state->cur_undo);
525 | for(size_t i = 0; i < num; i++) {
526 | buffer_move_right(state->buffer);
527 | }
528 | CREATE_UNDO(DELETE_MULT_CHAR, state->buffer->cursor);
529 | }
530 |
531 |
532 | void handle_insert_keys(Buffer *buffer, Buffer **modify_buffer, State *state) {
533 | (void)modify_buffer;
534 | ASSERT(buffer, "buffer exists");
535 | ASSERT(state, "state exists");
536 |
537 | switch(state->ch) {
538 | case '\b':
539 | case 127:
540 | case KEY_BACKSPACE: {
541 | if(buffer->cursor > 0) {
542 | buffer->cursor--;
543 | buffer_delete_char(buffer, state);
544 | }
545 | } break;
546 | case ctrl('s'): {
547 | handle_save(buffer);
548 | state->config.QUIT = 1;
549 | } break;
550 | case ctrl('c'):
551 | case ESCAPE: // Switch to NORMAL mode
552 | state->cur_undo.end = buffer->cursor;
553 | undo_push(state, &state->undo_stack, state->cur_undo);
554 | state->config.mode = NORMAL;
555 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
556 | break;
557 | case LEFT_ARROW: { // Move cursor left
558 | handle_move_left(state, 1);
559 | } break;
560 | case DOWN_ARROW: { // Move cursor down
561 | handle_move_down(state, 1);
562 | } break;
563 | case UP_ARROW: { // Move cursor up
564 | handle_move_up(state, 1);
565 | } break;
566 | case RIGHT_ARROW: { // Move cursor right
567 | handle_move_right(state, 1);
568 | } break;
569 | case KEY_RESIZE: {
570 | frontend_resize_window(state);
571 | } break;
572 | case KEY_TAB:
573 | buffer_create_indent(buffer, state, 1);
574 | break;
575 | case KEY_ENTER:
576 | case ENTER: {
577 | state->cur_undo.end = buffer->cursor;
578 | undo_push(state, &state->undo_stack, state->cur_undo);
579 |
580 | Brace brace = find_opposite_brace(buffer->data.data[buffer->cursor]);
581 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
582 | buffer_newline_indent(buffer, state);
583 | state->cur_undo.end = buffer->cursor;
584 |
585 | // handle the extra brace which gets inserted for brace auto-close
586 | buffer_brace_indent(state, brace);
587 | } break;
588 | default: { // Handle other characters
589 | ASSERT(buffer->data.count >= buffer->cursor, "check");
590 | ASSERT(buffer->data.data, "check2");
591 | Brace brace = (Brace){0};
592 | Brace cur_brace = {0};
593 | if(buffer->cursor == 0) cur_brace = find_opposite_brace(buffer->data.data[0]);
594 | else cur_brace = find_opposite_brace(buffer->data.data[buffer->cursor]);
595 |
596 | if((cur_brace.brace != '0' && cur_brace.closing &&
597 | state->ch == find_opposite_brace(cur_brace.brace).brace)) {
598 | buffer->cursor++;
599 | break;
600 | };
601 | brace = find_opposite_brace(state->ch);
602 | // TODO: make quotes auto close
603 | buffer_insert_char(state, buffer, state->ch);
604 | if(brace.brace != '0' && !brace.closing) {
605 | state->cur_undo.end -= 1;
606 | undo_push(state, &state->undo_stack, state->cur_undo);
607 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor-1);
608 | buffer_insert_char(state, buffer, brace.brace);
609 | undo_push(state, &state->undo_stack, state->cur_undo);
610 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
611 | handle_move_left(state, 1);
612 | CREATE_UNDO(DELETE_MULT_CHAR, buffer->cursor);
613 | }
614 | } break;
615 | }
616 | }
617 |
618 | void handle_command_keys(Buffer *buffer, Buffer **modify_buffer, State *state) {
619 | (void)modify_buffer;
620 | switch(state->ch) {
621 | case '\b':
622 | case 127:
623 | case KEY_BACKSPACE: {
624 | if(state->x != 1) {
625 | shift_str_left(state->command, &state->command_s, --state->x);
626 | frontend_move_cursor(state->status_bar, 1, state->x);
627 | }
628 | } break;
629 | case ctrl('c'):
630 | case ESCAPE:
631 | reset_command(state->command, &state->command_s);
632 | state->config.mode = NORMAL;
633 | break;
634 | case ctrl('s'): {
635 | handle_save(buffer);
636 | state->config.QUIT = 1;
637 | } break;
638 | case KEY_ENTER:
639 | case ENTER: {
640 | if(state->command[0] == '!') {
641 | shift_str_left(state->command, &state->command_s, 0);
642 | FILE *file = popen(state->command, "r");
643 | if(file == NULL) {
644 | CRASH("could not run command");
645 | }
646 | while(fgets(state->status_bar_msg, sizeof(state->status_bar_msg), file) != NULL) {
647 | state->is_print_msg = 1;
648 | }
649 | pclose(file);
650 | } else {
651 | size_t command_s = 0;
652 | Command_Token *command = lex_command(state, view_create(state->command, state->command_s), &command_s);
653 | execute_command(buffer, state, command, command_s);
654 | }
655 | reset_command(state->command, &state->command_s);
656 | state->config.mode = NORMAL;
657 | } break;
658 | case LEFT_ARROW:
659 | if(state->x > 1) state->x--;
660 | break;
661 | case DOWN_ARROW:
662 | break;
663 | case UP_ARROW:
664 | break;
665 | case RIGHT_ARROW:
666 | if(state->x < state->command_s) state->x++;
667 | break;
668 | case KEY_RESIZE:
669 | frontend_resize_window(state);
670 | break;
671 | default: {
672 | shift_str_right(state->command, &state->command_s, state->x);
673 | state->command[(state->x++)-1] = state->ch;
674 | } break;
675 | }
676 | }
677 |
678 | void handle_search_keys(Buffer *buffer, Buffer **modify_buffer, State *state) {
679 | (void)modify_buffer;
680 | (void)buffer;
681 | switch(state->ch) {
682 | case '\b':
683 | case 127:
684 | case KEY_BACKSPACE: {
685 | if(state->x != 1) {
686 | shift_str_left(state->command, &state->command_s, --state->x);
687 | frontend_move_cursor(state->status_bar, 1, state->x);
688 | }
689 | } break;
690 | case ctrl('c'):
691 | case ESCAPE:
692 | reset_command(state->command, &state->command_s);
693 | state->config.mode = NORMAL;
694 | break;
695 | case ENTER: {
696 | size_t index = search(buffer, state->command, state->command_s);
697 | if(state->command_s > 2 && strncmp(state->command, "s/", 2) == 0) {
698 | char str[128]; // replace with the maximum length of your command
699 | strncpy(str, state->command+2, state->command_s-2);
700 | str[state->command_s-2] = '\0'; // ensure null termination
701 |
702 | char *token = strtok(str, "/");
703 | int count = 0;
704 | char args[2][100];
705 |
706 | while (token != NULL) {
707 | char temp_buffer[100];
708 | strcpy(temp_buffer, token);
709 | if(count == 0) {
710 | strcpy(args[0], temp_buffer);
711 | } else if(count == 1) {
712 | strcpy(args[1], temp_buffer);
713 | }
714 | count++;
715 |
716 | // log for args.
717 | token = strtok(NULL, "/");
718 | }
719 | index = search(buffer, args[0], strlen(args[0]));
720 | find_and_replace(buffer, state, args[0], args[1]);
721 | }
722 | buffer->cursor = index;
723 | state->config.mode = NORMAL;
724 | } break;
725 | case ctrl('s'): {
726 | handle_save(buffer);
727 | state->config.QUIT = 1;
728 | } break;
729 | case LEFT_ARROW:
730 | if(state->x > 1) state->x--;
731 | break;
732 | case DOWN_ARROW:
733 | break;
734 | case UP_ARROW:
735 | break;
736 | case RIGHT_ARROW:
737 | if(state->x < state->command_s) state->x++;
738 | break;
739 | case KEY_RESIZE:
740 | frontend_resize_window(state);
741 | break;
742 | default: {
743 | shift_str_right(state->command, &state->command_s, state->x);
744 | state->command[(state->x++)-1] = state->ch;
745 | } break;
746 | }
747 | }
748 |
749 | void handle_visual_keys(Buffer *buffer, Buffer **modify_buffer, State *state) {
750 | (void)modify_buffer;
751 | frontend_cursor_visible(0);
752 | switch(state->ch) {
753 | case ctrl('c'):
754 | case ESCAPE: {
755 | state->config.mode = NORMAL;
756 | frontend_cursor_visible(1);
757 | state->buffer->visual = (Visual){0};
758 | } break;
759 | case ENTER: break;
760 | case ctrl('s'): {
761 | handle_save(buffer);
762 | state->config.QUIT = 1;
763 | } break;
764 | case 'd':
765 | case 'x': {
766 | int cond = (buffer->visual.start > buffer->visual.end);
767 | size_t start = (cond) ? buffer->visual.end : buffer->visual.start;
768 | size_t end = (cond) ? buffer->visual.start : buffer->visual.end;
769 | CREATE_UNDO(INSERT_CHARS, start);
770 | buffer_delete_selection(buffer, state, start, end);
771 | undo_push(state, &state->undo_stack, state->cur_undo);
772 | state->config.mode = NORMAL;
773 | frontend_cursor_visible(1);
774 | } break;
775 | case '>': {
776 | int cond = (buffer->visual.start > buffer->visual.end);
777 | size_t start = (cond) ? buffer->visual.end : buffer->visual.start;
778 | size_t end = (cond) ? buffer->visual.start : buffer->visual.end;
779 | size_t position = buffer->cursor + state->config.indent;
780 | size_t row = index_get_row(buffer, start);
781 | size_t end_row = index_get_row(buffer, end);
782 | for(size_t i = row; i <= end_row; i++) {
783 | buffer_calculate_rows(buffer);
784 | buffer->cursor = buffer->rows.data[i].start;
785 | buffer_create_indent(buffer, state, 1);
786 | }
787 | buffer->cursor = position;
788 | state->config.mode = NORMAL;
789 | frontend_cursor_visible(1);
790 | } break;
791 | case '<': {
792 | int cond = (buffer->visual.start > buffer->visual.end);
793 | size_t start = (cond) ? buffer->visual.end : buffer->visual.start;
794 | size_t end = (cond) ? buffer->visual.start : buffer->visual.end;
795 | size_t row = index_get_row(buffer, start);
796 | size_t end_row = index_get_row(buffer, end);
797 | for(size_t i = row; i <= end_row; i++) {
798 | buffer_calculate_rows(buffer);
799 | buffer->cursor = buffer->rows.data[i].start;
800 | if(state->config.indent > 0) {
801 | buffer_delete_indent(state, state->config.indent);
802 | } else {
803 | buffer_delete_indent(state, 1);
804 |
805 | }
806 | }
807 | state->config.mode = NORMAL;
808 | frontend_cursor_visible(1);
809 | } break;
810 | case 'y': {
811 | reset_command(state->clipboard.str, &state->clipboard.len);
812 | int cond = (buffer->visual.start > buffer->visual.end);
813 | size_t start = (cond) ? buffer->visual.end : buffer->visual.start;
814 | size_t end = (cond) ? buffer->visual.start : buffer->visual.end;
815 | buffer_yank_selection(buffer, state, start, end);
816 | buffer->cursor = start;
817 | state->config.mode = NORMAL;
818 | frontend_cursor_visible(1);
819 | break;
820 | }
821 | default: {
822 | handle_motion_keys(buffer, state, state->ch, &state->repeating.repeating_count);
823 | if(buffer->visual.is_line) {
824 | buffer->visual.end = buffer->rows.data[buffer_get_row(buffer)].end;
825 | if(buffer->visual.start >= buffer->visual.end) {
826 | buffer->visual.end = buffer->rows.data[buffer_get_row(buffer)].start;
827 | buffer->visual.start = buffer->rows.data[index_get_row(buffer, buffer->visual.start)].end;
828 | }
829 | } else {
830 | buffer->visual.end = buffer->cursor;
831 | }
832 | } break;
833 | }
834 | }
835 |
--------------------------------------------------------------------------------
/src/keys.h:
--------------------------------------------------------------------------------
1 | #ifndef KEYS_H
2 | #define KEYS_H
3 |
4 | #include "defs.h"
5 | #include "frontend.h"
6 | #include "tools.h"
7 | #include "buffer.h"
8 |
9 | void handle_move_left(State *state, size_t num);
10 | void handle_move_right(State *state, size_t num);
11 | void handle_move_up(State *state, size_t num);
12 | void handle_move_down(State *state, size_t num);
13 | int handle_motion_keys(Buffer *buffer, State *state, int ch, size_t *repeating_count);
14 | int handle_modifying_keys(Buffer *buffer, State *state);
15 | int handle_normal_to_insert_keys(Buffer *buffer, State *state);
16 | void handle_normal_keys(Buffer *buffer, Buffer **modify_buffer, State *state);
17 | void handle_insert_keys(Buffer *buffer, Buffer **modify_buffer, State *state);
18 | void handle_command_keys(Buffer *buffer, Buffer **modify_buffer, State *state);
19 | void handle_search_keys(Buffer *buffer, Buffer **modify_buffer, State *state);
20 | void handle_visual_keys(Buffer *buffer, Buffer **modify_buffer, State *state);
21 |
22 | #endif
23 |
--------------------------------------------------------------------------------
/src/lex.c:
--------------------------------------------------------------------------------
1 | #include "lex.h"
2 | #include "defs.h"
3 |
4 | static char *types_old[] = {
5 | "char",
6 | "double",
7 | "float",
8 | "int",
9 | "long",
10 | "short",
11 | "void",
12 | "size_t",
13 | };
14 |
15 | static char *keywords_old[] = {
16 | "auto",
17 | "break",
18 | "case",
19 | "const",
20 | "continue",
21 | "default",
22 | "do",
23 | "else",
24 | "enum",
25 | "extern",
26 | "for",
27 | "goto",
28 | "if",
29 | "register",
30 | "return",
31 | "signed",
32 | "sizeof",
33 | "static",
34 | "struct",
35 | "switch",
36 | "typedef",
37 | "union",
38 | "unsigned",
39 | "volatile",
40 | "while",
41 | };
42 |
43 | static char **keywords;
44 | static size_t keywords_s = 0;
45 |
46 | static char **types;
47 | static size_t types_s = 0;
48 |
49 | #define NUM_KEYWORDS sizeof(keywords_old)/sizeof(*keywords_old)
50 | #define NUM_TYPES sizeof(types_old)/sizeof(*types_old)
51 |
52 | int is_keyword(char *word, size_t word_s) {
53 | for(size_t i = 0; i < keywords_s; i++) {
54 | ASSERT(keywords[i] != NULL, "keywords were NOT generated properly");
55 | if(word_s < strlen(keywords[i])) continue;
56 | if(strcmp(word, keywords[i]) == 0) return 1;
57 | }
58 | return 0;
59 | }
60 |
61 | int is_type(char *word, size_t word_s) {
62 | for(size_t i = 0; i < types_s; i++) {
63 | if(word_s < strlen(types[i])) continue;
64 | if(strcmp(word, types[i]) == 0) return 1;
65 | }
66 | return 0;
67 | }
68 |
69 | char *strip_off_dot(char *str, size_t str_s) {
70 | char *p = str + str_s;
71 |
72 | for (; p > str && *p != '.'; --p);
73 | return (p == str) ? NULL : strdup(++p);
74 | }
75 |
76 | size_t read_file_to_str(char *filename, char **contents) {
77 | FILE *file = fopen(filename, "r");
78 | if(file == NULL) {
79 | return 0;
80 | }
81 | fseek(file, 0, SEEK_END);
82 | size_t length = ftell(file);
83 | fseek(file, 0, SEEK_SET);
84 | *contents = malloc(sizeof(char)*length+1);
85 | fread(*contents, 1, length, file);
86 | fclose(file);
87 | contents[length] = '\0';
88 | return length;
89 | }
90 |
91 | Color_Arr parse_syntax_file(char *filename) {
92 | keywords_s = 0;
93 | types_s = 0;
94 |
95 | char *contents = NULL;
96 | size_t contents_s = read_file_to_str(filename, &contents);
97 | if(contents_s == 0) {
98 | return (Color_Arr){0};
99 | }
100 | String_View contents_view = view_create(contents, contents_s);
101 |
102 | size_t num_of_dots = 0;
103 | for(size_t i = 0; i < contents_view.len; i++) {
104 | if(contents_view.data[i] == '.') {
105 | num_of_dots++;
106 | }
107 | }
108 |
109 | Custom_Color *color_arr = malloc(sizeof(*color_arr)*num_of_dots);
110 | size_t arr_s = 0;
111 |
112 | String_View *lines = malloc(sizeof(String_View)*num_of_dots);
113 | size_t lines_s = 0;
114 |
115 | size_t cur_size = 0;
116 | char *cur = contents_view.data;
117 | for(size_t i = 0; i <= contents_view.len; i++) {
118 | cur_size++;
119 | if(i > 0 && contents_view.data[i-1] == '.') {
120 | lines[lines_s].data = cur;
121 | cur += cur_size;
122 | lines[lines_s++].len = cur_size;
123 | cur_size = 0;
124 | }
125 | }
126 |
127 | for(size_t i = 0; i < lines_s; i++) {
128 | size_t num_of_commas = 0;
129 | for(size_t j = 0; j < lines[i].len; j++) {
130 | if(lines[i].data[j] == ',') {
131 | num_of_commas++;
132 | }
133 | }
134 | String_View *words = malloc((num_of_commas + 1) * sizeof *words);
135 | size_t words_s = 0;
136 | char *cur = lines[i].data;
137 | size_t cur_size = 0;
138 | for(size_t j = 0; j < lines[i].len; j++) {
139 | cur_size++;
140 | if(lines[i].data[j] == ',') {
141 | words[words_s].data = cur;
142 | cur += cur_size;
143 | words[words_s++].len = cur_size-1;
144 | cur_size = 0;
145 | }
146 | }
147 | cur_size--;
148 | words[words_s].data = cur;
149 | cur += cur_size;
150 | words[words_s++].len = cur_size-1;
151 | if(words_s < 4) {
152 | return (Color_Arr){0};
153 | }
154 |
155 | Custom_Color color = {0};
156 | color.custom_id = i+8;
157 | char cur_type = words[0].data[0];
158 | color.custom_r = view_to_int(words[1]);
159 | color.custom_g = view_to_int(words[2]);
160 | color.custom_b = view_to_int(words[3]);
161 | if(cur_type == 'k') {
162 | if(words_s > 4) {
163 | keywords = malloc(sizeof(char*)*words_s-3);
164 | } else {
165 | keywords = keywords_old;
166 | keywords_s = NUM_KEYWORDS;
167 | }
168 | color.custom_slot = 4;
169 | } else if(cur_type == 't') {
170 | if(words_s > 4) {
171 | types = malloc(sizeof(char*)*words_s-3);
172 | } else {
173 | types = types_old;
174 | types_s = NUM_TYPES;
175 | }
176 | color.custom_slot = 1;
177 | } else if(cur_type == 'w') {
178 | color.custom_slot = 2;
179 | }
180 |
181 | for(size_t j = 4; j < words_s; j++) {
182 | switch(cur_type) {
183 | case 'k':
184 | keywords[keywords_s++] = view_to_cstr(view_trim_left(words[j]));
185 | break;
186 | case 't':
187 | types[types_s++] = view_to_cstr(view_trim_left(words[j]));
188 | break;
189 | default:
190 | break;
191 | }
192 | }
193 |
194 | color_arr[arr_s++] = color;
195 | }
196 | Color_Arr arr = {
197 | .arr = color_arr,
198 | .arr_s = arr_s,
199 | };
200 |
201 | free(lines);
202 | free(contents);
203 |
204 | return arr;
205 | }
206 |
207 | int is_in_tokens_index(Token *token_arr, size_t token_s, size_t index, size_t *size, Color_Pairs *color) {
208 | for(size_t i = 0; i < token_s; i++) {
209 | if(token_arr[i].index == index) {
210 | *size = token_arr[i].size;
211 | switch(token_arr[i].type) {
212 | case Type_None:
213 | break;
214 | case Type_Keyword:
215 | *color = RED_COLOR;
216 | break;
217 | case Type_Type:
218 | *color = YELLOW_COLOR;
219 | break;
220 | case Type_Preprocessor:
221 | *color = CYAN_COLOR;
222 | break;
223 | case Type_String:
224 | *color = MAGENTA_COLOR;
225 | break;
226 | case Type_Comment:
227 | *color = GREEN_COLOR;
228 | break;
229 | case Type_Word:
230 | *color = BLUE_COLOR;
231 | break;
232 | }
233 | return 1;
234 | }
235 | }
236 | return 0;
237 | }
238 |
239 | Token generate_word(String_View *view, char *contents) {
240 | size_t index = view->data - contents;
241 | char word[128] = {0};
242 | size_t word_s = 0;
243 | while(view->len > 0 && (isalpha(view->data[0]) || view->data[0] == '_')) {
244 | if(word_s >= 128) break;
245 | word[word_s++] = view->data[0];
246 | view->data++;
247 | view->len--;
248 | }
249 | view->data--;
250 | view->len++;
251 | if(is_keyword(word, word_s)) {
252 | return (Token){.type = Type_Keyword, .index = index, .size = word_s};
253 | } else if(is_type(word, word_s)) {
254 | return (Token){.type = Type_Type, .index = index, .size = word_s};
255 | } else {
256 | return (Token){.type = Type_Word, .index = index, .size = word_s};
257 | }
258 | return (Token){Type_None};
259 | }
260 |
261 | size_t generate_tokens(char *line, size_t line_s, Token *token_arr, size_t *token_arr_capacity) {
262 | size_t token_arr_s = 0;
263 |
264 | String_View view = view_create(line, line_s);
265 | view = view_trim_left(view);
266 | while(view.len > 0) {
267 | if(isalpha(view.data[0])) {
268 | Token token = generate_word(&view, line);
269 | if(token_arr_s >= *token_arr_capacity) {
270 | token_arr = realloc(token_arr, sizeof(Token)*(*token_arr_capacity)*2);
271 | *token_arr_capacity *= 2;
272 | }
273 | if(token.type != Type_None) {
274 | token_arr[token_arr_s++] = token;
275 | }
276 | } else if(view.data[0] == '#') {
277 | Token token = {
278 | .type = Type_Preprocessor,
279 | .index = view.data-line,
280 | .size = view.len,
281 | };
282 |
283 | while(view.len > 0 && view.data[0] != '\n') {
284 | view.len--;
285 | view.data++;
286 | }
287 | token_arr[token_arr_s++] = token;
288 | } else if(view.len >= 2 && view.data[0] == '/' && view.data[1] == '/') {
289 | Token token = {
290 | .type = Type_Comment,
291 | .index = view.data-line,
292 | .size = view.len,
293 | };
294 | while(view.len > 0 && view.data[0] != '\n') {
295 | view.len--;
296 | view.data++;
297 | }
298 | token_arr[token_arr_s++] = token;
299 | } else if(view.data[0] == '"') {
300 | Token token = {
301 | .type = Type_String,
302 | .index = view.data-line,
303 | };
304 | size_t string_s = 1;
305 | view.len--;
306 | view.data++;
307 | while(view.len > 0 && view.data[0] != '"') {
308 | if(view.len > 1 && view.data[0] == '\\') {
309 | string_s++;
310 | view.len--;
311 | view.data++;
312 | }
313 | string_s++;
314 | view.len--;
315 | view.data++;
316 | }
317 | token.size = ++string_s;
318 | token_arr[token_arr_s++] = token;
319 | } else if(view.data[0] == '\'') {
320 | Token token = {
321 | .type = Type_String,
322 | .index = view.data-line,
323 | };
324 | size_t string_s = 1;
325 | view.len--;
326 | view.data++;
327 | while(view.len > 0 && view.data[0] != '\'') {
328 | if(view.len > 1 && view.data[0] == '\\') {
329 | string_s++;
330 | view.len--;
331 | view.data++;
332 | }
333 | string_s++;
334 | view.len--;
335 | view.data++;
336 | }
337 | token.size = ++string_s;
338 | token_arr[token_arr_s++] = token;
339 | }
340 | if(view.len == 0) break;
341 | view.data++;
342 | view.len--;
343 | view = view_trim_left(view);
344 | }
345 | return token_arr_s;
346 | }
347 |
348 | int read_file_by_lines(char *filename, char ***lines, size_t *lines_s) {
349 | FILE *file = fopen(filename, "r");
350 | if(file == NULL) {
351 | return 1;
352 | }
353 | fseek(file, 0, SEEK_END);
354 | size_t length = ftell(file);
355 | fseek(file, 0, SEEK_SET);
356 | if(length == 0) {
357 | fclose(file);
358 | return 1;
359 | }
360 |
361 | char *contents =malloc(sizeof(char)*length);
362 | fread(contents, 1, length, file);
363 | fclose(file);
364 |
365 | size_t line_count = 0;
366 | for(size_t i = 0; i < length; i++) {
367 | if(contents[i] == '\n') line_count++;
368 | }
369 | free(*lines);
370 |
371 | char **new_lines = malloc(sizeof(*lines)*line_count);
372 |
373 | char current_line[128] = {0};
374 | size_t current_line_s = 0;
375 | for(size_t i = 0; i < length; i++) {
376 | if(contents[i] == '\n') {
377 | new_lines[*lines_s] = malloc(sizeof(char)*current_line_s+1);
378 | strncpy(new_lines[*lines_s], current_line, current_line_s+1);
379 | current_line_s = 0;
380 | memset(current_line, 0, current_line_s);
381 | *lines_s += 1;
382 | continue;
383 | }
384 | current_line[current_line_s++] = contents[i];
385 | }
386 |
387 | *lines = new_lines;
388 |
389 | free(contents);
390 | return 0;
391 | }
392 |
--------------------------------------------------------------------------------
/src/lex.h:
--------------------------------------------------------------------------------
1 | #ifndef LEX_H
2 | #define LEX_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "frontend.h"
10 | #include "view.h"
11 | #include "colors.h"
12 |
13 | typedef enum {
14 | Type_None = 0,
15 | Type_Keyword,
16 | Type_Type,
17 | Type_Preprocessor,
18 | Type_String,
19 | Type_Comment,
20 | Type_Word,
21 | } Token_Type;
22 |
23 | typedef struct {
24 | Token_Type type;
25 | size_t index;
26 | size_t size;
27 | } Token;
28 |
29 | int is_keyword(char *word, size_t word_s);
30 | int is_type(char *word, size_t word_s);
31 | char *strip_off_dot(char *str, size_t str_s);
32 | size_t read_file_to_str(char *filename, char **contents);
33 | Color_Arr parse_syntax_file(char *filename);
34 | int is_in_tokens_index(Token *token_arr, size_t token_s, size_t index, size_t *size, Color_Pairs *color);
35 | Token generate_word(String_View *view, char *contents);
36 | size_t generate_tokens(char *line, size_t line_s, Token *token_arr, size_t *token_arr_capacity);
37 | int read_file_by_lines(char *filename, char ***lines, size_t *lines_s);
38 |
39 | #endif // LEX_H
40 |
--------------------------------------------------------------------------------
/src/main.c:
--------------------------------------------------------------------------------
1 | #include "main.h"
2 |
3 | char *string_modes[MODE_COUNT] = {"NORMAL", "INSERT", "SEARCH", "COMMAND", "VISUAL"};
4 | _Static_assert(sizeof(string_modes)/sizeof(*string_modes) == MODE_COUNT, "Number of modes");
5 |
6 | char *get_help_page(char *page) {
7 | if (page == NULL) return NULL;
8 |
9 | #ifndef HELP_DIR
10 | #warning "Missing HELP_DIR definition"
11 |
12 | #define HELP_DIR "docs/help"
13 | #endif
14 |
15 | char *help_page;
16 | asprintf(&help_page, (HELP_DIR "/%s"), page);
17 |
18 | struct stat file_info;
19 | if (stat(help_page, &file_info) < 0 || !S_ISREG(file_info.st_mode))
20 | return NULL;
21 |
22 | return help_page;
23 | }
24 |
25 | void handle_flags(char *program, char **argv, int argc, char **config_filename, char **help_filename) {
26 | char *flag = NULL;
27 |
28 | struct option longopts[] = {
29 | {"help", no_argument, NULL, 'h'},
30 | {"config", optional_argument, NULL, 'c'},
31 | };
32 |
33 | char opt = cgetopt_long(argc, argv, "", longopts, NULL);
34 |
35 | while(true) {
36 | if(opt == -1) break;
37 | switch(opt) {
38 | case 'c':
39 | flag = optarg;
40 | if(flag == NULL) {
41 | fprintf(stderr, "usage: %s --config \n", program);
42 | exit(EXIT_FAILURE);
43 | }
44 | *config_filename = flag;
45 | break;
46 | case 'h':
47 | flag = optarg;
48 | if (flag == NULL) {
49 | *help_filename = get_help_page("general");
50 | } else {
51 | *help_filename = get_help_page(flag);
52 | }
53 |
54 | if (*help_filename == NULL) {
55 | fprintf(stderr, "Failed to open help page. Check for typos or if you installed cano properly.\n");
56 | exit(EXIT_FAILURE);
57 | }
58 | break;
59 | default:
60 | fprintf(stderr, "Unexpected flag");
61 | exit(EXIT_FAILURE);
62 | }
63 | opt = cgetopt_long(argc, argv, "", longopts, NULL);
64 | }
65 | }
66 |
67 | /* ------------------------- FUNCTIONS END ------------------------- */
68 |
69 | int main(int argc, char **argv) {
70 | WRITE_LOG("starting (int main)");
71 | // nice
72 | setlocale(LC_ALL, "");
73 | char *program = argv[0];
74 | char *config_filename = NULL, *syntax_filename = NULL, *help_filename = NULL;
75 | handle_flags(program, argv, argc, &config_filename, &help_filename);
76 |
77 | char *filename = argv[optind];
78 |
79 | // define functions based on current mode
80 | void(*key_func[MODE_COUNT])(Buffer *buffer, Buffer **modify_buffer, struct State *state) = {
81 | handle_normal_keys, handle_insert_keys, handle_search_keys, handle_command_keys, handle_visual_keys
82 | };
83 |
84 | State state = init_state();
85 | state.command = calloc(64, sizeof(char));
86 | state.key_func = key_func;
87 | state.files = calloc(32, sizeof(File));
88 | state.config.lang = "UNUSED";
89 | scan_files(&state, ".");
90 |
91 | frontend_init(&state);
92 |
93 | if(filename == NULL) filename = "out.txt";
94 |
95 | if (help_filename != NULL) {
96 | state.buffer = load_buffer_from_file(help_filename);
97 | free(help_filename);
98 | } else {
99 | state.buffer = load_buffer_from_file(filename);
100 | }
101 |
102 | load_config_from_file(&state, state.buffer, config_filename, syntax_filename);
103 |
104 | char status_bar_msg[128] = {0};
105 | state.status_bar_msg = status_bar_msg;
106 | buffer_calculate_rows(state.buffer);
107 |
108 | while(state.ch != ctrl('q') && state.config.QUIT != 1) {
109 | handle_cursor_shape(&state);
110 | state_render(&state);
111 | state.ch = frontend_getch(state.main_win);
112 | state.key_func[state.config.mode](state.buffer, &state.buffer, &state);
113 | }
114 |
115 | frontend_end();
116 |
117 | free_buffer(state.buffer);
118 | free_undo_stack(&state.undo_stack);
119 | free_undo_stack(&state.redo_stack);
120 | free_files(&state.files);
121 |
122 | free(state.command);
123 | if(syntax_filename) free(syntax_filename);
124 | if(config_filename) free(config_filename);
125 |
126 | return 0;
127 | }
128 |
--------------------------------------------------------------------------------
/src/main.h:
--------------------------------------------------------------------------------
1 | #ifndef MAIN_H
2 | #define MAIN_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 |
18 | #include "cgetopt.h"
19 | #include "defs.h"
20 | #include "colors.h"
21 | #include "lex.h"
22 | #include "commands.h"
23 | #include "frontend.h"
24 | #include "keys.h"
25 | #include "tools.h"
26 | #include "buffer.h"
27 |
28 | #define CREATE_UNDO(t, p) do { \
29 | Undo undo = {0}; \
30 | undo.type = (t); \
31 | undo.start = (p); \
32 | state->cur_undo = undo; \
33 | } while(0)
34 |
35 | /* --------------------------- FUNCTIONS --------------------------- */
36 |
37 | int main(int argc, char **argv);
38 |
39 | #endif // MAIN_H
40 |
--------------------------------------------------------------------------------
/src/tools.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "buffer.h"
5 | #include "commands.h"
6 | #include "tools.h"
7 |
8 | typedef int(* __compar_fn_t) (const void *, const void *);
9 |
10 | Data dynstr_to_data(Sized_Str str) {
11 | return (Data){
12 | .data = str.str,
13 | .count = str.len,
14 | .capacity = str.len,
15 | };
16 | }
17 |
18 | void handle_cursor_shape(State *state) {
19 | switch(state->config.mode) {
20 | case NORMAL:
21 | case VISUAL:
22 | case COMMAND:
23 | case SEARCH:
24 | system("echo -e -n \"\x1b[\x30 q\"");
25 | wrefresh(stdscr);
26 | break;
27 | case INSERT:
28 | system("echo -e -n \"\x1b[\x35 q\"");
29 | wrefresh(stdscr);
30 | break;
31 | case MODE_COUNT:
32 | default:
33 | ASSERT(false, "unreachable");
34 | }
35 | }
36 |
37 | void free_buffer(Buffer *buffer) {
38 | free(buffer->data.data);
39 | free(buffer->rows.data);
40 | free(buffer->filename);
41 | buffer->data.count = 0;
42 | buffer->rows.count = 0;
43 | buffer->data.capacity = 0;
44 | buffer->rows.capacity = 0;
45 | }
46 |
47 | void free_undo(Undo *undo) {
48 | free(undo->data.data);
49 | }
50 |
51 | void reset_command(char *command, size_t *command_s) {
52 | memset(command, 0, *command_s);
53 | *command_s = 0;
54 | }
55 |
56 | void free_undo_stack(Undo_Stack *undo) {
57 | for(size_t i = 0; i < undo->count; i++) {
58 | free_undo(&undo->data[i]);
59 | }
60 | free(undo->data);
61 | }
62 |
63 |
64 | void handle_save(Buffer *buffer) {
65 | FILE *file = fopen(buffer->filename, "w");
66 |
67 | if (file == NULL)
68 | return; // TODO: add proper uesr-feedback
69 |
70 | fwrite(buffer->data.data, buffer->data.count, sizeof(char), file);
71 | fclose(file);
72 | }
73 |
74 | Buffer *load_buffer_from_file(char *filename) {
75 | Buffer *buffer = calloc(1, sizeof(Buffer));
76 | size_t filename_s = strlen(filename)+1;
77 | buffer->filename = calloc(filename_s, sizeof(char));
78 | strncpy(buffer->filename, filename, filename_s);
79 | FILE *file = fopen(filename, "r");
80 | if(file == NULL) CRASH("Could not open file");
81 | fseek(file, 0, SEEK_END);
82 | size_t length = ftell(file);
83 | fseek(file, 0, SEEK_SET);
84 | buffer->data.count = length;
85 | buffer->data.capacity = (length+1)*2;
86 | buffer->data.data = calloc(buffer->data.capacity+1, sizeof(char));
87 | ASSERT(buffer->data.data != NULL, "buffer allocated properly");
88 | fread(buffer->data.data, length, 1, file);
89 | fclose(file);
90 | buffer_calculate_rows(buffer);
91 | return buffer;
92 | }
93 |
94 | void shift_str_left(char *str, size_t *str_s, size_t index) {
95 | for(size_t i = index; i < *str_s; i++) {
96 | str[i] = str[i+1];
97 | }
98 | *str_s -= 1;
99 | }
100 |
101 | void shift_str_right(char *str, size_t *str_s, size_t index) {
102 | *str_s += 1;
103 | for(size_t i = *str_s; i > index; i--) {
104 | str[i] = str[i-1];
105 | }
106 | }
107 |
108 | void undo_push(State *state, Undo_Stack *stack, Undo undo) {
109 | DA_APPEND(stack, undo);
110 | state->cur_undo = (Undo){0};
111 | }
112 |
113 | Undo undo_pop(Undo_Stack *stack) {
114 | if(stack->count <= 0) return (Undo){0};
115 | return stack->data[--stack->count];
116 | }
117 |
118 |
119 | Brace find_opposite_brace(char opening) {
120 | switch(opening) {
121 | case '(': return (Brace){.brace = ')', .closing = 0};
122 | case '{': return (Brace){.brace = '}', .closing = 0};
123 | case '[': return (Brace){.brace = ']', .closing = 0};
124 | case ')': return (Brace){.brace = '(', .closing = 1};
125 | case '}': return (Brace){.brace = '{', .closing = 1};
126 | case ']': return (Brace){.brace = '[', .closing = 1};
127 | default: return (Brace){.brace = '0'};
128 | }
129 | }
130 |
131 |
132 | int check_keymaps(Buffer *buffer, State *state) {
133 | (void)buffer;
134 | for(size_t i = 0; i < state->config.key_maps.count; i++) {
135 | if(state->ch == state->config.key_maps.data[i].a) {
136 | for(size_t j = 0; j < state->config.key_maps.data[i].b_s; j++) {
137 | state->ch = state->config.key_maps.data[i].b[j];
138 | state->key_func[state->config.mode](buffer, &buffer, state);
139 | }
140 | return 1;
141 | }
142 | }
143 | return 0;
144 | }
145 |
146 | int compare_name(File const *leftp, File const *rightp) {
147 | return strcoll(leftp->name, rightp->name);
148 | }
149 |
150 | void scan_files(State *state, char *directory) {
151 | DIR *dp = opendir(directory);
152 | if(dp == NULL) {
153 | WRITE_LOG("Failed to open directory: %s\n", directory);
154 | CRASH("Failed to open directory");
155 | }
156 |
157 | struct dirent *dent;
158 | while((dent = readdir(dp)) != NULL) {
159 | // Do not ignore .. in order to navigate back to the last directory
160 | if(strcmp(dent->d_name, ".") == 0) continue;
161 |
162 | char *path = calloc(256, sizeof(char));
163 | strcpy(path, directory);
164 | strcat(path, "/");
165 | strcat(path, dent->d_name);
166 |
167 | char *name = calloc(256, sizeof(char));
168 | strcpy(name, dent->d_name);
169 |
170 | if(dent->d_type == DT_DIR) {
171 | strcat(name, "/");
172 | DA_APPEND(state->files, ((File){name, path, true}));
173 | } else if(dent->d_type == DT_REG) {
174 | DA_APPEND(state->files, ((File){name, path, false}));
175 | }
176 | }
177 | closedir(dp);
178 | qsort(state->files->data, state->files->count,
179 | sizeof *state->files->data, (__compar_fn_t)&compare_name);
180 | }
181 |
182 | void free_files(Files **files) {
183 | for(size_t i = 0; i < (*files)->count; ++i) {
184 | free((*files)->data[i].name);
185 | free((*files)->data[i].path);
186 | }
187 | free((*files)->data);
188 | free(*files);
189 | }
190 |
191 | // TODO: breaks when a config is already loaded
192 | void load_config_from_file(State *state, Buffer *buffer, char *config_filename, char *syntax_filename) {
193 | char *config_dir;
194 |
195 | if(config_filename == NULL) {
196 | if (state->env == NULL) {
197 | char *env = getenv("HOME");
198 | if(env == NULL) CRASH("could not get HOME");
199 | state->env = env;
200 | }
201 |
202 | asprintf(&config_dir, "%s/.config/cano", state->env);
203 |
204 | struct stat st;
205 | if(stat(config_dir, &st) == -1)
206 | mkdir(config_dir, 0755);
207 |
208 | if (!S_ISDIR(st.st_mode))
209 | CRASH("a file conflict with the config directory.");
210 |
211 | asprintf(&config_filename, "%s/config.cano", config_dir);
212 |
213 | char *language = strip_off_dot(buffer->filename, strlen(buffer->filename));
214 | if(language != NULL) {
215 | asprintf(&syntax_filename, "%s/%s.cyntax", config_dir, language);
216 | free(language);
217 | }
218 | }
219 | char **lines = calloc(2, sizeof(char*));
220 | size_t lines_s = 0;
221 | int err = read_file_by_lines(config_filename, &lines, &lines_s);
222 | if(err == 0) {
223 | for(size_t i = 0; i < lines_s; i++) {
224 | size_t cmd_s = 0;
225 | Command_Token *cmd = lex_command(state, view_create(lines[i], strlen(lines[i])), &cmd_s);
226 | execute_command(buffer, state, cmd, cmd_s);
227 | free(lines[i]);
228 | }
229 | }
230 | free(lines);
231 |
232 | if(syntax_filename != NULL) {
233 | Color_Arr color_arr = parse_syntax_file(syntax_filename);
234 | if(color_arr.arr != NULL) {
235 | for(size_t i = 0; i < color_arr.arr_s; i++) {
236 | init_pair(color_arr.arr[i].custom_slot, color_arr.arr[i].custom_id, state->config.background_color);
237 | init_ncurses_color(color_arr.arr[i].custom_id, color_arr.arr[i].custom_r,
238 | color_arr.arr[i].custom_g, color_arr.arr[i].custom_b);
239 | }
240 |
241 | free(color_arr.arr);
242 | }
243 | }
244 | }
245 |
246 | int contains_c_extension(const char *str) {
247 | const char *extension = ".c";
248 | size_t str_len = strlen(str);
249 | size_t extension_len = strlen(extension);
250 |
251 | if (str_len >= extension_len) {
252 | const char *suffix = str + (str_len - extension_len);
253 | if (strcmp(suffix, extension) == 0) {
254 | return 1;
255 | }
256 | }
257 |
258 | return 0;
259 | }
260 |
261 | void *check_for_errors(void *args) {
262 | ThreadArgs *threadArgs = (ThreadArgs *)args;
263 |
264 | bool loop = 1; /* loop to be used later on, to make it constantly check for errors. Right now it just runs once. */
265 | while (loop) {
266 |
267 | char path[1035];
268 |
269 | /* Open the command for reading. */
270 | char command[1024];
271 | sprintf(command, "gcc %s -o /dev/null -Wall -Wextra -Werror -std=c99 2> errors.cano && echo $? > success.cano", threadArgs->path_to_file);
272 | FILE *fp = popen(command, "r");
273 | if (fp == NULL) {
274 | loop = 0;
275 | static char return_message[] = "Failed to run command";
276 | WRITE_LOG("Failed to run command");
277 | return (void *)return_message;
278 | }
279 | pclose(fp);
280 |
281 | FILE *should_check_for_errors = fopen("success.cano", "r");
282 |
283 | if (should_check_for_errors == NULL) {
284 | loop = 0;
285 | WRITE_LOG("Failed to open file");
286 | return (void *)NULL;
287 | }
288 | while (fgets(path, sizeof(path) -1, should_check_for_errors) != NULL) {
289 | WRITE_LOG("return code: %s", path);
290 | if (!(strcmp(path, "0") == 0)) {
291 | FILE *file_contents = fopen("errors.cano", "r");
292 | if (fp == NULL) {
293 | loop = 0;
294 | WRITE_LOG("Failed to open file");
295 | return (void *)NULL;
296 | }
297 |
298 | fseek(file_contents, 0, SEEK_END);
299 | long filesize = ftell(file_contents);
300 | fseek(file_contents, 0, SEEK_SET);
301 |
302 | char *buffer = malloc(filesize + 1);
303 | if (buffer == NULL) {
304 | WRITE_LOG("Failed to allocate memory");
305 | return (void *)NULL;
306 | }
307 | fread(buffer, 1, filesize, file_contents);
308 | buffer[filesize] = '\0';
309 |
310 | char *bufffer = malloc(filesize + 1);
311 |
312 | while (fgets(path, sizeof(path) -1, file_contents) != NULL) {
313 | strcat(bufffer, path);
314 | strcat(buffer, "\n");
315 | }
316 |
317 | char *return_message = malloc(filesize + 1);
318 | if (return_message == NULL) {
319 | WRITE_LOG("Failed to allocate memory");
320 | free(buffer);
321 | return (void *)NULL;
322 | }
323 | strcpy(return_message, buffer);
324 |
325 | free(buffer);
326 | loop = 0;
327 | fclose(file_contents);
328 |
329 | return (void *)return_message;
330 | }
331 | else {
332 | loop = 0;
333 | static char return_message[] = "No errors found";
334 | return (void *)return_message;
335 | }
336 | }
337 |
338 | }
339 |
340 | return (void *)NULL;
341 | }
342 |
343 |
344 | Ncurses_Color rgb_to_ncurses(int r, int g, int b) {
345 |
346 | Ncurses_Color color = {0};
347 |
348 | color.r = (int) ((r / 256.0) * 1000);
349 | color.g = (int) ((g / 256.0) * 1000);
350 | color.b = (int) ((b / 256.0) * 1000);
351 | return color;
352 |
353 | }
354 |
355 | void init_ncurses_color(int id, int r, int g, int b) {
356 | Ncurses_Color color = rgb_to_ncurses(r, g, b);
357 | init_color(id, color.r, color.g, color.b);
358 | }
359 |
--------------------------------------------------------------------------------
/src/tools.h:
--------------------------------------------------------------------------------
1 | #ifndef TOOLS_H
2 | #define TOOLS_H
3 |
4 | #include "defs.h"
5 | #include "colors.h"
6 | #include "lex.h"
7 |
8 | Data dynstr_to_data(Sized_Str str);
9 | void handle_cursor_shape(State *state);
10 | void free_buffer(Buffer *buffer);
11 | void free_undo(Undo *undo);
12 | void free_undo_stack(Undo_Stack *undo);
13 | void handle_save(Buffer *buffer);
14 | Buffer *load_buffer_from_file(char *filename);
15 | void shift_str_left(char *str, size_t *str_s, size_t index);
16 | void shift_str_right(char *str, size_t *str_s, size_t index);
17 | void undo_push(State *state, Undo_Stack *stack, Undo undo);
18 | Undo undo_pop(Undo_Stack *stack);
19 | Brace find_opposite_brace(char opening);
20 | int check_keymaps(Buffer *buffer, State *state);
21 | void scan_files(State *state, char *directory);
22 | void free_files(Files **files);
23 | void load_config_from_file(State *state, Buffer *buffer, char *config_filename, char *syntax_filename);
24 | int contains_c_extension(const char *str);
25 | void *check_for_errors(void *args);
26 | Ncurses_Color rgb_to_ncurses(int r, int g, int b);
27 | void init_ncurses_color(int id, int r, int g, int b);
28 | void reset_command(char *command, size_t *command_s);
29 |
30 | #endif // TOOLS_H
31 |
--------------------------------------------------------------------------------
/src/view.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "view.h"
6 |
7 | int view_cmp(String_View a, String_View b) {
8 | return (a.len != b.len) ? 0 : !strncmp(a.data, b.data, a.len);
9 | }
10 |
11 | char *view_to_cstr(String_View view) {
12 | char *str = malloc(sizeof(char) * view.len+1);
13 | strncpy(str, view.data, view.len);
14 | str[view.len] = '\0';
15 | return str;
16 | }
17 |
18 | String_View view_trim_left(String_View view) {
19 | size_t i = 0;
20 |
21 | while(i < view.len && isspace(view.data[i]))
22 | i++;
23 | return view_create(view.data + i, view.len - i);
24 | }
25 |
26 | String_View view_trim_right(String_View view) {
27 | size_t i = view.len - 1;
28 |
29 | while(i < view.len && isspace(view.data[i]))
30 | i--;
31 | return view_create(view.data, i + 1);
32 | }
33 |
34 | int view_contains(String_View haystack, String_View needle) {
35 | if(needle.len > haystack.len) return 0;
36 | String_View compare = view_create(haystack.data, needle.len);
37 | for(size_t i = 0; i < haystack.len; i++) {
38 | compare.data = haystack.data + i;
39 | if(view_cmp(needle, compare))
40 | return 1;
41 | }
42 | return 0;
43 | }
44 |
45 | size_t view_first_of(String_View view, char target) {
46 | for(size_t i = 0; i < view.len; i++)
47 | if(view.data[i] == target)
48 | return i;
49 | return 0;
50 | }
51 |
52 | size_t view_last_of(String_View view, char target) {
53 | for(size_t i = view.len-1; i > 0; i--)
54 | if(view.data[i] == target)
55 | return i;
56 | return 0;
57 | }
58 |
59 | size_t view_split(String_View view, char c, String_View *arr, size_t arr_s) {
60 | char *cur = view.data;
61 | size_t arr_index = 0;
62 | size_t i;
63 |
64 | for(i = 0; i < view.len; i++) {
65 | if(view.data[i] == c) {
66 | if(arr_index < arr_s-2) {
67 | arr[arr_index++] = view_create(cur, view.data + i - cur);
68 | cur = view.data + i + 1;
69 | } else {
70 | arr[arr_index++] = view_create(view.data + i+1, view.len - i-1);
71 | return arr_index;
72 | }
73 | }
74 | }
75 | arr[arr_index++] = view_create(cur, view.data + i - cur);
76 | return arr_index;
77 | }
78 |
79 | String_View view_chop(String_View view, char c) {
80 | size_t i = 0;
81 |
82 | while(view.data[i] != c && i != view.len)
83 | i++;
84 | if(i < view.len)
85 | i++;
86 | return view_create(view.data + i, view.len - i);
87 | }
88 |
89 | String_View view_rev(String_View view, char *data, size_t data_s) {
90 | if(view.len >= data_s)
91 | return view_create(NULL, 0);
92 | String_View result = view_create(data, view.len);
93 |
94 | for(int i = view.len-1; i >= 0; i--)
95 | result.data[view.len-1 - i] = view.data[i];
96 | return result;
97 | }
98 |
99 | size_t view_find(String_View haystack, String_View needle) {
100 | if(needle.len > haystack.len) return 0;
101 | String_View compare = view_create(haystack.data, needle.len);
102 | for(size_t i = 0; i < haystack.len; i++) {
103 | compare.data = haystack.data + i;
104 | if(view_cmp(needle, compare))
105 | return i;
106 | }
107 | return 0;
108 | }
109 |
110 | int view_to_int(String_View view) {
111 | return strtol(view.data, NULL, 10);
112 | }
113 |
114 | float view_to_float(String_View view) {
115 | return strtof(view.data, NULL);
116 | }
117 |
--------------------------------------------------------------------------------
/src/view.h:
--------------------------------------------------------------------------------
1 | #ifndef VIEW_H
2 | #define VIEW_H
3 |
4 | #include
5 |
6 | typedef struct {
7 | char *data;
8 | size_t len;
9 | } String_View;
10 |
11 | #define View_Print "%.*s"
12 | #define View_Arg(view) (int)view.len, view.data
13 |
14 | #define LITERAL_CREATE(lit) view_create(lit, sizeof(lit)-1)
15 |
16 | static inline
17 | String_View view_create(char *str, size_t len) {
18 | return (String_View){ .data = str, .len = len };
19 | }
20 |
21 | int view_cmp(String_View a, String_View b);
22 |
23 | static inline
24 | int view_starts_with_s(String_View a, String_View b) {
25 | return view_cmp(view_create(a.data, b.len), b);
26 | }
27 |
28 | static inline
29 | int view_ends_with_s(String_View a, String_View b) {
30 | return view_cmp(view_create(a.data + a.len - b.len, b.len), b);
31 | }
32 |
33 | #define view_starts_with_c(view, c) ((view).data[0] == (c))
34 | #define view_ends_with_c(view, c) ((view).data[(view).len-1] == (c));
35 |
36 | char *view_to_cstr(String_View view);
37 | String_View view_trim_left(String_View view);
38 | String_View view_trim_right(String_View view);
39 | int view_contains(String_View haystack, String_View needle);
40 | size_t view_first_of(String_View view, char target);
41 | size_t view_last_of(String_View view, char target);
42 | size_t view_split(String_View view, char c, String_View *arr, size_t arr_s);
43 | String_View view_chop(String_View view, char c);
44 | String_View view_rev(String_View view, char *data, size_t data_s);
45 | size_t view_find(String_View haystack, String_View needle);
46 | int view_to_int(String_View view);
47 | float view_to_float(String_View view);
48 |
49 | #endif // VIEW_H
50 |
--------------------------------------------------------------------------------