├── .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 | ![Cano icon](cano.png) \ 5 | Icon made by [LocalTexan](https://github.com/LocalTexan) 6 | 7 | # Demo 8 | [![asciicast](https://asciinema.org/a/655184.svg)](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 | [![Packaging status](https://repology.org/badge/vertical-allrepos/cano.svg)](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 | --------------------------------------------------------------------------------