├── imgs
└── screen.png
├── Makefile
├── README.md
├── LICENSE
└── src
├── main.asm
├── model.asm
└── view.asm
/imgs/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zeal8bit/Zepto/HEAD/imgs/screen.png
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := /bin/bash
2 |
3 | SRCS = main.asm model.asm view.asm
4 | BIN = zepto.bin
5 |
6 | # Directory where source files are and where the binaries will be put
7 | INPUT_DIR = src
8 | OUTPUT_DIR = bin
9 |
10 | # Include directory containing Zeal 8-bit OS header file.
11 | ifndef ZOS_PATH
12 | $(error "Please define ZOS_PATH environment variable. It must point to Zeal 8-bit OS source code path.")
13 | endif
14 |
15 | ZOS_INCLUDE = $(ZOS_PATH)/kernel_headers/z88dk-z80asm/
16 |
17 | # Assembler binary name
18 | ASM = z88dk-z80asm
19 | # Assembler flags
20 | ASMFLAGS = -m -b -I$(ZOS_INCLUDE) -O$(OUTPUT_DIR)
21 |
22 | # If z88dk has been install through snap, the binary may be prefixed with "z88dk"
23 | # So choose any of z88dk-dis or z88dk.z88dk-dis, as long as one exists
24 | DISASSEMBLER=$(shell which z88dk-dis z88dk.z88dk-dis | head -1)
25 |
26 | # Mark version.txt as PHONY to force generating it every time
27 | .PHONY: all version.txt
28 |
29 | all: version.txt $(OUTPUT_DIR) $(BIN)
30 |
31 | version.txt:
32 | git describe --always | tr "\n" " " > $@
33 |
34 | $(BIN): $(addprefix $(INPUT_DIR)/, $(SRCS))
35 | $(ASM) $(ASMFLAGS) -o$@ $^
36 | mv $(OUTPUT_DIR)/*_TEXT.bin $(OUTPUT_DIR)/$@
37 | $(DISASSEMBLER) -mz80 -o 0x4000 -x $(OUTPUT_DIR)/zepto.map $(OUTPUT_DIR)/$@ > $(OUTPUT_DIR)/zepto.dump
38 |
39 | $(OUTPUT_DIR):
40 | mkdir -p $@
41 |
42 | clean:
43 | rm -rf bin/ version.txt
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Zepto text editor
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Presenting Zepto: A clone of nano entirely written in Z80 assembly, for Zeal 8-bit OS
10 |
11 |
12 |
13 | ## About Zepto
14 |
15 | The goal of Zepto is to provide a text editor for [Zeal 8-bit OS](https://github.com/Zeal8bit/Zeal-8-bit-OS) that is complete enough to write some code of text files, without taking too much disk space.
16 | This is why I decided to make a clone of the famous `nano` text editor present on Linux. The problem is `nano` is written in C and is tied to the libC and Linux kernel. As such, I decided to write a clone from scratch, in Z80 assembly, to minimize the space and time it will take.
17 |
18 | Thus, `nano` was only used as a source of **inspiration**, its code was not used **at all** in this project.
19 |
20 | ## Features and limitations
21 |
22 | Zepto is still in alpha, here are the features implemented:
23 |
24 | * Less than **4KB** once compiled!
25 | * Use Zeal 8-bit OS cursor and keyboard API, so completely portable
26 | * Use *Gap Buffer* data structure to avoid line length limitation
27 | * Use Controller-Model-View architecture
28 | * Open an existing file given as a parameter
29 | * Edit the file content, can split lines, can add characters at the beginning, at the middle and at the end of lines
30 | * Support upper, lower case and special characters
31 | * Support scrolling up and down in the file
32 | * Support `Ctrl + Key` for menu selection
33 | * Support `Location` option
34 | * Support `Save file` option
35 | * Support `Exit` option
36 | * Support "Save file on exit" question
37 |
38 | Regarding the current limitations:
39 |
40 | * Screen size is hardcoded to 80x40 characters. In the future, it should use Zeal 8-bit OS video API to get the screen area size
41 | * Doesn't support memory paging, so the maximum file size is limited by the amount of RAM mapped at once: around 40KB
42 | * No way to remove an empty line. Once a new line is created, it cannot be removed, it'll always be in the file
43 | * It is not possible to modify the file destination once the file is opened. For example, if the file `B:/file.txt` is opened, it is not possible to save it under a new name
44 | * Copy/Paste/Cut/Help/Open File/New File are not implemented
45 |
46 | ## Building the project
47 |
48 | ### Requirements
49 |
50 | At the moment, the project has only been assembled on Linux (Ubuntu 20.04 and 22.04), it should be compatible with Mac OS and Windows as long as you have:
51 |
52 | * bash
53 | * git (to clone this repo)
54 | * make
55 | * Zeal 8-bit OS source code. Only the `kernel_headers` directory is required
56 | * Zeal 8-bit OS v0.3.0 or above on the target computer. The older versions don't implement cursor and video APIs
57 | * z88dk v2.2 (or later). Only its assembler, `z80asm`, is strictly required. The latest version of `z80asm` must be used as earlier versions don't have support for `MACRO`.
58 |
59 | To install z88dk, please [check out their Github project](https://github.com/z88dk/z88dk).
60 |
61 | ### Building
62 |
63 | To build the program, define the path to Zeal 8-bit OS, this will let us find the header files used to assemble zepto:
64 | ```
65 | export ZOS_PATH=/your/path/to/Zeal8bitOS
66 | ```
67 | Then simply use the command:
68 | ```
69 | make
70 | ```
71 |
72 | After compiling, the folder `bin/` should contain the binary `zepto.bin`. This file can be then loaded to Zeal 8-bit OS through UART thanks to the `load` command.
73 |
74 | The binary can also be embedded within the `romdisk` that will contain both the OS and a read-only file system. For example:
75 | ```
76 | cd $ZOS_PATH
77 | export EXTRA_ROMDISK_FILES="/path/to/zepto/bin/zepto.bin"
78 | make
79 | ```
80 |
81 | More info about compiling Zeal 8-bit OS [here](https://github.com/Zeal8bit/Zeal-8-bit-OS#getting-started).
82 |
83 | The resulted ROM image can then be provided to an emulator or directly flashed to the computer's ROM that will use it.
84 |
85 | ## Usage
86 |
87 | If you are using Zeal 8-bit OS default romdisk, you can use `exec` command to execute `zepto.bin` or directly use `./`, followed by the source/destination file:
88 | ```
89 | A:/>exec zepto.bin B:/your_file.txt
90 | ```
91 | or
92 | ```
93 | A:/>./zepto.bin B:/your_file.txt
94 | ```
95 |
96 | As said above, keep in mind that once opened, the file cannot be renamed or saved somewhere else, so make sure you specify a destination disk that is **write-read**! (`A:/` disk isn't by default)
97 |
98 |
99 | ## Contact
100 |
101 | For any suggestion or request, you can contact me at contact [at] zeal8bit [dot] com
102 |
103 | Or, you can join Zeal 8-bit projects [Discord server](https://discord.gg/UzEjwRvBBb).
104 |
105 | For features requests, you can also open an issue or a pull request.
106 |
107 | ## Contributing
108 |
109 | Contributions are welcome! Feel free to fix any bug that you may see or encounter, or implement any feature that you find important.
110 |
111 | To contribute:
112 | * Fork the Project
113 | * Create your feature Branch (*optional*)
114 | * Commit your changes. Please make a clear and concise commit message (*)
115 | * Push to the branch
116 | * Open a Pull Request
117 |
118 |
119 | (*) A good commit message is as follow:
120 | ```
121 | Module: add/fix/remove a from b
122 |
123 | Explanation on what/how/why
124 | ```
125 | For example:
126 | ```
127 | Flash: add the possibility to flash programs bigger than 48KB
128 | ```
129 |
130 | ## License
131 |
132 | Distributed under the Apache 2.0 License. See `LICENSE` file for more information.
133 |
134 | You are free to use it for personal and commercial use, the boilerplate present in each file must not be removed.
135 |
--------------------------------------------------------------------------------
/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 [yyyy] [name of copyright owner]
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 |
--------------------------------------------------------------------------------
/src/main.asm:
--------------------------------------------------------------------------------
1 | ; SPDX-FileCopyrightText: 2023 Zeal 8-bit Computer
2 | ;
3 | ; SPDX-License-Identifier: Apache-2.0
4 |
5 | INCLUDE "zos_sys.asm"
6 | INCLUDE "zos_keyboard.asm"
7 |
8 | ; Linker script part
9 | SECTION TEXT
10 | ORG 0x4000
11 | SECTION DATA
12 | SECTION BUFFER
13 | ; Program part
14 |
15 |
16 | EXTERN editor_set_end_memory
17 | EXTERN editor_view_init
18 | EXTERN editor_model_init
19 | EXTERN model_show_location
20 | EXTERN model_save_file
21 | EXTERN _buffer_insert_char_at_cursor
22 | EXTERN _buffer_insert_new_line
23 | EXTERN _buffer_remove_char_at_cursor
24 | EXTERN _buffer_previous_char
25 | EXTERN _buffer_next_char
26 | EXTERN _buffer_previous_line
27 | EXTERN _buffer_next_line
28 | EXTERN editor_show_notification
29 |
30 | DEFC FLAGS_CAPS_MASK = 1 << 0
31 | DEFC FLAGS_SHIFT_MASK = 1 << 1
32 | DEFC FLAGS_CTRL_MASK = 1 << 2
33 |
34 | ; Entry point of the program
35 | ; Parameters:
36 | ; DE - String parameter (NULL-terminated), must be ignored if BC is 0
37 | ; BC - Length of the string parameter
38 | SECTION TEXT
39 | _start:
40 | ; Before processing any parameter, let's estimate the size of free RAM we have.
41 | ; As this program can be executed on a target than has no MMU, the stack pointer
42 | ; may not be 0xFFFF. So, we need to calculate the last byte address of memory.
43 | ld hl, 0
44 | add hl, sp
45 | ; Allocate 256 bytes for the stack, should be enough, if any problem is encountered, it
46 | ; should be increased.
47 | dec h
48 | dec hl
49 | call editor_set_end_memory
50 | ; Process the argument if any
51 | call process_argument
52 | push de
53 | ; Start by drawing the menu bar
54 | call editor_view_init
55 | pop de
56 | push de
57 | call editor_model_init
58 | call _editor_controller_init
59 | ; If a file was provided, no need to write the test data
60 | pop de
61 | ld a, d
62 | or e
63 | jp _editor_main_loop
64 |
65 | ; Draw the different element of the editor, they must be updated after each action
66 | ; of the user
67 | ld ix, _controller_print_printable
68 | ld iy, _editor_event_process_special_ascii
69 | _editor_main_loop:
70 | ; Load the routine to jump to in IX if the character received is a printable one
71 | ; and the one to jump to if it is a special ASCII character, in IY
72 | ld ix, _controller_print_printable
73 | ld iy, _editor_event_process_special_ascii
74 | call _editor_wait_for_event
75 | jp _editor_main_loop
76 |
77 |
78 | ; Parameters:
79 | ; DE - Address of the NULL-terminated string
80 | ; BC - Length of the string
81 | ; Returns:
82 | ; DE - Processed argument, NULL if not valid
83 | process_argument:
84 | ld a, b
85 | or c
86 | jr z, process_argument_error
87 | ; Left trim the parameter
88 | ex de, hl
89 | call ltrim
90 | jr z, process_argument_error
91 | ; If the argument is made out of several argument, split and keep the first
92 | push hl
93 | call strsep
94 | pop de
95 | ret
96 | process_argument_error:
97 | ; Set DE to 0
98 | ld d, a
99 | ld e, a
100 | ret
101 |
102 | ; Parameters:
103 | ; HL - Address of the string to separate
104 | ; Alters:
105 | ; A, HL
106 | strsep:
107 | inc hl
108 | ld a, (hl)
109 | or a
110 | ret z
111 | sub ' '
112 | jp nz, strsep
113 | ; Character is space, replace it with 0 (A)
114 | ld (hl), a
115 | ret
116 |
117 | ; Parameters:
118 | ; HL - Address of the string to trim
119 | ; Returns:
120 | ; HL - Address of the first non-space character
121 | ; Z - The resulted string is empty
122 | ; NZ - Resulted string is not empty
123 | ltrim:
124 | ld a, (hl)
125 | or a
126 | ret z
127 | cp ' '
128 | ret nz
129 | inc hl
130 | jp ltrim
131 |
132 |
133 | ; Routine returning the length of a NULL-terminated string
134 | ; Parameters:
135 | ; HL - NULL-terminated string to get the length from
136 | ; Returns:
137 | ; BC - Length of the string
138 | ; Alters:
139 | ; A, BC
140 | PUBLIC strlen
141 | strlen:
142 | push hl
143 | xor a
144 | ld b, a
145 | ld c, a
146 | _strlen_loop:
147 | cp (hl)
148 | jr z, _strlen_end
149 | inc hl
150 | inc bc
151 | jr _strlen_loop
152 | _strlen_end:
153 | pop hl
154 | ret
155 |
156 |
157 | ; Function copying src string into dest, including the terminating null byte
158 | ; At most BC characters are copied
159 | PUBLIC strncpy
160 | strncpy:
161 | _strncpy_loop:
162 | ld a, (hl)
163 | ldi
164 | or a
165 | ret z
166 | ld a, b
167 | or c
168 | ret z
169 | jp _strncpy_loop
170 |
171 |
172 | ; Return the basename of a path
173 | ; Parameters:
174 | ; HL - Address of the file path, which can be relative or absolute
175 | ; Returns:
176 | ; HL - Address of the file basename
177 | ; Alters:
178 | ; A, HL, BC
179 | PUBLIC basename
180 | basename:
181 | ; BC will contain the address of the last slash + 1
182 | ld b, h
183 | ld c, l
184 | _basename_loop:
185 | ld a, (hl)
186 | inc hl
187 | or a
188 | jr z, _basename_end
189 | cp '/'
190 | jr nz, _basename_loop
191 | ; Found a slash, copy the current incremented HL to BC
192 | ld b, h
193 | ld c, l
194 | jr _basename_loop
195 | _basename_end:
196 | ; Copy the basename address in HL and return
197 | ld h, b
198 | ld l, c
199 | ret
200 |
201 |
202 | ;=========================================================;
203 | ;================== CONTROLLER ==========================;
204 | ;=========================================================;
205 |
206 | _editor_controller_init:
207 | ; Set STDIN to raw input
208 | ld h, DEV_STDIN
209 | ld c, KB_CMD_SET_MODE
210 | ld e, KB_MODE_RAW
211 | IOCTL()
212 | or a
213 | ret z
214 | ; Error, the target doesn't support RAW mode
215 | S_WRITE3(DEV_STDOUT, _editor_raw_err_str, _editor_raw_err_str_end - _editor_raw_err_str)
216 | EXIT()
217 | _editor_raw_err_str:
218 | DEFM "Could not switch input to RAW mode\n"
219 | _editor_raw_err_str_end:
220 |
221 |
222 | is_print:
223 | ; Printable characters are above 0x20 (space) and below 0x7F
224 | cp ' '
225 | ret c
226 | cp 0x7F
227 | ccf
228 | ret
229 |
230 |
231 | _editor_wait_for_event:
232 | ; Get the key pressed from the keyboard
233 | S_READ3(DEV_STDIN, _editor_keys, _editor_keys_end - _editor_keys)
234 | _event_received:
235 | ld a, b
236 | or c
237 | jr z, _editor_wait_for_event
238 | _editor_event_next:
239 | ; We received at least one character, process it
240 | ld a, (de)
241 | inc de
242 | cp KB_RELEASED
243 | jr nz, _editor_event_not_ignore
244 | ; Make the assumption that the released key follows directly
245 | dec c
246 | ld a, (de)
247 | call controller_check_if_special
248 | ret z
249 | inc de
250 | dec c
251 | ret z
252 | jp _editor_event_next
253 | _editor_event_not_ignore:
254 | push bc
255 | push de
256 | call _editor_event_process_char
257 | pop de
258 | pop bc
259 | dec c
260 | jp nz, _editor_event_next
261 | ret
262 |
263 |
264 | ; Parameters:
265 | ; A - Character to test
266 | ; Returns:
267 | ; A - 0 if the character is not special
268 | ; > 0 if it is
269 | controller_check_if_special:
270 | cp KB_LEFT_SHIFT
271 | jr z, _controller_toggle_shift
272 | cp KB_RIGHT_SHIFT
273 | jr z, _controller_toggle_shift
274 | cp KB_LEFT_CTRL
275 | jr z, _controller_toggle_ctrl
276 | cp KB_RIGHT_CTRL
277 | jr z, _controller_toggle_ctrl
278 | ; Return 0, it was not a special character
279 | xor a
280 | ret
281 |
282 | _controller_toggle_ctrl:
283 | ld a, FLAGS_CTRL_MASK
284 | jr _controller_toggle
285 | _controller_toggle_shift:
286 | ld a, FLAGS_SHIFT_MASK
287 | jr _controller_toggle
288 | _controller_toggle_caps:
289 | ld a, FLAGS_CAPS_MASK
290 | _controller_toggle:
291 | ld hl, _editor_flags
292 | xor (hl)
293 | ld (hl), a
294 | ret
295 |
296 |
297 |
298 | ; Process the keyboard key stored in A register:
299 | ; Parameters:
300 | ; A - Key code to process
301 | ; IX - Routine to jump to if A is a printable character
302 | ; IY - Routine to jump to if A is a special ASCII character
303 | ; Returns:
304 | ; None
305 | ; Alters:
306 | ; A, BC, DE, HL
307 | _editor_event_process_char:
308 | ; Copy character in B
309 | ld b, a
310 | call is_print
311 | ; C flag not set is A is printable
312 | ; jp nc, _controller_print_printable
313 | jp c, _editor_event_process_not_printable
314 | jp (ix)
315 | _editor_event_process_not_printable:
316 | cp KB_CAPS_LOCK
317 | jr z, _controller_toggle_caps
318 | ; Check if the character is special, i.e. CTRL or SHIFT
319 | call controller_check_if_special
320 | ; Return A > 0 if it was, we can return directly then
321 | or a
322 | ; This RET will be replaced by the routines that need to receive user input without interpreting them
323 | ret nz
324 | jp (iy)
325 |
326 | ; Jump to this branch if the keyboard key/character to process is an ASCII
327 | ; special key, such as newline, backspace, arrows, etc...
328 | ; Parameters:
329 | ; B - ASCII special character
330 | _editor_event_process_special_ascii:
331 | ; Put back the character value in A
332 | ld a, b
333 | cp KB_KEY_TAB
334 | jr z, _controller_insert_tab
335 | cp KB_KEY_ENTER
336 | jp z, _buffer_insert_new_line
337 | cp KB_KEY_BACKSPACE
338 | jp z, _buffer_remove_char_at_cursor
339 | cp KB_LEFT_ARROW
340 | jp z, _buffer_previous_char
341 | cp KB_RIGHT_ARROW
342 | jp z, _buffer_next_char
343 | cp KB_UP_ARROW
344 | jp z, _buffer_previous_line
345 | cp KB_DOWN_ARROW
346 | jp z, _buffer_next_line
347 | ret
348 |
349 | _controller_insert_tab:
350 | ld b, ' '
351 | call _buffer_insert_char_at_cursor
352 | ld b, ' '
353 | jp _buffer_insert_char_at_cursor
354 |
355 |
356 | ; A, B - Character to print
357 | _controller_print_printable:
358 | ; If caps lock XOR shift is 1, look for the alternate keys
359 | ld hl, _editor_flags
360 | ld a, (hl)
361 | ; If CTRL key is pressed, interpret the key differently
362 | and FLAGS_CTRL_MASK
363 | jr nz, _controller_ctrl_combination
364 | ld a, (hl)
365 | ; Put LSB in D
366 | ld d, 0
367 | rrca
368 | rl d
369 | and 1
370 | xor d
371 | ; If the result is 0, no need to look for the alternate set
372 | jp z, _buffer_insert_char_at_cursor
373 | ; Else, we have to get the alternate set of keys
374 | ld a, b
375 | ; Check if it starts before or after 0x5B
376 | cp 0x5b
377 | jp c, _controller_print_printable_before
378 | ld hl, alternate_key_set_from_bracket
379 | sub 0x5b
380 | jp _controller_print_printable_table
381 | _controller_print_printable_before:
382 | ld hl, alternate_key_set_from_space
383 | sub 0x20
384 | _controller_print_printable_table:
385 | add l
386 | ld l, a
387 | adc h
388 | sub l
389 | ld h, a
390 | ld b, (hl)
391 | jp _buffer_insert_char_at_cursor
392 |
393 |
394 | ; A CTRL + Key combination was pressed, interpret it here
395 | ; Parameter:
396 | ; B - Character pressed with CTRL (printable char)
397 | ; Returns:
398 | ; -
399 | ; Alters:
400 | ; A, BC, DE, HL
401 | _controller_ctrl_combination:
402 | ld a, b
403 | cp KB_KEY_L
404 | jp z, model_show_location
405 | cp KB_KEY_S
406 | jp z, model_save_file
407 | cp KB_KEY_K
408 | jr z, _controller_ask_and_exit
409 | ; Unsupported feature
410 | ld hl, str_unsupported
411 | jp editor_show_notification
412 |
413 |
414 | ; Routine called before exiting, it will ask the user whether he wants to save the file or not
415 | _controller_ask_and_exit:
416 | ; User wants to exit the program, ask whether to save the file or not
417 | call controller_ask_save_file
418 | ; If A is 0, do not save, simply exit
419 | ; If A is 1, save the file
420 | ; If A is 2, cancel
421 | or a
422 | jr z, controller_exit
423 | dec a
424 | jp z, controller_save_exit
425 | controller_cancelled:
426 | ; Exit cancelled, print a message and continue
427 | ld hl, str_cancelled
428 | jp editor_show_notification
429 | controller_save_exit:
430 | call model_save_file
431 | controller_exit:
432 | EXIT()
433 |
434 | ; Ask the user whether he wants to save the file or not
435 | ; Returns:
436 | ; A - 0, do not save, simply exit
437 | ; 1, save the file
438 | ; 2, cancel the operation
439 | controller_ask_save_file:
440 | ld hl, str_choices
441 | call editor_show_notification
442 | ; Re-use the regular even loop to wait on Y or N or C
443 | ld ix, controller_ask_save_file_printable
444 | ld iy, controller_ask_save_file_printable_ret
445 | ; Empty the response
446 | ld a, -1
447 | ld (_controller_ask_response), a
448 | ; Call to the event loop routine
449 | controller_ask_save_file_loop:
450 | call _editor_wait_for_event
451 | ; Check the response
452 | ld a, (_controller_ask_response)
453 | call controller_check_response
454 | ; Returns 0, 1, 2 or -1 if invalid
455 | or a
456 | ; If positive, return directly
457 | ret p
458 | jp controller_ask_save_file_loop
459 |
460 |
461 | ; Decode teh response contained in register A
462 | ; Parameters:
463 | ; A - ASCII character corresponding to a response
464 | ; Returns:
465 | ; A - 0 for Yes, 1 for No, 2 for Cancel, -1 else
466 | controller_check_response:
467 | cp 'y'
468 | jr z, controller_ask_save_yes
469 | cp 'n'
470 | jr z, controller_ask_save_no
471 | cp 'c'
472 | jr z, controller_ask_save_cancel
473 | ld a, -1
474 | ret
475 | controller_ask_save_no:
476 | ld a, 0
477 | ret
478 | controller_ask_save_yes:
479 | ld a, 1
480 | ret
481 | controller_ask_save_cancel:
482 | ld a, 2
483 | ret
484 |
485 | ; Called from the event loop!
486 | ; Parameters:
487 | ; A, B - Keyboard key code/ASCII character
488 | controller_ask_save_file_printable:
489 | ld (_controller_ask_response), a
490 | controller_ask_save_file_printable_ret:
491 | ret
492 | _controller_ask_response: DEFS 1
493 |
494 |
495 | str_unsupported: DEFM "Not implemented", 0
496 | str_cancelled: DEFM "Cancelled", 0
497 | str_choices: DEFM "Save the file? Y(es) / N(o) / C(ancel)", 0
498 | alternate_key_set_from_space: ; Characters starting at 0x20
499 | DEFM " !\"#$%&\"()*+<_>?)!@#$%^&*(::<+>?"
500 | alternate_key_set_from_bracket: ; Characters starting at 0x5B
501 | DEFM "{|}^_~ABCDEFGHIJKLMNOPQRSTUVWXYZ"
502 |
503 |
504 | SECTION DATA
505 | _editor_keys: DEFS 32
506 | _editor_keys_end:
507 | DEFS 1 ; For debugging so that symbols are correct
508 | _editor_flags: DEFM 0
509 |
510 |
511 | SECTION BUFFER
512 | ; By adding a newline before the buffer, finding the length of the first line
513 | ; can use the same algorithm as the other lines
514 | DEFM "\n"
515 | ; Start of the buffer, must be the last symbol of the binary!
516 | PUBLIC _start_buffer
517 | _start_buffer:
518 |
519 |
--------------------------------------------------------------------------------
/src/model.asm:
--------------------------------------------------------------------------------
1 | ; SPDX-FileCopyrightText: 2023 Zeal 8-bit Computer
2 | ;
3 | ; SPDX-License-Identifier: Apache-2.0
4 |
5 | INCLUDE "zos_sys.asm"
6 |
7 | DEFC EDITOR_MAX_COL = (80 - 1)
8 |
9 | EXTERN _editor_view_insert_string_at_cursor
10 | EXTERN _editor_view_insert_at_cursor
11 | EXTERN _editor_view_insert_string_before_cursor
12 | EXTERN _editor_view_remove_at_cursor
13 | EXTERN _editor_view_left_cursor
14 | EXTERN _editor_view_right_cursor
15 | EXTERN _editor_view_clean_string_at_cursor
16 | EXTERN _editor_view_goto_newline
17 | EXTERN _editor_view_cursor_prev_line
18 | EXTERN _editor_view_cursor_prev_line
19 | EXTERN _editor_view_8bit_var
20 | EXTERN _editor_view_cursor_next_line
21 | EXTERN _editor_view_init_new_line
22 | EXTERN _editor_view_restore_cursor
23 | EXTERN editor_show_notification
24 | EXTERN _start_buffer
25 | EXTERN strlen
26 | ; Use this buffer as temporary one only!
27 | EXTERN _editor_view_buffer
28 | DEFC TEMPORARY_BUFFER = _editor_view_buffer
29 |
30 | ;=========================================================;
31 | ;===================== MODEL ============================;
32 | ;=========================================================;
33 |
34 | SECTION TEXT
35 |
36 |
37 | ; Text buffer address (RAM bank)
38 | DEFC BUFFER_START_ADDRESS = _start_buffer
39 | ; DEFC BUFFER_MAX_SIZE = 32*1024
40 | ; DEFC BUFFER_END_ADDRESS = _start_buffer + BUFFER_MAX_SIZE - 1
41 | DEFC BUFFER_MAX_LINE_LENGTH = 0xFFFF
42 |
43 | DEFC EDITOR_MAX_LINE = (40 - 1) - 4
44 |
45 | ; Set the end memory address. Used to determine how many bytes our work
46 | ; buffer will have.
47 | ; It also determines how big can be the file we are going to work on can be.
48 | ; Parameters:
49 | ; HL - End of memory address (last usable byte)
50 | PUBLIC editor_set_end_memory
51 | editor_set_end_memory:
52 | ; The last byte will be a '\n' to simplify the calculations, so reserve one byte
53 | dec hl
54 | ld (_buffer_memory_end), hl
55 | ret
56 |
57 |
58 | ; Initialize the buffers and read the file if any was given.
59 | ; Parameters:
60 | ; DE - File name to open
61 | PUBLIC editor_model_init
62 | editor_model_init:
63 | ld hl, _start_buffer
64 | ld (_buffer_left_addr), hl
65 | ld hl, (_buffer_memory_end)
66 | ld (_buffer_right_addr), hl
67 | ; To simplify the calculations for the last line, add a final \n
68 | inc hl
69 | ld (hl), '\n'
70 | ld hl, 1
71 | ld (_buffer_line_count), hl
72 | ld hl, 0
73 | ld (_buffer_line_num), hl
74 | ld (_buffer_col_num), hl
75 | ; Check if a file was given
76 | ld a, d
77 | or e
78 | ret z
79 | ; TODO: Check that the filename is valid?
80 | ; A file was given, open it, save the full path
81 | ld (_model_full_path), de
82 | ld b, d
83 | ld c, e
84 | ld h, O_RDONLY
85 | OPEN()
86 | ; If an error occurred, return
87 | or a
88 | jp m, _editor_model_open_error
89 | ; Save file descriptor in a static variable
90 | ld (_model_file_dev), a
91 | ; Check if the file is too big, use stat to get file info
92 | ld de, TEMPORARY_BUFFER
93 | ld h, a ; opened device descriptor
94 | DSTAT()
95 | or a
96 | jp nz, _editor_model_init_error
97 | ; Make sure the file size doesn't go above the limit
98 | ex de, hl
99 | ld e, (hl)
100 | inc hl
101 | ld d, (hl)
102 | ; DE contains the size of the file, calculate the maximum file size in HL:
103 | ; HL = BUFFER_END_ADDRESS - _start_buffer + 1
104 | ; BC is free to use here
105 | _free_size:
106 | ld bc, -_start_buffer + 1
107 | ld hl, (_buffer_memory_end)
108 | add hl, bc
109 | ; HL now contains the maximum number of bytes
110 | sbc hl, de ; C is 0 already
111 | jp c, _editor_model_init_error
112 | ; TODO: check if the entry is a directory (once d_flags is part of stat structure)
113 | ; We can start reading from the file, the buffer of destination is spread across
114 | ; three virtual pages, so we will perform `read` three times if necessary.
115 | ld de, BUFFER_START_ADDRESS
116 | ; First bank finishes at 0x8000
117 | ld bc, 0x8000 - BUFFER_START_ADDRESS
118 | push bc
119 | call _editor_model_read_bc
120 | ; We can now close the file
121 | ld a, (_model_file_dev)
122 | ld h, a
123 | CLOSE()
124 | ; DE contains the remaining size in the buffer, so the size read from the
125 | ; file is BC - DE
126 | or a ; clear carry flag
127 | pop hl
128 | sbc hl, de
129 | ; If BC is 0, we have nothing to read more from the file, else, we have to
130 | ; continue reading, inside the rest of the buffer.
131 | ld a, b
132 | or c
133 | ; TODO: Support copying to the next buffers
134 | jp nz, _editor_model_open_error
135 | ; Copy the file content to the end of the buffer
136 | ld de, (_buffer_memory_end)
137 | ; Copy the file size (number of bytes to copy) in BC
138 | ld b, h
139 | ld c, l
140 | ; Calculate the address of the last byte of the loaded file
141 | ld hl, BUFFER_START_ADDRESS
142 | add hl, bc
143 | ; HL points to the next free character, decrement it to point to the last char of the file
144 | dec hl
145 | lddr
146 | ; Copy the characters to the view, DE contains the address of the first char (-1)
147 | ; Set the right cursor to this value
148 | ld (_buffer_right_addr), de
149 | inc de
150 | ex de, hl
151 | ; Manually handle the first line
152 | call _model_line_show_lines
153 | ld (_buffer_cur_length), bc
154 | _model_lines_show_loop:
155 | ; Put next line address in DE
156 | ex de, hl
157 | ; We can continue if the address of the next line is not the end
158 | ld hl, (_buffer_memory_end)
159 | dec hl ; - 1 because we want C to be set if equal
160 | sbc hl, de
161 | jr c, _model_lines_show_loop_end
162 | ; Do not print the line to the screen if we reached the end of the screen
163 | ; (we are simply counting the total number of lines)
164 | ; Increment the total number of lines
165 | ld hl, (_buffer_line_count)
166 | inc hl
167 | ld (_buffer_line_count), hl
168 | ld a, h
169 | or a
170 | jr nz, _model_lines_show_loop
171 | or l
172 | cp EDITOR_MAX_LINE + 1
173 | ; Put back the next line address in HL before (potentially) jumping
174 | ex de, hl
175 | jr nc, _model_lines_show_loop
176 | call _model_line_show_lines
177 | jp _model_lines_show_loop
178 | _model_lines_show_loop_end:
179 | jp _editor_view_restore_cursor
180 |
181 |
182 | ; Parameter:
183 | ; HL - Address of the line to show
184 | ; B - Line iteration
185 | ; Returns:
186 | ; HL - Address of the next line
187 | ; C - Length of the line
188 | _model_line_show_lines:
189 | push hl
190 | ; Calculate the length of the first line
191 | call _editor_model_line_length
192 | ; Get the original line back
193 | ex (sp), hl
194 | ; Save the length on the stack
195 | push bc
196 | call _editor_view_init_new_line
197 | pop bc
198 | pop hl
199 | ret
200 |
201 |
202 | ; Parameters:
203 | ; HL - Address of the line
204 | ; Returns:
205 | ; BC - Length of the line
206 | ; HL - Address of the next line
207 | _editor_model_line_length:
208 | ld bc, 0
209 | ld a, '\n'
210 | push bc
211 | cpir
212 | ; Put next line address in DE
213 | ex de, hl
214 | pop hl
215 | xor a
216 | sbc hl, bc
217 | ; Do not count the \n in the line length
218 | dec hl
219 | ld b, h
220 | ld c, l
221 | ex de, hl
222 | ret
223 |
224 |
225 | _editor_model_read_bc:
226 | ; Put the opened file dev in H
227 | ld a, (_model_file_dev)
228 | ld h, a
229 | ; Save the maximum size to read on the stack
230 | push bc
231 | READ()
232 | ; BC contains the number of bytes read from the file
233 | pop hl
234 | or a
235 | jp nz, _editor_model_init_error
236 | ; Carry flag is 0 because of `or a`
237 | sbc hl, bc
238 | ; Put the result in DE
239 | ex de, hl
240 | ; If the result is 0, we have to read in the next buffer
241 | ret z
242 | ; If BC is 0, we finished reading the file
243 | ld a, b
244 | or c
245 | ret z
246 | ; Else, continue reading the file, maximum remaining size is in DE,
247 | ; HL is the destination of the buffer.
248 | add hl, bc
249 | ex de, hl
250 | ld b, h
251 | ld c, l
252 | jp _editor_model_read_bc
253 |
254 |
255 | _editor_model_open_error:
256 | ; If the error is "ERR_NO_SUCH_ENTRY", it's not really an error as we will
257 | ; create this file, so don't exit is that case
258 | cp -ERR_NO_SUCH_ENTRY
259 | ret z
260 | _editor_model_init_error:
261 | S_WRITE3(DEV_STDOUT, _editor_model_error_str, _editor_model_error_str_end - _editor_model_error_str)
262 | EXIT()
263 |
264 | _editor_model_error_str:
265 | DEFM "ERROR OPENING THE FILE\n", 0
266 | _editor_model_error_str_end:
267 |
268 | ; Get the size of the line after the cursor
269 | ; Parameters:
270 | ; None
271 | ; Returns:
272 | ; HL - Number of characters after the cursor on the current line
273 | ; Alters:
274 | ; HL, BC
275 | _buffer_size_right_line:
276 | ld hl, (_buffer_cur_length)
277 | ld bc, (_buffer_col_num)
278 | sbc hl, bc
279 | ret
280 |
281 |
282 | ; Generic function to insert a character in the buffer, at the left address
283 | ; The only check performed here is whether the file is full or not.
284 | ; No check is performed for the current line length, it shall be done by
285 | ; the caller. This routine also updates the _file_size global variable.
286 | ; Parameters:
287 | ; B - Character to print
288 | ; Returns:
289 | ; A - 0 if no shift on the view is required, 1 else
290 | ; HL - Right address + 1 (when A = 1)
291 | ; BC - Size of the rest of the line, from cursor to end of line (when A = 1)
292 | PUBLIC _buffer_generic_insert_char
293 | _buffer_generic_insert_char:
294 | ; - Total length has not been reached (i.e. left_addr != right_addr)
295 | ld hl, (_buffer_right_addr)
296 | ld de, (_buffer_left_addr)
297 | xor a
298 | sbc hl, de
299 | ret z
300 | ; Line is not full, neither is the buffer, add the character. Put left
301 | ; cursor address in HL (currently in DE)
302 | ex de, hl
303 | ld (hl), b
304 | ; Increment the left cursor and store it back
305 | inc hl
306 | ld (_buffer_left_addr), hl
307 | ; Before incrementing the rest of the fields, calculate the size of the right buffer. In other words,
308 | ; the number of characters on the same line but after the cursor
309 | call _buffer_size_right_line
310 | push hl
311 | ; Increment line's length, no need to check for overflow (already done)
312 | ld hl, (_buffer_cur_length)
313 | inc hl
314 | ld (_buffer_cur_length), hl
315 | ; Increment the col number
316 | ld hl, (_buffer_col_num)
317 | inc hl
318 | ld (_buffer_col_num), hl
319 | ; Return the right buffer address if the (right) size is not zero
320 | pop bc
321 | ld a, b
322 | or c
323 | ret z
324 | ld hl, (_buffer_right_addr)
325 | inc hl
326 | ret
327 |
328 |
329 | ; Insert the character in the split buffer, at the cursor position
330 | ; Tested: OK!
331 | ; Parameters:
332 | ; B - Character to print
333 | ; Alters:
334 | ; A, BC, DE, HL
335 | PUBLIC _buffer_insert_char_at_cursor
336 | _buffer_insert_char_at_cursor:
337 | ; Check that the current line length has not exceed the maximum size
338 | ld hl, (_buffer_cur_length)
339 | ld de, BUFFER_MAX_LINE_LENGTH
340 | or a
341 | sbc hl, de
342 | ; The current line is full, do nothing
343 | ret z
344 | ; We have to save BC as B contains the character to print!
345 | push bc
346 | ; Call the generic function that pushes the char into the buffer
347 | ; and returns the right_address + 1 in HL and the length of the rest
348 | ; of the line in BC
349 | call _buffer_generic_insert_char
350 | ; If returned A is 0, no need to shift the view
351 | or a
352 | jp z, _insert_no_shift
353 | ; Pop the character to print in A
354 | pop af
355 | ; Modify the behavior here to simplify writing to the screen
356 | dec hl
357 | ld (hl), a
358 | ; More character to print
359 | inc bc
360 | jp _editor_view_insert_string_at_cursor
361 | _insert_no_shift:
362 | ; Pop the character to print in A
363 | pop af
364 | jp _editor_view_insert_at_cursor
365 |
366 |
367 | ; Remove the char currently pointed by the cursor
368 | PUBLIC _buffer_remove_char_at_cursor
369 | _buffer_remove_char_at_cursor:
370 | ; Make sure the cursor is not at the beginning (this also happens if the line is empty)
371 | ; TODO: Support going to previous line here too
372 | ld hl, (_buffer_col_num)
373 | ld a, h
374 | or l
375 | ret z
376 | ; Before modifying the properties of the buffer, calculate the size of the right buffer
377 | call _buffer_size_right_line
378 | push hl
379 | ; Decrement the column
380 | ld hl, (_buffer_col_num)
381 | dec hl
382 | ld (_buffer_col_num), hl
383 | ; Decrement the line length
384 | ld hl, (_buffer_cur_length)
385 | dec hl
386 | ld (_buffer_cur_length), hl
387 | ; Line is not empty, decrement left pointer
388 | ld hl, (_buffer_left_addr)
389 | dec hl
390 | ld (_buffer_left_addr), hl
391 | ; To print this character to the view, we have to shift all the line left.
392 | ; It shall not be done if the size of the right buffer is 0.
393 | pop bc
394 | ld a, b
395 | or c
396 | jp z, _editor_view_remove_at_cursor
397 | ; We have to shift BC characters (exclude \n) from right_addr + 1
398 | ld hl, (_buffer_right_addr)
399 | inc hl
400 | jp _editor_view_insert_string_before_cursor
401 |
402 |
403 | ; Move the cursor of the buffer left down to the given cursor
404 | ; No check will be performed here.
405 | ; Parameters:
406 | ; HL - New cursor index
407 | ; Returns:
408 | ; -
409 | PUBLIC model_move_cursor_left_to
410 | model_move_cursor_left_to:
411 | ld de, (_buffer_col_num)
412 | ex de, hl
413 | or a
414 | sbc hl, de
415 | ret z
416 | ld (_buffer_col_num), de
417 | ld b, h
418 | ld c, l
419 | ld hl, (_buffer_left_addr)
420 | dec hl
421 | ld de, (_buffer_right_addr)
422 | lddr
423 | inc hl
424 | ld (_buffer_left_addr), hl
425 | ld (_buffer_right_addr), de
426 | ret
427 |
428 |
429 | ; Make the buffer cursor point to the previous character.
430 | ; This won't change the size of course.
431 | PUBLIC _buffer_previous_char
432 | _buffer_previous_char:
433 | ; Cursor shall not be at the beginning!
434 | ; TODO: Support going to the previous line
435 | ld hl, (_buffer_col_num)
436 | ld a, h
437 | or l
438 | ret z
439 | ; Decrement the cursor col position
440 | dec hl
441 | ld (_buffer_col_num), hl
442 | ; We can go back safely, save the new address of the left cursor,
443 | ; put the character in the right cursor and decrement right cursor.
444 | ld hl, (_buffer_left_addr)
445 | dec hl
446 | ld (_buffer_left_addr), hl
447 | ld a, (hl)
448 | ld hl, (_buffer_right_addr)
449 | ld (hl), a
450 | dec hl
451 | ld (_buffer_right_addr), hl
452 | ; Update the view accordingly
453 | jp _editor_view_left_cursor
454 |
455 |
456 | ; Make the buffer cursor point to the next character.
457 | PUBLIC _buffer_next_char
458 | _buffer_next_char:
459 | ; Same as the previous routine but with right cursor
460 | ld de, (_buffer_right_addr)
461 | ld hl, (_buffer_memory_end)
462 | ; Clear carry flag
463 | or a
464 | ; 16-bit 'add' doesn't update Z flag
465 | sbc hl, de
466 | ret z
467 | ; Put back the right address in HL
468 | ex de, hl
469 | ; Check that the next char is NOT a new line
470 | ; TODO: Go to the next line
471 | inc hl
472 | ld a, (hl)
473 | cp '\n'
474 | ret z
475 | ; We can go forward safely, save the new address of the right cursor,
476 | ; put the character in the left cursor and increment the left cursor.
477 | ld (_buffer_right_addr), hl
478 | ld hl, (_buffer_left_addr)
479 | ld (hl), a
480 | inc hl
481 | ld (_buffer_left_addr), hl
482 | ; Update the column number
483 | ld hl, (_buffer_col_num)
484 | inc hl
485 | ld (_buffer_col_num), hl
486 | ; Update the view accordingly
487 | jp _editor_view_right_cursor
488 |
489 |
490 | ; Insert a new line in the buffer at the cursor position.
491 | ; Tested: OK!
492 | ; Parameters:
493 | ; B - '\n' char value
494 | PUBLIC _buffer_insert_new_line
495 | _buffer_insert_new_line:
496 | ; Call the generic function that pushes \n into the buffer
497 | ; and returns the right_address + 1 in HL and the length of the rest
498 | ; of the line in BC
499 | call _buffer_generic_insert_char
500 | ; Store BC, rest of the line in _buffer_cur_length
501 | ld (_buffer_cur_length), bc
502 | push bc
503 | ; If returned A is 0, no need to shift the view
504 | or a
505 | call nz, _editor_view_clean_string_at_cursor
506 | ; Add a line to the count
507 | ld hl, (_buffer_line_count)
508 | inc hl
509 | ld (_buffer_line_count), hl
510 | ; Reset column number
511 | ld hl, 0
512 | ld (_buffer_col_num), hl
513 | ; Increment the line index
514 | ld hl, (_buffer_line_num)
515 | inc hl
516 | ld (_buffer_line_num), hl
517 | ; Retrieve the parameters from the stack and jump to the view routine
518 | pop bc
519 | ld hl, (_buffer_right_addr)
520 | inc hl
521 | jp _editor_view_goto_newline
522 |
523 |
524 | ; Get the address of the next line
525 | ; Parameters:
526 | ; None
527 | ; Returns:
528 | ; HL - Address of the next line
529 | ; Alters:
530 | ; A, BC, HL
531 | PUBLIC model_next_line_address
532 | model_next_line_address:
533 | ; Get the size of the line in the right buffer
534 | call _buffer_size_right_line
535 | ex de, hl
536 | ld hl, (_buffer_right_addr)
537 | ; Increment HL to point to the next valid character
538 | inc hl
539 | ; Set the carry to add 1 in the calculation below, to skip the \n
540 | scf
541 | adc hl, de
542 | ret
543 |
544 |
545 | ; Get the number of lines that follow the cursor, including the current one
546 | ; Tested: OK!
547 | ; Parameters:
548 | ; -
549 | ; Returns:
550 | ; HL - Number of lines remaining
551 | ; Alters:
552 | ; A, HL, DE
553 | PUBLIC model_get_remaining_lines
554 | model_get_remaining_lines:
555 | ld hl, (_buffer_line_count)
556 | ld de, (_buffer_line_num)
557 | xor a
558 | sbc hl, de
559 | ret
560 |
561 |
562 | ; Retrieve the the A-th previous line of the current buffer pointer
563 | ; Parameters:
564 | ; A - Index of the previous line to get (0 = current line, 1 = previous line, etc...)
565 | ; Returns:
566 | ; HL - Address of the A-th previous line
567 | ; Alters:
568 | ; A, HL, BC, E
569 | PUBLIC _buffer_previous_ath_line_address
570 | _buffer_previous_ath_line_address:
571 | ld hl, (_buffer_left_addr)
572 | dec hl
573 | ld e, a
574 | inc e
575 | ld a, '\n'
576 | _buffer_previous_ath_line_address_loop:
577 | ld bc, BUFFER_MAX_LINE_LENGTH + 1
578 | cpdr
579 | ret nz ; Should not happen
580 | dec e
581 | jp nz, _buffer_previous_ath_line_address_loop
582 | ; After CPDR, HL points to the char before the \n that was found,
583 | ; we want it to point to the beginning of the line, so increment it twice.
584 | inc hl
585 | inc hl
586 | ret
587 |
588 |
589 | ; Calculate the minimum between HL and BC and put the result in BC
590 | ; Parameters:
591 | ; HL - 16-bit value
592 | ; BC - 16-bit value
593 | ; Returns:
594 | ; BC - Minimum between HL and BC
595 | ; Alters:
596 | ; HL, BC
597 | _buffer_min_hl_bc:
598 | or a ; Clear carry flag
599 | sbc hl, bc
600 | ret nc
601 | ; HL was the minimum, restore it and store it in BC
602 | add hl, bc
603 | ld b, h
604 | ld c, l
605 | ret
606 |
607 |
608 | ; Get a pointer to characters located on the left of the
609 | ; cursor.
610 | ; The parameter BC must not exceed the size of the buffer,
611 | ; no check is performed here!
612 | ; Parameters:
613 | ; BC - Number of characters to get
614 | ; Returns:
615 | ; HL - Pointer to the string
616 | ; Alters:
617 | ; A, HL, BC
618 | PUBLIC model_get_bc_left_chars
619 | model_get_bc_left_chars:
620 | ld hl, (_buffer_left_addr)
621 | or a ; clear carry flag
622 | sbc hl, bc
623 | ret
624 |
625 | ; Get a pointer to characters located on the right of the
626 | ; cursor.
627 | ; Parameters:
628 | ; BC - Request size
629 | ; Returns:
630 | ; HL - Pointer to the string
631 | ; BC - Minimum between requested size and size of the rest of the line
632 | ; Alters:
633 | ; HL
634 | PUBLIC model_get_bc_right_chars
635 | model_get_bc_right_chars:
636 | push bc
637 | call _buffer_size_right_line
638 | pop bc
639 | call _buffer_min_hl_bc
640 | ld hl, (_buffer_right_addr)
641 | inc hl
642 | ret
643 |
644 |
645 | ; Fills the given buffer with current line content.
646 | ; Parameters:
647 | ; DE - Buffer to fill with the current line content
648 | ; BC - Maximum number of bytes to fill in DE
649 | ; Returns:
650 | ; DE - End of the buffer
651 | ; BC - Number of characters written
652 | ; Alters:
653 | ; A, BC, DE, HL
654 | PUBLIC model_get_current_line
655 | model_get_current_line:
656 | push bc
657 | ; Copy 'col' characters from left iterator
658 | ld hl, (_buffer_col_num)
659 | ld a, h
660 | or l
661 | jr z, model_get_current_line_right
662 | ; Make HL point to the beginning of the line
663 | push de
664 | ld de, (_buffer_left_addr)
665 | ex de, hl
666 | sbc hl, de ; Carry is 0 here
667 | ex de, hl
668 | ; HL - contains 'col' number
669 | ; BC - max number to write to caller buffer
670 | ; DE - start of current line buffer
671 | ; [SP] - User buffer
672 | ; Keep BC on the stack, and get the minimum between BC and HL, store it in BC
673 | push bc
674 | call _buffer_min_hl_bc
675 | ; Before actually start copying, subtract BC from the user buffer size
676 | pop hl
677 | xor a
678 | sbc hl, bc
679 | ; HL contains the user buffer remaining size after copying. Exchange it with user buffer.
680 | ex (sp), hl
681 | ; Copy BC bytes from current line (DE) to the user buffer (HL)
682 | ex de, hl
683 | ldir
684 | pop bc
685 | ; BC - Remaining size in user buffer
686 | ; DE - User buffer next free byte
687 | ; If there is no space anymore, return
688 | ld a, b
689 | or c
690 | jr z, model_get_current_line_return
691 | ; Calculate for the right buffer now
692 | ld hl, (_buffer_col_num)
693 | model_get_current_line_right:
694 | ; HL contains column index, calculate the length of the line in the right buffer
695 | push de
696 | ld de, (_buffer_cur_length)
697 | ex de, hl
698 | xor a
699 | sbc hl, de
700 | pop de
701 | ; If the result is 0, we have nothing more to write to the buffer
702 | jr z, model_get_current_line_calculate_return
703 | ; Calculate the minimum between HL (line size in right buffer) and BC (user buffer remaining size)
704 | call _buffer_min_hl_bc
705 | ; We have to write BC characters from right buffer
706 | ld hl, (_buffer_right_addr)
707 | inc hl
708 | ldir
709 | ; The total amount of bytes written is MIN(line_length, user_buffer_size)
710 | ld hl, (_buffer_cur_length)
711 | pop bc
712 | jp _buffer_min_hl_bc
713 | model_get_current_line_return:
714 | ; Pop original buffer size, to say that we filled the buffer
715 | pop bc
716 | ret
717 | model_get_current_line_calculate_return:
718 | ; Calculate user buffer original size - remaining size
719 | pop hl
720 | ; Carry is 0 if we reached this point
721 | sbc hl, bc
722 | ld b, h
723 | ld c, l
724 | ret
725 |
726 |
727 | ; Calculate the length of the previous line.
728 | ; Parameters:
729 | ; HL - Address of the \n finalizing the previous line
730 | ; Returns:
731 | ; HL - Length of the previous line
732 | _buffer_previous_line_length:
733 | ; Go past the previous line's \n
734 | dec hl
735 | ; The previous line is preceded by a \n, look for it
736 | ld bc, BUFFER_MAX_LINE_LENGTH + 1
737 | ld a, '\n'
738 | cpdr
739 | ; When found, length of line is BUFFER_MAX_LINE_LENGTH - BC
740 | ld hl, BUFFER_MAX_LINE_LENGTH
741 | sbc hl, bc
742 | ret
743 |
744 |
745 | ; Make the buffer cursor point to the previous line.
746 | ; If the previous line has less character than current line, the cursor
747 | ; will be placed at the end of the previous line.
748 | ; Parameters:
749 | ; None
750 | ; Returns:
751 | ; None
752 | PUBLIC _buffer_previous_line
753 | _buffer_previous_line:
754 | ; Make sure we are not at the first line
755 | ld hl, (_buffer_line_num)
756 | ld a, h
757 | or l
758 | ; Return if HL is 0 (meaning we are at the first line)
759 | ret z
760 | ; Update _buffer_line_num now
761 | dec hl
762 | ld (_buffer_line_num), hl
763 | ; Load the pointers
764 | ld hl, (_buffer_left_addr)
765 | dec hl
766 | ld de, (_buffer_right_addr)
767 | ld bc, (_buffer_col_num)
768 | ; Copy BC bytes from left buffer into right buffer
769 | ld a, b
770 | or c
771 | jr z, _buffer_previous_line_no_copy
772 | lddr
773 | _buffer_previous_line_no_copy:
774 | push hl
775 | call _buffer_previous_line_length
776 | ; Store the current line length
777 | ld (_buffer_cur_length), hl
778 | ; If the new line has the same amount or more characters than the current cursor index, do not move it,
779 | ; else, move it to the end of the line.
780 | ld bc, (_buffer_col_num)
781 | ; If the cursor is at the end, trigger a carry flag during 'sbc'
782 | scf
783 | sbc hl, bc
784 | jr nc, _buffer_previous_line_cursor_valid
785 | ; BC is bigger than the size, set the cursor to the end of the line
786 | adc hl, bc
787 | ld (_buffer_col_num), hl
788 | ; Pop the left buffer index from the stack
789 | pop hl
790 | ; Copy the ending \n, we can alter BC, it won't be used later
791 | ldd
792 | jp _buffer_previous_line_cursor_no_copy
793 | _buffer_previous_line_cursor_valid:
794 | ; HL contains size - cursor, copy that many bytes into right buffer (DE)
795 | ld b, h
796 | ld c, l
797 | ; Increment BC because we subtracted a carry too
798 | inc bc
799 | ; Increment again to integrate the previous line's \n in the copy
800 | inc bc
801 | ; Pop the left buffer index from the stack
802 | pop hl
803 | lddr
804 | _buffer_previous_line_cursor_no_copy:
805 | ; Make HL point to the next available spot
806 | inc hl
807 | ld (_buffer_left_addr), hl
808 | ld (_buffer_right_addr), de
809 | ld bc, (_buffer_col_num)
810 | jp _editor_view_cursor_prev_line
811 |
812 |
813 | ; Calculate the length of the next line.
814 | ; Parameters:
815 | ; HL - Address of the first character of next line
816 | ; Returns:
817 | ; HL - Length of the next line
818 | _buffer_next_line_length:
819 | ; The previous line is preceded by a \n, look for it
820 | ld bc, BUFFER_MAX_LINE_LENGTH + 1
821 | ld a, '\n'
822 | cpir
823 | ; When found, length of line is BUFFER_MAX_LINE_LENGTH - BC
824 | ld hl, BUFFER_MAX_LINE_LENGTH
825 | sbc hl, bc
826 | ret
827 |
828 |
829 | ; Make the buffer cursor point to the next line.
830 | ; If the next line has less character than current line, the cursor
831 | ; will be placed at the end of the next line.
832 | ; Parameters:
833 | ; None
834 | ; Returns:
835 | ; None
836 | PUBLIC _buffer_next_line
837 | _buffer_next_line:
838 | ; Make sure we are not at the last line
839 | ld bc, (_buffer_line_num)
840 | ld hl, (_buffer_line_count)
841 | scf
842 | sbc hl, bc
843 | ; Return if HL is the last line
844 | ret z
845 | ; Update _buffer_line_num now
846 | inc bc
847 | ld (_buffer_line_num), bc
848 | ; Calculate the number of bytes to send to the end of the buffer, in other
849 | ; words, make the cursor point to the end of the current line:
850 | call _buffer_size_right_line
851 | ; It doesn't count the \n, so add it
852 | inc hl
853 | ld b, h
854 | ld c, l
855 | ; Perform the copy
856 | ld de, (_buffer_left_addr)
857 | ld hl, (_buffer_right_addr)
858 | inc hl
859 | ldir
860 | ; Look for the length of the next line, HL is already pointing to its first character
861 | push hl
862 | call _buffer_next_line_length
863 | ; Store the current line length
864 | ld (_buffer_cur_length), hl
865 | ld bc, (_buffer_col_num)
866 | ; Now, we have to transfer MIN(length, cursor) characters <=> MIN(HL, BC)
867 | call _buffer_min_hl_bc
868 | ld (_buffer_col_num), bc
869 | ; Pop the right buffer index from the stack
870 | pop hl
871 | ; Check if BC is 0 (cursor at the beginning already)
872 | ld a, b
873 | or c
874 | jp z, _buffer_next_line_cursor_no_copy
875 | ldir
876 | _buffer_next_line_cursor_no_copy:
877 | ; HL points to the right buffer (source)
878 | dec hl
879 | ld (_buffer_right_addr), hl
880 | ld (_buffer_left_addr), de
881 | ld bc, (_buffer_col_num)
882 | jp _editor_view_cursor_next_line
883 |
884 |
885 | ; Save the current buffer to a file
886 | PUBLIC model_save_file
887 | model_save_file:
888 | ld hl, str_saving
889 | call editor_show_notification
890 | ld bc, (_model_full_path)
891 | ld h, O_WRONLY | O_CREAT | O_TRUNC
892 | OPEN()
893 | ; If an error occurred, return
894 | or a
895 | jp m, model_print_error_neg
896 | ; There are two parts to write to the file:
897 | ; - From the beginning to the cursor
898 | ; - From the cursor to the end
899 | ld hl, (_buffer_left_addr)
900 | ld de, BUFFER_START_ADDRESS
901 | or a ; clear carry flag
902 | sbc hl, de
903 | ld b, h
904 | ld c, l
905 | ex de, hl
906 | push af ; Save the opened descriptor
907 | call model_save_buffer
908 | pop de
909 | or a
910 | jr nz, model_print_error
911 | ; Get back the opened descriptor
912 | ld a, d
913 | ; Save for the right buffer now
914 | ld de, (_buffer_memory_end)
915 | ld hl, (_buffer_right_addr)
916 | ex de, hl
917 | sbc hl, de ; carry flag is already 0
918 | ld b, h
919 | ld c, l
920 | ex de, hl
921 | inc hl
922 | push af
923 | call model_save_buffer
924 | pop hl
925 | or a
926 | jr nz, model_print_error
927 | ; Close the file descriptor in H
928 | CLOSE()
929 | ; Success!
930 | ld hl, str_success
931 | jp editor_show_notification
932 |
933 | model_print_error_neg:
934 | neg
935 | model_print_error:
936 | ld hl, str_error_code
937 | call model_show_location_to_decimal
938 | ld (hl), 0
939 | ld hl, str_error_msg
940 | jp editor_show_notification
941 |
942 | str_saving: DEFM "Saving...", 0
943 | str_success: DEFM "Success", 0
944 | str_error_msg: DEFM "Error saving file: "
945 | str_error_code: DEFS 3
946 | DEFB 0
947 |
948 |
949 | ; Save the given buffer of given size to the opened descriptor (file)
950 | ; Parameters:
951 | ; A - Opened descriptor
952 | ; HL - Buffer containing data to write
953 | ; BC - Size of the buffer
954 | ; Returns:
955 | ; A - ERR_SUCCESS on success, error code else
956 | ; Alters:
957 | ; A, BC, DE, HL
958 | model_save_buffer:
959 | ld (_model_file_dev), a
960 | model_save_buffer_descriptor_saved:
961 | ; If BC is 0, return success directly
962 | ld a, b
963 | or c
964 | ret z
965 | ; Check if the buffer goes beyond one virtual page (the kernel would refuse it else)
966 | push hl
967 | ld a, h
968 | ; Get the virtual page of HL and store it in D
969 | and 0xc0
970 | ld d, a
971 | ; Get the address of the last char of the buffer
972 | dec hl
973 | add hl, bc
974 | ; Get the virtual page of this new address
975 | ld a, h
976 | and 0xc0
977 | ld h, a
978 | ; If it is the same as the former one, the buffer doesn't cross-boundary, we have to call
979 | ; WRITE syscall only once
980 | cp d
981 | jr z, model_save_buffer_not_cross_boundary
982 | ; The buffer crosses boundaries, at most 3, the first address to write is the original one, but
983 | ; we need to modify the size.
984 | pop hl ; Original buffer address
985 | ; Calculate the boundary address out of the virtual page count
986 | ld a, d
987 | add 0x40 ; the virtual page is not 3 for sure, so increment it by 1
988 | ld d, a
989 | ld e, 0
990 | push de ; save next virtual size to write
991 | ; DE contains the end of page address, calculate the new size
992 | push bc ; Save original size
993 | ex de, hl
994 | or a
995 | sbc hl, de
996 | ; Size is in HL
997 | ld b, h
998 | ld c, l
999 | ; Buffer is in DE, write now
1000 | call model_save_buffer_not_cross_boundary_buffer_in_de
1001 | ; Calculate remaining size to write
1002 | pop hl
1003 | or a
1004 | sbc hl, bc
1005 | ; Put the remaining size in BC
1006 | ld b, h
1007 | ld c, l
1008 | ; Pop the next buffer to write
1009 | pop hl
1010 | ; If there was no error, continue writing
1011 | or a
1012 | jp z, model_save_buffer_descriptor_saved
1013 | ; Else, return directly
1014 | ret
1015 |
1016 | ; Parameters:
1017 | ; (_model_file_dev) - Opened descriptor
1018 | ; [SP] - Address of the buffer to print
1019 | ; BC - Size of the buffer to print
1020 | ; Returns:
1021 | ; BC - Number of bytes written
1022 | model_save_buffer_not_cross_boundary:
1023 | ; Buffer to write to file in DE
1024 | pop de
1025 | model_save_buffer_not_cross_boundary_buffer_in_de:
1026 | ; Opened descriptor in H
1027 | ld a, (_model_file_dev)
1028 | ld h, a
1029 | WRITE()
1030 | ret
1031 |
1032 | ; Show the current current of the cursor in the notification bar
1033 | PUBLIC model_show_location
1034 | model_show_location:
1035 | ld de, loc_line
1036 | ; Since the line_num is an index, we have to increment it
1037 | ld hl, (_buffer_line_num)
1038 | inc hl
1039 | call model_show_location_to_decimal
1040 | ; Same goes for the column
1041 | ld (hl), ','
1042 | inc hl
1043 | ld (hl), ' '
1044 | inc hl
1045 | ld (hl), 'C'
1046 | inc hl
1047 | ld (hl), 'o'
1048 | inc hl
1049 | ld (hl), 'l'
1050 | inc hl
1051 | ld (hl), ' '
1052 | inc hl
1053 | ex de, hl
1054 | ld hl, (_buffer_col_num)
1055 | inc hl
1056 | call model_show_location_to_decimal
1057 | ; Clean the end of the buffer
1058 | ld (hl), 0
1059 | ; Finally, we can print the resulting buffer
1060 | ld hl, loc_start
1061 | jp editor_show_notification
1062 |
1063 |
1064 | ; Convert a 16-bit value to decimal (ASCII) and store it in the
1065 | ; buffer pointed by DE
1066 | ; Parameters:
1067 | ; DE - Buffer to store the result in
1068 | ; HL - 16-bit value to convert
1069 | ; Returns:
1070 | ; HL - Address of the last digit + 1
1071 | model_show_location_to_decimal:
1072 | ld c, 10 ; Constant, not modified by the code
1073 | push de
1074 | pop ix ; Move DE in IX
1075 | ld d, 0 ; Number of digits
1076 | model_show_location_to_decimal_loop:
1077 | call _model_divide_hl_c
1078 | ; Remainder in A, convert to ASCII
1079 | add '0'
1080 | push af
1081 | inc d
1082 | ; Check if HL is 0
1083 | ld a, h
1084 | or l
1085 | jp nz, model_show_location_to_decimal_loop
1086 | ; We have D digits, iterate over all the digits
1087 | ld b, d
1088 | push ix
1089 | pop hl
1090 | model_show_location_to_decimal_pop_loop:
1091 | pop af
1092 | ld (hl), a
1093 | inc hl
1094 | djnz model_show_location_to_decimal_pop_loop
1095 | ret
1096 |
1097 |
1098 | loc_start: DEFM "Ln "
1099 | loc_line: DEFS 20
1100 |
1101 |
1102 | ; Divide a 16-bit value by an 8-bit value
1103 | ; Parameters:
1104 | ; HL - Number of divide
1105 | ; C - Divider
1106 | ; Returns:
1107 | ; HL - Result
1108 | ; A - Remainder
1109 | _model_divide_hl_c:
1110 | xor a
1111 | ld b, 16
1112 | _model_divide_hl_c_loop:
1113 | add hl, hl
1114 | rla
1115 | jp c, _model_divide_hl_carry
1116 | cp c
1117 | jp c, _model_divide_hl_next
1118 | _model_divide_hl_carry:
1119 | sub c
1120 | inc l
1121 | _model_divide_hl_next:
1122 | djnz _model_divide_hl_c_loop
1123 | ret
1124 |
1125 |
1126 | SECTION DATA
1127 | _model_full_path: DEFS 2
1128 | _model_file_dev: DEFS 1
1129 | ; Split buffer related
1130 | ; Current length of the line, in bytes
1131 | _buffer_cur_length: DEFS 2
1132 | ; Contains the line number the cursor is currently at
1133 | _buffer_line_num: DEFS 2
1134 | ; Same for the column
1135 | _buffer_col_num: DEFS 2
1136 | ; Contains the number of lines
1137 | _buffer_line_count: DEFS 2
1138 | ; Contains the address of the LEFT cursor in the file buffer
1139 | _buffer_left_addr: DEFS 2
1140 | ; Same but for the RIGHT cursor
1141 | _buffer_right_addr: DEFS 2
1142 | ; Last valid memory address, in other words, maximum value of _buffer_right_addr
1143 | _buffer_memory_end: DEFS 2
--------------------------------------------------------------------------------
/src/view.asm:
--------------------------------------------------------------------------------
1 | ; SPDX-FileCopyrightText: 2023 Zeal 8-bit Computer
2 | ;
3 | ; SPDX-License-Identifier: Apache-2.0
4 |
5 | INCLUDE "zos_sys.asm"
6 | INCLUDE "zos_video.asm"
7 |
8 | DEFC EDITOR_CHARS_COLOR = (TEXT_COLOR_BLACK << 8) | TEXT_COLOR_WHITE
9 | DEFC EDITOR_BACKGROUND_COLOR = (TEXT_COLOR_WHITE << 8) | TEXT_COLOR_BLACK
10 | DEFC EDITOR_FOOTER_HEIGHT = 3
11 |
12 | EXTERN _buffer_previous_ath_line_address
13 | EXTERN model_get_current_line
14 | EXTERN model_next_line_address
15 | EXTERN model_get_remaining_lines
16 | EXTERN model_get_bc_left_chars
17 | EXTERN model_get_bc_right_chars
18 | EXTERN model_move_cursor_left_to
19 | EXTERN strlen
20 | EXTERN strncpy
21 | EXTERN basename
22 |
23 | SECTION TEXT
24 |
25 |
26 | ;=========================================================;
27 | ;===================== VIEW =============================;
28 | ;=========================================================;
29 |
30 | ; Parameters:
31 | ; DE - String containing the file name to open, NULL else
32 | PUBLIC editor_view_init
33 | editor_view_init:
34 | ; Copy the filename
35 | ld a, d
36 | or e
37 | jr z, _editor_view_init_no_file
38 | ex de, hl
39 | call basename
40 | ; Basename in HL
41 | ld de, _editor_filename
42 | ld bc, 16
43 | call strncpy
44 | _editor_view_init_no_file:
45 | ld a, 80
46 | ld (_editor_screen_width), a
47 | ld a, 40
48 | ld (_editor_screen_height), a
49 | ; Absolute X coordinate for lines
50 | ld hl, 0
51 | ld (_file_cursor_x), hl
52 | ; Draw the initial view
53 | call _editor_draw_init_screen
54 | ; Set X and Y to (0, 1) at the same time, cursor_y comes first
55 | ld de, 1
56 | ld (_cursor_y), de
57 | jp _editor_view_restore_cursor_no_load
58 |
59 |
60 | _editor_name: DEFM "ZOS Zepto "
61 | INCBIN "version.txt"
62 | _editor_name_end:
63 | _editor_filename: DEFS 17, "New_File" ; \0 will be added automatically
64 | ; The welcome message must NOT be bigger than the screen width or it will corrupt other data
65 | _editor_welcome_msg: DEFM "Welcome to zepto. For help, type Ctrl+H", 0
66 | _editor_shortcuts_msg:
67 | DEFM "^H Help ^L Location ^X Cut ^C Copy ^V Paste \n"
68 | DEFM "^K Exit ^S Save File ^O Open File ^N New File "
69 | _editor_shortcuts_msg_end:
70 |
71 |
72 | ; Routine to draw the initial screen, with the top menu and the options at the bottom
73 | nop
74 | _editor_draw_init_screen:
75 | ld de, EDITOR_BACKGROUND_COLOR
76 | call _editor_draw_set_color
77 | ; Set the cursor to the top of the screen and start drawing
78 | ld de, 0
79 | call _editor_view_restore_cursor_no_load
80 | ; Print the file name or "New file*" in the absence of file name
81 | call _editor_view_erase_buffer
82 | push bc ; Save the screen width
83 | ld hl, _editor_filename
84 | xor a ; not a notification
85 | call _editor_copy_message
86 | ; Print the name of the program at the top left corner
87 | ld de, _editor_view_buffer + 1
88 | ld hl, _editor_name
89 | ld bc, _editor_name_end - _editor_name
90 | ldir
91 | ld de, _editor_view_buffer
92 | pop bc
93 | inc bc
94 | call _editor_draw_line
95 | ; Let's print this buffer once, prepare the length
96 | ld de, EDITOR_CHARS_COLOR
97 | call _editor_draw_set_color
98 | call _editor_view_erase_buffer
99 | ; Add a newline at the end of the buffer
100 | ld a, '\n'
101 | ld (de), a
102 | ld de, _editor_view_buffer
103 | ; Then print height - (footer + 1) lines
104 | ld a, (_editor_screen_height)
105 | sub EDITOR_FOOTER_HEIGHT + 1
106 | ; Put this amount in L and H
107 | ld h, a
108 | ld l, a
109 | ; Print the \n that follows the buffer
110 | inc bc
111 | _editor_draw_init_lines_loop:
112 | call _editor_draw_line
113 | dec l
114 | jp nz, _editor_draw_init_lines_loop
115 | ; Now we have to print the footer, set the background colors again
116 | ld de, EDITOR_BACKGROUND_COLOR
117 | call _editor_draw_set_color
118 | ; Before calling editor_show_notification, set the cursor_y position that will be set
119 | ; at the end of the routine (_cursor_x was already set to 0 by the loader)
120 | ld a, h
121 | add 2
122 | ld (_cursor_y), a
123 | ld hl, _editor_welcome_msg
124 | call editor_show_notification
125 | ; Print the shortcuts
126 | S_WRITE3(DEV_STDOUT, _editor_shortcuts_msg, _editor_shortcuts_msg_end - _editor_shortcuts_msg)
127 | ; And reset the colors
128 | ld de, EDITOR_CHARS_COLOR
129 | jp _editor_draw_set_color
130 |
131 |
132 | ; Write a message in the notification zone
133 | ; Parameters:
134 | ; HL - Message to write
135 | ; Returns:
136 | ; None
137 | PUBLIC editor_show_notification
138 | editor_show_notification:
139 | push hl
140 | call _editor_view_erase_buffer
141 | ; Save the length of the buffer to print on the stack
142 | ld h, b
143 | ld l, c
144 | ex (sp), hl
145 | ld a, 1
146 | call _editor_copy_message
147 | ; Set the position of the cursor to the notification zone
148 | ld a, (_editor_screen_height)
149 | sub EDITOR_FOOTER_HEIGHT
150 | ld d, 0
151 | ld e, a
152 | call _editor_view_restore_cursor_no_load
153 | ; Set the color of the notification text
154 | ld de, EDITOR_BACKGROUND_COLOR
155 | call _editor_draw_set_color
156 | ; Print the buffer, pop its size from the stack
157 | pop bc
158 | ld de, _editor_view_buffer
159 | S_WRITE1(DEV_STDOUT)
160 | ; Restore the current text color
161 | ld de, EDITOR_CHARS_COLOR
162 | call _editor_draw_set_color
163 | ; Restore the cursor position and return
164 | jp _editor_view_restore_cursor
165 |
166 |
167 | ; Copy a message to the message buffer, it will be copied at the middle
168 | ; Parameters:
169 | ; HL - Message to copy
170 | ; A - 1 if notification ([] appended), 0 else
171 | _editor_copy_message:
172 | push af
173 | call strlen
174 | ; B is 0 for sure here, make sure it is not bigger than the width - 4 (for brackets)
175 | ld a, (_editor_screen_width)
176 | sub 4
177 | cp c
178 | jr nc, _editor_copy_message_size_ok
179 | ld c, a
180 | _editor_copy_message_size_ok:
181 | ; DE = _editor_view_buffer + (size/2 - name_len/2)
182 | ld a, c
183 | srl a
184 | adc 0
185 | ld d, a
186 | ld a, (_editor_screen_width)
187 | srl a
188 | sub d
189 | ; DE += A
190 | ld de, _editor_view_buffer
191 | add e
192 | ld e, a
193 | adc d
194 | sub e
195 | ld d, a
196 | ; Copy the name and finally copy the line
197 | pop af
198 | or a
199 | jr z, _editor_copy_message_no_brack
200 | ; Go backward to add the '[', the buffer was cleaned with spaces, no need to write a space again
201 | ex de, hl
202 | dec hl
203 | dec hl
204 | ld (hl), '['
205 | inc hl
206 | inc hl
207 | ex de, hl
208 | _editor_copy_message_no_brack:
209 | ldir
210 | or a
211 | ret z
212 | ex de, hl
213 | inc hl
214 | ld (hl), ']'
215 | ret
216 |
217 |
218 | ; Returns:
219 | ; BC - Width of the screen
220 | ; HL - Address of the last character
221 | ; DE - Address of the last character + 1
222 | ; Alters:
223 | ; BC, HL, DE
224 | _editor_view_erase_buffer:
225 | ; Initialize the temporary buffer with spaces first
226 | ld bc, (_editor_screen_width)
227 | push bc
228 | ld hl, _editor_view_buffer
229 | ld (hl), ' '
230 | ld d, h
231 | ld e, l
232 | inc de
233 | dec bc
234 | ldir
235 | pop bc
236 | ret
237 |
238 |
239 | ; Parameters:
240 | ; DE - Line to print
241 | ; BC - Length of the line to print
242 | ; Returns:
243 | ; A - ERR_SUCCESS on success, error code else
244 | ; Alters:
245 | ; A
246 | _editor_draw_line:
247 | push hl
248 | push bc
249 | S_WRITE1(DEV_STDOUT)
250 | pop bc
251 | pop hl
252 | ret
253 |
254 |
255 | ; Set the current colors
256 | ; Parameter:
257 | ; DE - Background and foreground colors
258 | _editor_draw_set_color:
259 | push hl
260 | push bc
261 | ld h, DEV_STDOUT
262 | ld c, CMD_SET_COLORS
263 | IOCTL()
264 | pop bc
265 | pop hl
266 | ret
267 |
268 |
269 |
270 | ; Adjust the value of BC to be the minimum between BC and screen width
271 | ; Parameters:
272 | ; BC - Value to test
273 | ; Returns:
274 | ; BC - MIN(BC,_editor_screen_width)
275 | ; Alters:
276 | ; A
277 | editor_adjust_bc_width:
278 | ld a, b
279 | or a
280 | jr nz, _editor_adjust_bc_big
281 | ld a, (_editor_screen_width)
282 | cp c
283 | ret nc
284 | _editor_adjust_bc_big:
285 | ld bc, (_editor_screen_width)
286 | ret
287 |
288 |
289 | ; Calculate the minimum between HL and BC and put the result in BC
290 | ; Parameters:
291 | ; HL - 16-bit value
292 | ; BC - 16-bit value
293 | ; Returns:
294 | ; BC - Minimum between HL and BC
295 | ; Alters:
296 | ; HL, BC
297 | editor_min_hl_bc:
298 | or a ; Clear carry flag
299 | sbc hl, bc
300 | ret nc
301 | ; HL was the minimum, restore it and store it in BC
302 | add hl, bc
303 | ld b, h
304 | ld c, l
305 | ret
306 |
307 | ; Calculate the remaining size on the screen line
308 | ; Parameters:
309 | ; -
310 | ; Returns:
311 | ; A - Remaining size on screen line
312 | ; Alters:
313 | ; A, L
314 | editor_line_rem_size:
315 | ld a, (_cursor_x)
316 | ld l, a
317 | ld a, (_editor_screen_width)
318 | sub l
319 | ret
320 |
321 |
322 |
323 | ; Print a line at the cursor location and go to the next line afterwards.
324 | ; This is used when loading a file, in order to show it on screen.
325 | ; Parameters:
326 | ; HL - Address of the line to show
327 | ; BC - Length of the line to print
328 | PUBLIC _editor_view_init_new_line
329 | _editor_view_init_new_line:
330 | call editor_adjust_bc_width
331 | ex de, hl
332 | S_WRITE1(DEV_STDOUT)
333 | ; Print a new line at the end
334 | ld hl, _editor_view_buffer
335 | ld (hl), '\n'
336 | ex de, hl
337 | ld c, 1
338 | S_WRITE1(DEV_STDOUT)
339 | ret
340 |
341 |
342 | ; Clean the whole string of size BC at cursor, DO NOT modify the cursor
343 | ; This shall be called when a new line is inserted in the middle of a line.
344 | ; Parameters:
345 | ; BC - Size to clean
346 | PUBLIC _editor_view_clean_string_at_cursor
347 | _editor_view_clean_string_at_cursor:
348 | ret
349 |
350 | ; Restore the cursor to its original position
351 | PUBLIC _editor_view_restore_cursor
352 | _editor_view_restore_cursor:
353 | ld de, (_cursor_y)
354 | _editor_view_restore_cursor_no_load:
355 | ld c, CMD_SET_CURSOR_XY
356 | ld h, DEV_STDOUT
357 | IOCTL()
358 | ret
359 |
360 |
361 | ; Increment both the view cursor as well as the absolute cursor
362 | ; Parameters:
363 | ; -
364 | ; Returns:
365 | ; -
366 | editor_view_increment_x:
367 | ld hl, (_file_cursor_x)
368 | inc hl
369 | ld (_file_cursor_x), hl
370 | ld hl, _cursor_x
371 | inc (hl)
372 | ret
373 |
374 |
375 | ; Decrement both the view cursor as well as the absolute cursor
376 | ; Parameters:
377 | ; -
378 | ; Returns:
379 | ; -
380 | editor_view_decrement_x:
381 | ld hl, (_file_cursor_x)
382 | dec hl
383 | ld (_file_cursor_x), hl
384 | ld hl, _cursor_x
385 | dec (hl)
386 | ret
387 |
388 |
389 | ; Checks if the current line is a long line (requires horizontal scrolling)
390 | ; Parameters:
391 | ; -
392 | ; Returns:
393 | ; NZ flag - Is a long line
394 | ; Z - Not a long line
395 | ; Alters:
396 | ; A, HL, DE
397 | editor_view_long_line:
398 | ld hl, (_file_cursor_x)
399 | ld de, (_editor_screen_width)
400 | or a
401 | ; X cursor starts at index 0, increment it to get the actual length
402 | inc hl
403 | sbc hl, de
404 | ; If no carry:
405 | ; Z is set - the line is not longer than the width
406 | ; NZ is not set - the line is longer than the width
407 | ret nc
408 | ; On carry, the width is bigger than the cursor, set Z flag
409 | xor a
410 | ret
411 |
412 |
413 | ; Clean the current line and position the cursor at the beginning
414 | ; Parameters:
415 | ; -
416 | ; Returns:
417 | ; -
418 | ; Alters:
419 | ; A, BC, DE, HL
420 | _editor_view_line_clean_beginning:
421 | xor a
422 | ld (_cursor_x), a
423 | call _editor_view_restore_cursor
424 | call _editor_view_erase_buffer
425 | ld de, _editor_view_buffer
426 | S_WRITE1(DEV_STDOUT)
427 | call _editor_view_restore_cursor
428 | ret
429 |
430 |
431 | ; Print a whole string at current cursor position without moving it. After printing, move
432 | ; it to the right once.
433 | ; At the end the cursor must be at original position + 1.
434 | ; Parameters:
435 | ; HL - String to print
436 | ; BC - Size of the string (guaranteed > 1)
437 | PUBLIC _editor_view_insert_string_at_cursor
438 | _editor_view_insert_string_at_cursor:
439 | ex de, hl
440 | ; Set BC to the minimum between BC and the remaining size on this line on screen
441 | call editor_line_rem_size
442 | ; If there is 1 space left on screen, we will reach the border after writing the current one
443 | dec a
444 | jr z, _editor_view_insert_string_at_cursor_overflow
445 | inc a
446 | ld l, a
447 | ld h, 0
448 | call editor_min_hl_bc
449 | _editor_view_insert_string_at_cursor_print:
450 | S_WRITE1(DEV_STDOUT)
451 | call editor_view_increment_x
452 | jp _editor_view_restore_cursor
453 | ; Jump to the following label if the cursor is at the end of the line
454 | ; Clear the whole line, and make the cursor jump back to the beginning of the line
455 | _editor_view_insert_string_at_cursor_overflow:
456 | push de
457 | push bc
458 | call _editor_view_line_clean_beginning
459 | pop bc
460 | pop de
461 | jp _editor_view_insert_string_at_cursor_print
462 |
463 |
464 | ; Print the character given at the current cursor location,
465 | ; reset the cursor (i.e. switch back to characters colors).
466 | ; All checks have been performed already by the buffer/model.
467 | ; Parameters:
468 | ; A - Character to print
469 | PUBLIC _editor_view_insert_at_cursor
470 | _editor_view_insert_at_cursor:
471 | ; D is not altered by the routine below
472 | ld d, a
473 | call editor_line_rem_size
474 | ; If there is only a single spot on the screen line remaining, scroll to the right
475 | dec a
476 | jr z, _editor_view_insert_at_cursor_overflow
477 | ld a, d
478 | _editor_view_insert_at_cursor_print:
479 | ld de, _editor_view_buffer
480 | ld (de), a
481 | ld bc, 1
482 | S_WRITE1(DEV_STDOUT)
483 | jp editor_view_increment_x
484 | _editor_view_insert_at_cursor_overflow:
485 | push de
486 | call _editor_view_line_clean_beginning
487 | pop af
488 | ; Increment the absolute cursor here too, in total it needs to be incremented twice:
489 | ; - Once when we switch screen
490 | ; - Once after inserting the new character
491 | ld hl, (_file_cursor_x)
492 | inc hl
493 | ld (_file_cursor_x), hl
494 | jp _editor_view_insert_at_cursor_print
495 |
496 |
497 | ; Print a whole string at cursor - 1, and update the cursor.
498 | ; Routine called when a character is removed from the middle of a
499 | ; line. So also clear the character after printing.
500 | ; Parameters:
501 | ; HL - String to print
502 | ; BC - Size of the string
503 | ; Alters:
504 | ; BC, DE, HL
505 | PUBLIC _editor_view_insert_string_before_cursor
506 | _editor_view_insert_string_before_cursor:
507 | push hl
508 | push bc
509 | ; Move the cursor left, it will perform the horizontal scroll if needed
510 | call _editor_view_left_cursor
511 | pop bc
512 | ; If the cursor is at the end of the screen, we need to print the next
513 | ; character below it
514 | call editor_line_rem_size
515 | dec a
516 | jr z, _editor_view_insert_string_before_cursor_next_char
517 | ; Regular insertion at cursor, we are not at the edge of the screen
518 | inc a
519 | ld l, a
520 | ld h, 0
521 | call editor_min_hl_bc
522 | pop hl
523 | ; We can print BC characters on screen, copy it to our buffer first, because
524 | ; we need to append a ' ' to clean the last character on the line
525 | push bc
526 | ld de, _editor_view_buffer
527 | ldir
528 | ld a, ' '
529 | ld (de), a
530 | pop bc
531 | ; We just added a space to the string, increment the size
532 | inc bc
533 | ld de, _editor_view_buffer
534 | S_WRITE1(DEV_STDOUT)
535 | jp _editor_view_restore_cursor
536 | _editor_view_insert_string_before_cursor_next_char:
537 | pop hl
538 | ld de, _editor_view_buffer
539 | ldi
540 | ; Continue by adding a backspace character
541 | ld a, '\b'
542 | ld (de), a
543 | dec de
544 | ; Print out these chars
545 | ld bc, 2
546 | S_WRITE1(DEV_STDOUT)
547 | ; The cursors have been updated already in _editor_view_left_cursor routine
548 | ret
549 |
550 |
551 | ; Remove the character on the view. The cursor must be moved to the left.
552 | PUBLIC _editor_view_remove_at_cursor
553 | _editor_view_remove_at_cursor:
554 | ; Simplify this function by moving left first
555 | call _editor_view_left_cursor
556 | ; Replace the character under the cursor, do not update the cursors anymore
557 | ld hl, _editor_view_buffer
558 | ld (hl), ' '
559 | inc hl
560 | ld (hl), '\b'
561 | ; Reposition HL and put it in DE
562 | dec hl
563 | ex de, hl
564 | ld bc, 2
565 | S_WRITE1(DEV_STDOUT)
566 | ret
567 |
568 |
569 | ; Move the cursor left, all checks have been performed by
570 | ; the model.
571 | PUBLIC _editor_view_left_cursor
572 | _editor_view_left_cursor:
573 | ; If the X cursor is 0, it means we are currently on a long line, we need to
574 | ; horizontally scroll to the left
575 | ld a, (_cursor_x)
576 | ; Check if A is 1
577 | dec a
578 | jr z, _editor_view_left_cursor_check_scroll
579 | _editor_view_left_cursor_regular:
580 | ; Else, no scroll, we can decrement both cursors
581 | call editor_view_decrement_x
582 | jp _editor_view_restore_cursor
583 | _editor_view_left_cursor_check_scroll:
584 | ; Check if we need to go to the line left content. This is the case when
585 | ; the file cursor is NOT 1
586 | ld hl, (_file_cursor_x)
587 | dec hl
588 | ; Check if HL is 0, if yes, we are not on a long line
589 | ld a, h
590 | or l
591 | jp z, _editor_view_left_cursor_regular
592 | ; We are on a long line, load the left content!
593 | ; Decrement HL again as we are in fact moving the cursor twice
594 | dec hl
595 | ld (_file_cursor_x), hl
596 | ; Get the last screen width - 1 characters from the model
597 | ld bc, (_editor_screen_width)
598 | dec c
599 | ; Take advantage of the fact that BC is set to set the X cursor on screen
600 | ld a, c
601 | ld (_cursor_x), a
602 | ; Get the previous BC bytes from where the cursor is
603 | call model_get_bc_left_chars
604 | ; The cursor is not at X = 0, we can do a syscall for thi, but it will add
605 | ; an overhead, so, a faster way is to insert a \b before the buffer.
606 | ; Of course, we have to save its content first
607 | dec hl
608 | ld a, (hl)
609 | ld (hl), '\b'
610 | ; We jus added a new char, increment BC
611 | inc bc
612 | ex de, hl
613 | push af
614 | S_WRITE1(DEV_STDOUT)
615 | ; Restore the -1st byte of the buffer
616 | pop af
617 | ld (de), a
618 | ; Print the character that must be under the cursor
619 | ; FIXME: Use a '>' to show that there are more characters hiding?
620 | ld bc, 1
621 | call model_get_bc_right_chars
622 | ex de, hl
623 | S_WRITE1(DEV_STDOUT)
624 | jp _editor_view_restore_cursor
625 |
626 |
627 | ; Move the cursor right
628 | PUBLIC _editor_view_right_cursor
629 | _editor_view_right_cursor:
630 | ld hl, _cursor_x
631 | ; If the cursor has reached the end of the line (last character), we have to
632 | ; horizontally scroll the content.
633 | call editor_line_rem_size
634 | ; If there is 1 space left on screen, we will reach the border after writing the current one
635 | dec a
636 | jr z, _editor_view_right_cursor_overflow
637 | _editor_view_right_cursor_no_overflow:
638 | ; No overflow, we can simply move X cursor right (increment)
639 | call editor_view_increment_x
640 | jp _editor_view_restore_cursor
641 | _editor_view_right_cursor_overflow:
642 | ; In total we will increment the absolute X cursor twice, because we have to print
643 | ; back the last character on the first column.
644 | call editor_view_increment_x
645 | ; Clean the buffer line, set the visual X cursor to 0
646 | call _editor_view_erase_buffer
647 | ; Reposition the cursor to the beginning of the screen
648 | xor a
649 | ld (_cursor_x), a
650 | call _editor_view_restore_cursor
651 | ; Get the character that is right before the cursor and the characters that are
652 | ; right after, put them inside our own buffer.
653 | ld bc, 1
654 | call model_get_bc_left_chars
655 | ld de, _editor_view_buffer
656 | ldi
657 | ; DE points to the rest of the buffer
658 | ld bc, (_editor_screen_width)
659 | push bc
660 | dec c ; the width is in fact an 8-bit value
661 | call model_get_bc_right_chars
662 | ; Copy them into our buffer now (BC has been adjusted)
663 | ldir
664 | ; Print our buffer now
665 | pop bc
666 | ld de, _editor_view_buffer
667 | S_WRITE1(DEV_STDOUT)
668 | jp _editor_view_right_cursor_no_overflow
669 |
670 |
671 | ; Check if the cursor is at the bottom of the text area
672 | ; Parameters:
673 | ; -
674 | ; Returns:
675 | ; Z flag - Bottom of text area
676 | ; NZ flag - Not at the bottom of text area
677 | ; Alters:
678 | ; A, D
679 | _editor_cursor_bottom_line:
680 | ld a, (_cursor_y)
681 | ld d, a
682 | ld a, (_editor_screen_height)
683 | ; The text area is smaller than the screen height because of the header and footer
684 | sub EDITOR_FOOTER_HEIGHT + 1
685 | cp d
686 | ret
687 |
688 |
689 | ; Copy a given string to the given buffer.
690 | ; If the string is smaller than the buffer, the latter will be stuffed with spaces.
691 | ; At most _editor_screen_width characters will be written to the buffer.
692 | ; Parameters:
693 | ; HL - Source string address
694 | ; Returns:
695 | ; BC - Total bytes written
696 | _editor_copy_string_to_buffer:
697 | ; Copy the lines and stuff the buffer with spaces
698 | ld bc, (_editor_screen_width)
699 | push bc
700 | ld de, _editor_view_buffer
701 | ld b, c
702 | ld a, '\n'
703 | _editor_copy_string_to_buffer_fst_loop:
704 | cp (hl)
705 | jr z, _editor_copy_string_to_buffer_stuff
706 | ; Use LDI but don't use BC as the size, simply use B
707 | ldi
708 | djnz _editor_copy_string_to_buffer_fst_loop
709 | ; No more space in the buffer, return it
710 | jp _editor_copy_string_to_buffer_ret
711 | _editor_copy_string_to_buffer_stuff:
712 | ; Write the remaining characters
713 | ld a, ' '
714 | _editor_copy_string_to_buffer_loop:
715 | ld (de), a
716 | inc de
717 | djnz _editor_copy_string_to_buffer_loop
718 | ; Force a newline at the end
719 | _editor_copy_string_to_buffer_ret:
720 | ld a, '\n'
721 | ld (de), a
722 | pop bc
723 | inc bc
724 | ret
725 |
726 |
727 | ; Redraw the current line. If the line was horizontally scrolled, it will
728 | ; start back from its first character.
729 | ; The cursor will be positioned at the beginning of the following line.
730 | ; Parameters:
731 | ; None
732 | ; Returns:
733 | ; None;
734 | ; Alters:
735 | ; A, BC, DE, HL
736 | _editor_view_restore_current_line:
737 | ld a, 1
738 | call _buffer_previous_ath_line_address
739 | call _editor_copy_string_to_buffer
740 | push bc
741 | _editor_view_restore_line_from_buffer:
742 | ; Re-position the cursors to the beginning of the line
743 | ld hl, 0
744 | ld (_file_cursor_x), hl
745 | xor a
746 | ld (_cursor_x), a
747 | call _editor_view_restore_cursor
748 | ; Print the current buffer
749 | pop bc
750 | ld de, _editor_view_buffer
751 | S_WRITE1(DEV_STDOUT)
752 | ret
753 |
754 | ; Same as above but for the next line, which is bigger than the
755 | ; screen width for sure.
756 | ; Parameters:
757 | ; None
758 | ; Returns:
759 | ; None
760 | ; Alters:
761 | ; A, BC, DE, HL
762 | _editor_view_restore_next_line:
763 | call model_next_line_address
764 | ld bc, (_editor_screen_width)
765 | push bc
766 | ld de, _editor_view_buffer
767 | ldir
768 | ld a, '\n'
769 | ld (de), a
770 | jp _editor_view_restore_line_from_buffer
771 |
772 |
773 | ; Move cursor to the next line
774 | ; Parameters:
775 | ; BC - Length of the new line
776 | ; HL - Newline characters
777 | ; Parameters must be popped out of the stack!
778 | PUBLIC _editor_view_goto_newline
779 | _editor_view_goto_newline:
780 | ; We have to increment Y, if it is at the bottom of the page, we must
781 | ; manually scroll the screen by redrawing all the previous lines
782 | call _editor_cursor_bottom_line
783 | jp z, _editor_cursor_move_lines_up
784 | push hl
785 | ; Optimize a bit by restoring the current line if BC is not 0 or the current
786 | ; line is scrolled
787 | call _editor_view_restore_current_line_if_needed
788 | ; Get the remaining number of lines, so that we can update them
789 | call model_get_remaining_lines
790 | ; The total includes the newly created line.
791 | ld b, h
792 | ld c, l
793 | ; Get the remaining number of lines that we can show on screen
794 | ld hl, _cursor_y
795 | inc (hl)
796 | ld a, (_editor_screen_height)
797 | sub EDITOR_FOOTER_HEIGHT
798 | sub (hl)
799 | ; Store the result in HL
800 | ld h, 0
801 | ld l, a
802 | call editor_min_hl_bc
803 | ; The new line address was given to us as a parameter
804 | pop hl
805 | ; Move the number of lines to update in B
806 | ld b, c
807 | call _editor_view_print_b_lines
808 | jp _editor_view_goto_newline_size_0
809 |
810 | ; Checks if the current line needs to be restored, adn restores it
811 | _editor_view_restore_current_line_if_needed:
812 | call editor_view_long_line
813 | jp nz, _editor_view_restore_current_line
814 | ld a, b
815 | or c
816 | jp nz, _editor_view_restore_current_line
817 | ; Position the cursor at the beginning of the next line
818 | ld a, (_cursor_y)
819 | inc a
820 | ld e, a
821 | ld d, 0
822 | jp _editor_view_restore_cursor_no_load
823 |
824 |
825 | ; Jump to here if the cursor is at the bottom of the screen and we need to
826 | ; go to the next line. In order word, we will scroll down all the lines
827 | ; (by moving them up once...)
828 | _editor_cursor_move_lines_up:
829 | ld a, (_editor_screen_height)
830 | sub EDITOR_FOOTER_HEIGHT + 1
831 | ; Save the new line size
832 | push bc
833 | call _editor_view_scroll_down
834 | pop bc
835 | ; As scrolling printed all the lines, including the new one, the only thing we
836 | ; have to do now is clean BC characters on screen. Because these chars used to
837 | ; be at the end of the previous line.
838 | ld a, b
839 | or c
840 | ; If the size is 0, nothing to clean, simply reposition the cursor
841 | jr z, _editor_view_goto_newline_size_0
842 | ; Calculate the minimum between the screen width and the number of characters to
843 | ; clean on screen
844 | push bc
845 | call _editor_view_erase_buffer
846 | pop hl
847 | call editor_min_hl_bc
848 | ld de, _editor_view_buffer
849 | S_WRITE1(DEV_STDOUT)
850 | ; The position is 0 for sure because we just jumped to a new line.
851 | ; Store 0 in X and absolute X.
852 | _editor_view_goto_newline_size_0:
853 | ld hl, 0
854 | ld (_file_cursor_x), hl
855 | ld a, l
856 | ld (_cursor_x), a
857 | jp _editor_view_restore_cursor
858 |
859 |
860 | ; Reposition the cursor sent by the model if necessary and print the current
861 | ; line at the current cursor location.
862 | ; Parameters:
863 | ; H - Former X coordinate
864 | ; BC - New X position given by the model
865 | ; Returns:
866 | ; BC - New coordinates
867 | _editor_view_reposition_cursor:
868 | ld a, h
869 | ld l, a
870 | ld h, 0
871 | or a
872 | sbc hl, bc
873 | ; If no carry, the buffer is only printed on screen, no need to modify it either
874 | ret nc
875 | ld h, 0
876 | ld l, a
877 | push hl
878 | ; The screen is actually already showing the beginning of the line, so there
879 | ; is no need to re-print it (this routine was entered because we don't need
880 | ; to scroll)
881 | call model_move_cursor_left_to
882 | pop bc
883 | ret
884 |
885 |
886 | ; Move the cursor to the previous line
887 | ; Parameters:
888 | ; BC - New cursor position after moving to the previous line
889 | PUBLIC _editor_view_cursor_prev_line
890 | _editor_view_cursor_prev_line:
891 | push bc
892 | ld a, (_cursor_y)
893 | dec a
894 | jp z, _editor_view_prev_line_scroll
895 | ; No need to scroll here, first, check if the current line is a long line (and the
896 | ; cursor is beyond the limit)
897 | call editor_view_long_line
898 | ; Save the current X value
899 | ld a, (_cursor_x)
900 | push af
901 | ; If it is, we need to restore current line (which is now considered next by the
902 | ; model as the cursor was repositioned)
903 | call nz, _editor_view_restore_next_line
904 | ; Current line was restored, save the new coordinate
905 | ld hl, _cursor_y
906 | dec (hl)
907 | _editor_view_line_changed_restore:
908 | ; New cursor position must not be scrolled, so we have to reposition it.
909 | pop hl
910 | pop bc
911 | ; This label can be used to reposition and restore the model cursor to a value
912 | ; that won't require horizontal scrolling
913 | _editor_view_reposition_model_and_restore:
914 | call _editor_view_reposition_cursor
915 | ; Returns the new X coordinate in BC
916 | ld (_file_cursor_x), bc
917 | ld a, c
918 | ld (_cursor_x), a
919 | jp _editor_view_restore_cursor
920 |
921 | _editor_view_next_line_scroll:
922 | ld a, (_editor_screen_height)
923 | sub EDITOR_FOOTER_HEIGHT + 1
924 | call _editor_view_scroll_down
925 | jp _editor_view_reposition_after_scroll
926 |
927 | _editor_view_prev_line_scroll:
928 | call _editor_view_scroll_up
929 | ; Fall-through
930 |
931 | _editor_view_reposition_after_scroll:
932 | pop bc
933 | ; All the lines have been scrolled, and the current one has been updated, we simply
934 | ; need to update the cursor now
935 | ld a, (_cursor_x)
936 | ld h, a
937 | jp _editor_view_reposition_model_and_restore
938 |
939 |
940 | ; Move the cursor to the next line
941 | ; Parameters:
942 | ; BC - New cursor position after moving to the next line
943 | PUBLIC _editor_view_cursor_next_line
944 | _editor_view_cursor_next_line:
945 | push bc
946 | call _editor_cursor_bottom_line
947 | jp z, _editor_view_next_line_scroll
948 | ; No scroll needed but we may need to restore the current line if
949 | ; it was horizontally scrolled.
950 | call editor_view_long_line
951 | ; Save the current X value
952 | ld a, (_cursor_x)
953 | push af
954 | ; If it is, we need to restore current line (which is now considered
955 | ; previous by the model as the cursor was repositioned)
956 | call nz, _editor_view_restore_current_line
957 | ; Current line was restored, save the new coordinate
958 | ld hl, _cursor_y
959 | inc (hl)
960 | jp _editor_view_line_changed_restore
961 |
962 |
963 | ; Prepare the buffer and the watermark counter before a scroll loop
964 | ; Alters:
965 | ; HL, DE, A
966 | _editor_view_scroll_init:
967 | ; Add \n at the end of the buffer FOR THE CURRENT SCREEN WIDTH
968 | ld a, '\n'
969 | ld hl, _editor_view_buffer
970 | ld de, (_editor_screen_width)
971 | add hl, de
972 | ld (hl), a
973 | ; Set the previous line length to the maximum so that it's cleaned
974 | ; by the first line to print
975 | ld a, e
976 | ld (_editor_view_8bit_counter), a
977 | ; Write the code that "returns" the length of the line to override in A
978 | ; For scrolling down: ld a, (_editor_view_8bit_counter)
979 | ld a, 0x3a ; ld a, (nnnn)
980 | ld hl, _editor_view_8bit_counter ; nnnn
981 | ld (_editor_view_copy_and_fill_modify_me), a
982 | ld (_editor_view_copy_and_fill_modify_me + 1), hl
983 | ret
984 |
985 |
986 | ; Calculate the length of the string present in HL
987 | ; This routine must NOT modify any register (except A of course)
988 | ; Parameters:
989 | ; HL - Address of the line to erase
990 | ; Returns:
991 | ; A - Length of the line to erase
992 | _editor_get_line_to_erase_length:
993 | push hl
994 | push bc
995 | ld bc, (_editor_screen_width)
996 | push bc
997 | ld a, '\n'
998 | cpir
999 | pop hl
1000 | or a
1001 | sbc hl, bc
1002 | ; The result will be 8-bit because the screen width is 8-bit
1003 | ld a, l
1004 | pop bc
1005 | pop hl
1006 | ret
1007 |
1008 |
1009 | ; Parameters:
1010 | ; B - Number of lines to print
1011 | ; HL - Address of the buffer containing the lines
1012 | _editor_view_print_b_lines:
1013 | push hl
1014 | call _editor_view_scroll_init
1015 | pop hl
1016 | jp _editor_view_print_n_lines
1017 |
1018 |
1019 | ; Scroll the lines up on screen.
1020 | ; Parameters:
1021 | ; BC - New position of the cursor after moving it to the previous line
1022 | ; Returns:
1023 | ; -
1024 | _editor_view_scroll_up:
1025 | ; Set the cursor to the top of the view and start drawing
1026 | ld de, 1 ; Set the cursor to (0,1)
1027 | call _editor_view_restore_cursor_no_load
1028 | ; Set the watermark to the minimum
1029 | call _editor_view_scroll_init
1030 | ; Print current line WITH newline at the end
1031 | ld a, 1
1032 | call _editor_view_print_current_line
1033 | call model_next_line_address
1034 | ; Number of lines to restore
1035 | ld a, (_editor_screen_height)
1036 | sub EDITOR_FOOTER_HEIGHT + 2
1037 | ld b, a
1038 | _editor_view_print_n_lines:
1039 | dec b
1040 | inc b
1041 | ret z
1042 | ; Modify the code in the routine _editor_view_copy_and_fill to calculate the length of the line
1043 | ; to override
1044 | ; New code: call _editor_get_line_to_erase_length
1045 | ld a, 0xcd
1046 | ld de, _editor_get_line_to_erase_length
1047 | ld (_editor_view_copy_and_fill_modify_me), a
1048 | ld (_editor_view_copy_and_fill_modify_me + 1), de
1049 | _editor_view_scroll_up_loop:
1050 | push bc
1051 | ; Fill the temporary buffer with the current line data
1052 | call _editor_view_copy_and_fill
1053 | ; Print the line on screen
1054 | push hl
1055 | S_WRITE1(DEV_STDOUT)
1056 | pop hl
1057 | pop bc
1058 | djnz _editor_view_scroll_up_loop
1059 | ret
1060 |
1061 |
1062 | ; Scroll the lines on screen, optimized by only writing enough characters
1063 | ; to hide previous line content. Speed now depends on the amount of characters
1064 | ; on screen.
1065 | ; For lines that have around 1-4 characters, this routine is now 8x faster, for a
1066 | ; whole screen refresh.
1067 | ; Parameters:
1068 | ; A - Number of lines to scroll
1069 | _editor_view_scroll_down:
1070 | ; Get the A-th previous line of text. For example we have 36 lines to show,
1071 | ; 36th is the first one (top), because 0 is the new empty one. Ignore the
1072 | ; top most line, so decrement A.
1073 | dec a
1074 | ld d, a ; D is not altered
1075 | call _buffer_previous_ath_line_address
1076 | push hl
1077 | push de
1078 | ; Set the cursor to the top of the view and start drawing
1079 | ld de, 1 ; Set the cursor to (0,1)
1080 | call _editor_view_restore_cursor_no_load
1081 | call _editor_view_scroll_init
1082 | ; Store the number of iterations in BC
1083 | pop bc
1084 | pop hl
1085 | _editor_view_scroll_down_loop:
1086 | push bc
1087 | ; Fill the temporary buffer with the current line data
1088 | call _editor_view_copy_and_fill
1089 | ; Print the line on screen
1090 | push hl
1091 | S_WRITE1(DEV_STDOUT)
1092 | pop hl
1093 | pop bc
1094 | djnz _editor_view_scroll_down_loop
1095 | ; Print the current line without a \n, to prevent the cursor from going to the next line
1096 | xor a
1097 | ; Print the model current line
1098 | ; Parameters:
1099 | ; A - 1 if print a newline at the end of the current line, 0 else
1100 | _editor_view_print_current_line:
1101 | push af
1102 | ; Write the last line, which is the current line
1103 | ld de, _editor_view_buffer
1104 | ; B will be 0, so the routine will return B = 0
1105 | ld bc, (_editor_screen_width)
1106 | call model_get_current_line
1107 | ld a, c
1108 | call _editor_view_copy_and_fill_size_in_a
1109 | ; Decrement C if we must not include/print the final \n
1110 | pop af
1111 | or a
1112 | ; A != 0 <=> Do not decrement C
1113 | jr nz, _editor_view_print_current_line_with_newline
1114 | dec c
1115 | _editor_view_print_current_line_with_newline:
1116 | S_WRITE1(DEV_STDOUT)
1117 | ret
1118 |
1119 |
1120 | ; Parameters:
1121 | ; HL - Address of the current line to print
1122 | ; Returns:
1123 | ; DE - Address of the buffer to print
1124 | ; HL - Address of the next line to print
1125 | ; Alters:
1126 | ; A, BC, DE, HL
1127 | _editor_view_copy_and_fill:
1128 | ld de, _editor_view_buffer
1129 | ld a, '\n'
1130 | ; Maximum size to write is the size of the screen
1131 | ld bc, (_editor_screen_width)
1132 | push bc
1133 | _editor_view_copy_and_fill_loop:
1134 | cp (hl)
1135 | jr z, _editor_view_copy_and_fill_end
1136 | ldi
1137 | jp pe, _editor_view_copy_and_fill_loop
1138 | ; The given line from the model has a bigger size than the screen size
1139 | ; Look for its ending \n, to return next line address
1140 | cpir
1141 | ; Add a newline at the end of DE
1142 | ld (de), a
1143 | ; Pop the size of the current line in BC
1144 | pop bc
1145 | ; Save the current buffer size
1146 | ld a, c
1147 | ; Increment C after setting A as the newline doesn't count as part of the current line length
1148 | inc c
1149 | jp _editor_view_copy_and_fill_ret
1150 | _editor_view_copy_and_fill_end:
1151 | inc hl ; Make HL point to the character after the newline
1152 | ; Add a new line at the end of DE
1153 | ld (de), a
1154 | ; Calculate the current size
1155 | ld a, c
1156 | pop bc
1157 | ld b, a
1158 | ld a, c
1159 | sub b
1160 | _editor_view_copy_and_fill_size_in_a:
1161 | ; Check if the previous buffer was bigger than the current, if so,
1162 | ; fill the difference with spaces
1163 | ld b, a
1164 | ; Code that will be modified at runtime, upon execution, it stores the length
1165 | ; of the size to overwrite in A
1166 | ; For scrolling down: ld a, (_editor_view_8bit_counter)
1167 | ; For scrolling up: call _editor_get_line_to_erase_length
1168 | _editor_view_copy_and_fill_modify_me:
1169 | nop
1170 | nop
1171 | nop
1172 | ld c, a
1173 | ; Calculate current_size - previous_size
1174 | ld a, b
1175 | sub c
1176 | ; If there is no carry, the previous size was smaller, no need to fill
1177 | ; the buffer with spaces but we have to update the size.
1178 | jp nc, _editor_view_copy_and_fill_smaller
1179 | ; Fill the buffer with |A - C| spaces, current length is still in B.
1180 | neg
1181 | ld c, b ; Current length in C
1182 | ld b, a ; Number of spaces to write
1183 | ; Save the current length in the global variable
1184 | ld a, c
1185 | ld (_editor_view_8bit_counter), a
1186 | ; At the end, return BC = current length + number of spaces written
1187 | add b
1188 | push af
1189 | ex de, hl
1190 | ld a, ' '
1191 | _editor_view_copy_and_fill_end_loop:
1192 | ld (hl), a
1193 | inc hl
1194 | djnz _editor_view_copy_and_fill_end_loop
1195 | ld (hl), '\n'
1196 | ex de, hl
1197 | ; Return the number of characters to write on screen
1198 | pop bc
1199 | ld c, b
1200 | ld b, 0
1201 | ; Increment (B)C because of the final \n
1202 | inc c
1203 | ld de, _editor_view_buffer
1204 | ret
1205 | _editor_view_copy_and_fill_smaller:
1206 | ; The current size, in B, is bigger or equal to the previous line size.
1207 | ; We need to print B characters.
1208 | ld c, b
1209 | ld b, 0
1210 | ld a, c
1211 | ; Increment (B)C because a '\n' was added
1212 | inc c
1213 | _editor_view_copy_and_fill_ret:
1214 | ld (_editor_view_8bit_counter), a
1215 | ld de, _editor_view_buffer
1216 | ret
1217 |
1218 |
1219 | SECTION DATA
1220 |
1221 | ; The following represent the cursor on screen, it is synchronized with the
1222 | ; actual driver's cursor
1223 | _cursor_y: DEFS 1
1224 | _cursor_x: DEFS 1
1225 |
1226 | ; This cursor represents the absolute value of the cursor on the line.
1227 | ; If a line is longer than the screen width, this cursor will still be incremented
1228 | ; and monotonic
1229 | _file_cursor_x: DEFS 2
1230 |
1231 | ; Using a 16-bit value will simply the code
1232 | _editor_screen_width: DEFS 2
1233 | _editor_screen_height: DEFS 1
1234 | ; Temporary buffer used to store data to print
1235 | PUBLIC _editor_view_buffer
1236 | _editor_view_buffer:
1237 | DEFS 80
1238 | DEFM '\n'
1239 | _editor_view_buffer_end:
1240 | ; 8-bit iterator for loops
1241 | _editor_view_8bit_counter: DEFS 1
1242 |
--------------------------------------------------------------------------------