├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── src
├── buffer.c
├── buffer.h
├── commands.c
├── commands.h
├── controller.c
├── controller.h
├── ex.c
├── ex.h
├── listeners.c
├── listeners.h
├── main.c
├── render.c
├── render.h
├── screen.c
├── screen.h
├── state.c
└── state.h
└── tests
├── Makefile
├── buffer-test.c
├── commands-test.c
├── file.txt
└── state-test.c
/.gitignore:
--------------------------------------------------------------------------------
1 | viw
2 | test
3 | test.txt
4 | vgcore.*
5 |
6 | # Object files
7 | *.o
8 | *.ko
9 | *.obj
10 | *.elf
11 |
12 | # Precompiled Headers
13 | *.gch
14 | *.pch
15 |
16 | # Libraries
17 | *.lib
18 | *.a
19 | *.la
20 | *.lo
21 |
22 | # Shared objects (inc. Windows DLLs)
23 | *.dll
24 | *.so
25 | *.so.*
26 | *.dylib
27 |
28 | # Executables
29 | *.exe
30 | *.out
31 | *.app
32 | *.i*86
33 | *.x86_64
34 | *.hex
35 |
36 | # Debug files
37 | *.dSYM/
38 | *.su
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | {one line to give the program's name and a brief idea of what it does.}
635 | Copyright (C) {year} {name of author}
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | {project} Copyright (C) {year} {fullname}
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | CC=gcc
2 | CFLAGS=
3 | LDFLAGS=--std=gnu11 -lncurses
4 |
5 | ifeq (Windows_NT, $(OS))
6 | CFLAGS+=$(shell ncursesw6-config --cflags)
7 | LDFLAGS=$(shell ncursesw6-config --libs)
8 | endif
9 |
10 | build:
11 | @$(CC) $(CFLAGS) src/*.c -o viw $(LDFLAGS)
12 |
13 | run:
14 | @$(MAKE) build
15 | @./viw test.txt
16 | @rm viw
17 |
18 | clean:
19 | rm -f viw *~
20 |
21 | mem:
22 | @$(CC) $(CFLAGS) src/*.c -o viw $(LDFLAGS)
23 | valgrind --leak-check=yes ./viw test.txt
24 | @rm viw
25 |
26 | test-buffer:
27 | @$(MAKE) -C tests buffer
28 |
29 | test-commands:
30 | @$(MAKE) -C tests commands
31 |
32 | test-state:
33 | @$(MAKE) -C tests state
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Viw, the ghetto editor
2 |
3 | VI Worsened, a lightweight and fun VI clone. Inspired by the
4 | [6-domino-cascade](https://github.com/Day8/re-frame#it-is-a-6-domino-cascade)
5 | from the React world.
6 |
7 | ### DEMO
8 | https://vimeo.com/205701269
9 | ## Dependencies
10 |
11 | * gcc
12 | * ncurses
13 |
14 | ```console
15 | # Fedora
16 | sudo dnf install ncurses-devel
17 |
18 | # Debian/Ubuntu
19 | sudo apt-get install libncurses5-dev
20 | ```
21 |
22 | ## Installation & usage
23 |
24 | ```console
25 | git clone https://github.com/lpan/viw
26 | cd viw/
27 | make build
28 | ./viw [filename]
29 | ```
30 |
31 | Using mingw compiler on Windows, you need to install `mingw-w64-x86_64-ncurses`
32 |
33 | ```console
34 | pacman -S mingw-w64-x86_64-ncurses
35 | mingw32-make build
36 | ```
37 |
38 | ### Supported keybindings
39 |
40 | - `j` Cursor down
41 | - `k` Cursor up
42 | - `h` Cursor left
43 | - `l` Cursor right
44 | - `0` Cursor to the beginning of the line
45 | - `$` Cursor to the end of the line
46 | - `i` Insert before
47 | - `I` Insert at the beginning of the line
48 | - `a` Insert after
49 | - `A` Insert at the end of the line
50 | - `o` Append line then insert
51 | - `O` Prepend line then insert
52 | - `dd` Delete line under the cursor
53 | - `gg` Go to the first line of the file
54 | - `G` Go the last line of the file
55 | - `u` Undo
56 | - `r` Redo (**Unstable**)
57 |
58 | ### Supported EX mode commands
59 | - `:q` quit
60 | - `:w` save
61 | - `:wq` save then quit
62 |
63 | ## Hacking
64 |
65 | Feel free to contribute! :)
66 |
67 | ### How does it work
68 |
69 | 1. initiate the state
70 | - Read file to buffer.
71 | - Set up interface with ncurses
72 | 2. Listen to keyboard events.
73 | - Each supported keybinding is mapped to a method that mutates the `buffer`
74 | 3. Run `update_state(st)` and `render_update(st)`
75 | - Similar to `selectors` in redux, `update_state(state_t *st)` will update all the
76 | computed properties (such as cursor position, rows to be displayed on the screen, etc)
77 | according to the new mutated `buffer` state.
78 | - `render_update(state_t *st)` will actually render everything on the screen according to
79 | the result from `update_state()`.
80 | 4. Goto step 2
81 |
82 | ### Undo & Redo
83 |
84 | Viw's undo & redo functionality is based on the [state machine replication principle](https://en.wikipedia.org/wiki/State_machine_replication)
85 | 1. Initialization:
86 | * Deep clone the initial buffer.
87 | * Initialize two stacks (`history stack` and `redo stack`).
88 | 2. Capture all state-mutating functions and their payloads and push it on to the
89 | `history stack`.
90 | 3. When the user hits `undo`:
91 | * Pop the `history stack` and the push the result onto `redo stack`.
92 | * Clone the initial buffer and apply all the commands saved in the `history stack`
93 | on top of it.
94 | 4. When the user hits `redo`:
95 | * Pop the `redo stack` and apply the command immediately onto the current buffer.
96 | 5. Clear the `redo stack` when a command gets pushed to the `history stack` by the user.
97 |
98 | See https://github.com/lpan/viw/blob/master/src/controller.c#L152 for more details
99 |
100 | ### Hierarchy of the states
101 |
102 | Our main `state` object has two children states, namely `buffer` and `screen`. This seperation
103 | makes it easier to perform unit tests against the `buffer`. It also facilitates the migration
104 | to a different rendering library in the future.
105 |
106 | #### The State
107 |
108 | * The parent state stores computed states that depend on the `buffer` and/or `screen`. Those states
109 | include cursor positions, aount of space reserved for line numbers, etc.
110 |
111 | #### The Buffer
112 |
113 | * The buffer is represented as a two dimensional doubly linked list. This
114 | allows conatant time line/char deletion and insertion. Go to [buffer.h](/src/buffer.h)
115 | for more information.
116 | * Buffer is only allowed to be modified by the methods declared in `buffer.h`
117 |
118 | #### The Screen
119 |
120 | * The screen is the state of the interface between viw and GNU ncurses. It stores information
121 | such as pointers to the ncurses windows, etc. You can learn more about it at
122 | [screen.h](/src/screen.h).
123 |
--------------------------------------------------------------------------------
/src/buffer.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "buffer.h"
7 |
8 | #ifdef _WIN32
9 | int getline(char **lp, size_t *n, FILE *fp) {
10 | char buf[BUFSIZ];
11 | char *p;
12 | size_t len;
13 |
14 | if (lp == NULL || n == NULL) {
15 | errno = EINVAL;
16 | return -1;
17 | }
18 |
19 | if (ferror (fp) || feof(fp))
20 | return -1;
21 |
22 | buf[0] = '\0';
23 | fgets(buf, BUFSIZ, fp);
24 | p = strchr(buf, '\n');
25 | if (p) *p = '\0';
26 | len = strlen(buf);
27 |
28 | if (len < BUFSIZ-1) {
29 | p = realloc(*lp, BUFSIZ);
30 | if (p == NULL) return -1;
31 | *lp = p;
32 | *n = BUFSIZ;
33 | }
34 |
35 | strcpy(*lp, buf);
36 | return len;
37 | }
38 | #endif
39 |
40 | /*
41 | * Read file to buffer
42 | */
43 | static void read_file(buffer_t *buf, const char *filename) {
44 | // read file to memory
45 | FILE *fp = fopen(filename, "r");
46 | if (fp) {
47 | char *line = NULL;
48 | size_t len = 0;
49 |
50 | for (size_t i = 0; getline(&line, &len, fp) != -1; i++) {
51 | // trim newline char
52 | line[strcspn(line, "\n")] = 0;
53 |
54 | append_row(buf, line);
55 | }
56 |
57 | free(line);
58 | line = NULL;
59 | fclose(fp);
60 | }
61 |
62 | // insert empty row if file is empty or doesn't exist
63 | if (!buf->current) {
64 | append_row(buf, NULL);
65 | }
66 | }
67 |
68 | void add_char(row_t *r, char c) {
69 | echar_t *ec = malloc(sizeof(echar_t));
70 | echar_t *next = NULL;
71 | echar_t *prev = NULL;
72 |
73 | ec->c = c;
74 | ec->prev = NULL;
75 | ec->next = NULL;
76 |
77 | if (r->line_size == 0) {
78 | r->head = ec;
79 | r->last = ec;
80 | r->current = ec;
81 | r->line_size ++;
82 | r->is_dirty = true;
83 | return;
84 | }
85 |
86 | if (r->current == r->last) {
87 | prev = r->current;
88 | prev->next = ec;
89 | r->last = ec;
90 | } else {
91 | prev = r->current;
92 | next = r->current->next;
93 | prev->next = ec;
94 | next->prev = ec;
95 | }
96 |
97 | ec->next = next;
98 | ec->prev = prev;
99 |
100 | r->is_dirty = true;
101 | r->line_size ++;
102 | r->current = ec;
103 | }
104 |
105 | /*
106 | * Init a row with a NULL char at the beginning
107 | */
108 | row_t *init_row(const char *line) {
109 | row_t *r = malloc(sizeof(row_t));
110 |
111 | r->next = NULL;
112 | r->prev = NULL;
113 | r->line_size = 0;
114 | r->is_dirty = true;
115 |
116 | r->head = NULL;
117 | r->last = NULL;
118 | r->current = NULL;
119 |
120 | if (!line) {
121 | return r;
122 | }
123 |
124 | for (size_t i = 0; i < strlen(line); i ++) {
125 | add_char(r, line[i]);
126 | }
127 |
128 | // set current to be the first char
129 | r->current = r->head;
130 | return r;
131 | }
132 |
133 | void destroy_row(row_t *r) {
134 | echar_t *c = r->head, *tmp;
135 | while (c) {
136 | tmp = c;
137 | c = c->next;
138 | free(tmp);
139 | }
140 | free(r);
141 | }
142 |
143 | buffer_t *init_buffer(const char *filename) {
144 | buffer_t *buf = malloc(sizeof(buffer_t));
145 |
146 | buf->num_rows = 0;
147 | buf->head = NULL;
148 | buf->last = NULL;
149 | buf->current = NULL;
150 |
151 | buf->is_dirty = false;
152 | buf->filename = filename;
153 |
154 | buf->mode = NORMAL;
155 |
156 | read_file(buf, filename);
157 |
158 | // we want to start at the top when user opens up a new file
159 | buf->current = buf->head;
160 | // Set current to the first char of line
161 | buf->current->current = buf->current->head;
162 |
163 | buf->current_char = 0;
164 | buf->current_row = 0;
165 |
166 | // go to the first non-empty character
167 | if (buf->current->line_size > 1) {
168 | while (buf->current->current->c == ' ' && buf->current->current->next) {
169 | buf->current->current = buf->current->current->next;
170 | buf->current_char ++;
171 | }
172 | }
173 |
174 | return buf;
175 | }
176 |
177 | /*
178 | * Append char to line then set the current char to new char
179 | */
180 | void append_char(buffer_t *buf, char c) {
181 | if (buf->current->line_size != 0) {
182 | buf->current_char ++;
183 | }
184 |
185 | add_char(buf->current, c);
186 | }
187 |
188 | /*
189 | * Prepend char to line then set the current char to new char
190 | */
191 | void prepend_char(buffer_t *buf, char c) {
192 | row_t *r = buf->current;
193 |
194 | assert(r->line_size > 0);
195 |
196 | // insert at head
197 | if (r->head && r->current == r->head) {
198 | echar_t *ec = malloc(sizeof(echar_t));
199 | ec->c = c;
200 | ec->prev = NULL;
201 | ec->next = r->head;
202 |
203 | r->head->prev = ec;
204 | r->head = ec;
205 | r->current = ec->next;
206 |
207 | buf->current_char ++;
208 | r->is_dirty = true;
209 | r->line_size ++;
210 | return;
211 | }
212 |
213 | r->current = r->current->prev;
214 | append_char(buf, c);
215 | r->current = r->current->next;
216 | }
217 |
218 | void drop_char(row_t *r) {
219 | if (r->line_size == 0) {
220 | return;
221 | }
222 |
223 | echar_t *to_delete = r->current;
224 |
225 | if (r->line_size == 1) {
226 | to_delete = r->current;
227 | r->current = NULL;
228 | r->head = NULL;
229 | r->last = NULL;
230 |
231 | r->line_size --;
232 | r->is_dirty = true;
233 |
234 | free(to_delete);
235 | return;
236 | }
237 |
238 | if (to_delete == r->head) {
239 | r->current = to_delete->next;
240 | r->head = r->current;
241 | r->head->prev = NULL;
242 | } else if (to_delete == r->last) {
243 | r->current = to_delete->prev;
244 | r->last = r->current;
245 | r->last->next = NULL;
246 | } else {
247 | r->current = to_delete->next;
248 | r->current->prev = to_delete->prev;
249 | to_delete->prev->next = r->current;
250 | }
251 |
252 | r->line_size --;
253 | r->is_dirty = true;
254 |
255 | free(to_delete);
256 | }
257 |
258 | /*
259 | * Delete current char and set 'current' to point to next char
260 | * if only NULL char is present, do nothing
261 | * if next char is NULL, point to the previous char
262 | */
263 | void delete_char(buffer_t *buf) {
264 | row_t *r = buf->current;
265 |
266 | if (r->line_size > 1 && r->current && r->current == r->last) {
267 | buf->current_char --;
268 | }
269 |
270 | drop_char(buf->current);
271 | }
272 |
273 | /*
274 | * Add an empty line below the current line and set current to the new line
275 | */
276 | void append_row(buffer_t *buf, const char *line) {
277 | row_t *r = init_row(line);
278 | row_t *prev = NULL;
279 | row_t *next = NULL;
280 |
281 | if (buf->num_rows == 0) {
282 | buf->head = r;
283 | buf->last= r;
284 | } else if (buf->current == buf->last) {
285 | prev = buf->current;
286 | prev->next = r;
287 | buf->last = r;
288 | } else {
289 | prev = buf->current;
290 | next = buf->current->next;
291 | prev->next = r;
292 | next->prev = r;
293 | }
294 |
295 | r->next = next;
296 | r->prev = prev;
297 |
298 | buf->current_char = 0;
299 | buf->current_row ++;
300 | buf->num_rows ++;
301 | buf->current = r;
302 | }
303 |
304 | /*
305 | * Add an empty line above the current line and set current to the new line
306 | */
307 | void prepend_row(buffer_t *buf, const char *line) {
308 | // insert at head
309 | if (buf->head && buf->current == buf->head) {
310 | row_t *r = init_row(line);
311 | r->prev = NULL;
312 | r->next = buf->head;
313 |
314 | buf->head->prev = r;
315 | buf->head = r;
316 | buf->current = r;
317 |
318 | buf->current_char = 0;
319 | buf->current_row = 0;
320 | buf->num_rows ++;
321 | return;
322 | }
323 |
324 | move_current(buf, UP);
325 | append_row(buf, line);
326 | }
327 |
328 | static void adjust_current_char(buffer_t *buf) {
329 | row_t *r = buf->current;
330 | echar_t *ec = r->head;
331 |
332 | if (!ec) {
333 | buf->current_char = 0;
334 | return;
335 | }
336 |
337 | for (size_t i = 0; i < buf->current_char; i ++) {
338 | if (!ec->next) {
339 | buf->current_char = i;
340 | break;
341 | }
342 |
343 | ec = ec->next;
344 | }
345 |
346 | r->current = ec;
347 | }
348 |
349 | static void move_up(buffer_t *buf) {
350 | if (buf->current->prev) {
351 | buf->current = buf->current->prev;
352 | buf->current_row --;
353 | adjust_current_char(buf);
354 | }
355 | }
356 |
357 | static void move_down(buffer_t *buf) {
358 | if (buf->current->next) {
359 | buf->current = buf->current->next;
360 | buf->current_row ++;
361 | adjust_current_char(buf);
362 | }
363 | }
364 |
365 | static void move_left(buffer_t *buf) {
366 | row_t *r = buf->current;
367 | if (r->current && r->current->prev) {
368 | r->current = r->current->prev;
369 | buf->current_char --;
370 | }
371 | }
372 |
373 | static void move_right(buffer_t *buf) {
374 | row_t *r = buf->current;
375 | if (r->current && r->current->next) {
376 | r->current = r->current->next;
377 | buf->current_char ++;
378 | }
379 | }
380 |
381 | /*
382 | * Move the cursor up/down/left/right
383 | * Do nothing if it reaches the end
384 | */
385 | void move_current(buffer_t *buf, DIRECTION d) {
386 | switch (d) {
387 | case UP:
388 | move_up(buf);
389 | break;
390 | case DOWN:
391 | move_down(buf);
392 | break;
393 | case LEFT:
394 | move_left(buf);
395 | break;
396 | case RIGHT:
397 | move_right(buf);
398 | break;
399 | default:
400 | break;
401 | }
402 | }
403 |
404 | /*
405 | * Move cursor all the way to the right
406 | */
407 | void to_right(buffer_t *buf) {
408 | for (size_t i = buf->current_char; i < buf->current->line_size; i ++) {
409 | move_current(buf, RIGHT);
410 | }
411 | }
412 |
413 | void to_left(buffer_t *buf) {
414 | size_t cur = buf->current_char;
415 | for (size_t i = 0; i < cur; i ++) {
416 | move_current(buf, LEFT);
417 | }
418 | }
419 |
420 | void to_top(buffer_t *buf) {
421 | size_t cur = buf->current_row;
422 | for (size_t i = 0; i < cur; i ++) {
423 | move_current(buf, UP);
424 | }
425 | }
426 |
427 | void to_bottom(buffer_t *buf) {
428 | for (size_t i = buf->current_row; i < buf->num_rows; i ++) {
429 | move_current(buf, DOWN);
430 | }
431 | }
432 |
433 | /*
434 | * Join current row and the row above
435 | */
436 | void join_row(buffer_t *buf) {
437 | // do nothing on top line
438 | if (!buf->current->prev) {
439 | return;
440 | }
441 |
442 | row_t *src = buf->current;
443 | row_t *dest = src->prev;
444 |
445 | if (dest->line_size == 0) {
446 | move_current(buf, UP);
447 | delete_row(buf);
448 |
449 | return;
450 | }
451 |
452 | if (src->line_size == 0) {
453 | delete_row(buf);
454 | buf->current = dest;
455 | dest->current = dest->last;
456 | buf->current_row --;
457 | buf->current_char = dest->line_size - 1;
458 |
459 | return;
460 | }
461 |
462 | buf->current_char = dest->line_size;
463 |
464 | dest->line_size += src->line_size;
465 | dest->last->next = src->head;
466 | src->head->prev = dest->last;
467 |
468 | dest->current = src->head;
469 | dest->last = src->last;
470 |
471 | src->head = NULL;
472 | src->last = NULL;
473 |
474 | delete_row(buf);
475 | move_current(buf, UP);
476 | }
477 |
478 | /*
479 | * When user press enter in insert mode, we split the line into two
480 | * Current char becomes the head of the new line
481 | */
482 | void split_row(buffer_t *buf) {
483 | row_t *src = buf->current;
484 | size_t current_char = buf->current_char;
485 |
486 | append_row(buf, NULL);
487 | row_t *dest = buf->current;
488 |
489 | if (src->line_size == 0) {
490 | return;
491 | }
492 |
493 | dest->line_size = src->line_size - current_char;
494 | src->line_size = current_char;
495 |
496 | dest->head = src->current;
497 | dest->last = src->last;
498 |
499 | src->last = src->current->prev;
500 |
501 | // enter at the very end of the line
502 | if (dest->head) {
503 | dest->head->prev = NULL;
504 | }
505 |
506 | // enter at the beginning of the line
507 | if (src->last) {
508 | src->last->next = NULL;
509 | } else {
510 | // src->last is NULL implies line is empty
511 | src->head = NULL;
512 | }
513 |
514 | dest->current = dest->head;
515 | buf->current_char = 0;
516 | }
517 |
518 | void clear_row(row_t *r) {
519 | echar_t *ec = r->head;
520 | while (ec) {
521 | echar_t *tmp = ec;
522 | ec = ec->next;
523 | free(tmp);
524 | }
525 |
526 | r->last = r->head;
527 | r->current = r->head;
528 | r->head = NULL;
529 | r->line_size = 0;
530 | }
531 |
532 | /*
533 | * Delete current row and reset 'current' to point to the next row.
534 | * If next is null then set it to previous
535 | * If there is only one row left, we simply "clear" the row
536 | */
537 | void delete_row(buffer_t *buf) {
538 | row_t *to_delete = buf->current;
539 |
540 | // there is at least one row in buffer
541 | if (buf->num_rows == 1) {
542 | clear_row(buf->current);
543 | return;
544 | }
545 |
546 | if (to_delete == buf->head) {
547 | buf->current = to_delete->next;
548 | buf->current->prev = NULL;
549 | buf->head = buf->current;
550 | } else if (to_delete == buf->last) {
551 | buf->current = to_delete->prev;
552 | buf->current->next = NULL;
553 | buf->last = buf->current;
554 | buf->current_row --;
555 | } else {
556 | to_delete->prev->next = to_delete->next;
557 | to_delete->next->prev = to_delete->prev;
558 | buf->current = to_delete->next;
559 | }
560 |
561 | buf->num_rows --;
562 | destroy_row(to_delete);
563 | }
564 |
565 | void destroy_buffer(buffer_t *buf) {
566 | row_t *r = buf->head, *tmp;
567 |
568 | while (r) {
569 | tmp = r;
570 | r = r->next;
571 | destroy_row(tmp);
572 | }
573 |
574 | free(buf);
575 | }
576 |
--------------------------------------------------------------------------------
/src/buffer.h:
--------------------------------------------------------------------------------
1 | #ifndef BUFFER_H
2 | #define BUFFER_H
3 |
4 | #include
5 | #include
6 |
7 | typedef enum DIRECTION {
8 | UP,
9 | DOWN,
10 | RIGHT,
11 | LEFT
12 | } DIRECTION;
13 |
14 | typedef enum MODE {
15 | NORMAL,
16 | INSERT_FRONT,
17 | INSERT_BACK,
18 | VISUAL,
19 | EX
20 | } MODE;
21 |
22 | typedef struct echar {
23 | char c;
24 | struct echar *prev;
25 | struct echar *next;
26 | } echar_t;
27 |
28 | typedef struct row {
29 | echar_t *current;
30 | echar_t *head;
31 | echar_t *last;
32 | size_t line_size;
33 | bool is_dirty;
34 |
35 | struct row *prev;
36 | struct row *next;
37 | } row_t;
38 |
39 | typedef struct buffer {
40 | row_t *current;
41 | row_t *head;
42 | row_t *last;
43 |
44 | // total number of rows out there
45 | size_t num_rows;
46 |
47 | // current char/row (x, y) index
48 | size_t current_char;
49 | size_t current_row;
50 |
51 | bool is_dirty;
52 | const char *filename;
53 |
54 | // insert/normal/visual/ex
55 | MODE mode;
56 | } buffer_t;
57 |
58 | buffer_t *init_buffer(const char *filename);
59 |
60 | void add_char(row_t *r, char c);
61 |
62 | void drop_char(row_t *r);
63 |
64 | void append_char(buffer_t *buf, char c);
65 |
66 | void prepend_char(buffer_t *buf, char c);
67 |
68 | void delete_char(buffer_t *buf);
69 |
70 | row_t *init_row(const char *line);
71 |
72 | void destroy_row(row_t *r);
73 |
74 | void append_row(buffer_t *buf, const char *line);
75 |
76 | void prepend_row(buffer_t *buf, const char *line);
77 |
78 | void join_row(buffer_t *buf);
79 |
80 | void split_row(buffer_t *buf);
81 |
82 | void move_current(buffer_t *buf, DIRECTION d);
83 |
84 | void to_right(buffer_t *buf);
85 |
86 | void to_left(buffer_t *buf);
87 |
88 | void to_top(buffer_t *buf);
89 |
90 | void to_bottom(buffer_t *buf);
91 |
92 | void clear_row(row_t *r);
93 |
94 | void delete_row(buffer_t *buf);
95 |
96 | void destroy_buffer(buffer_t *buf);
97 |
98 | #endif
99 |
--------------------------------------------------------------------------------
/src/commands.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "commands.h"
5 | #include "controller.h"
6 |
7 | command_stack_t *init_command_stack(void) {
8 | command_stack_t *cs = malloc(sizeof(command_stack_t));
9 | cs->top = NULL;
10 | cs->bottom = NULL;
11 |
12 | return cs;
13 | }
14 |
15 | void destroy_command_stack(command_stack_t *cs) {
16 | command_t *c = cs->bottom, *tmp;
17 |
18 | while (c) {
19 | tmp = c;
20 | c = c->next;
21 | free(tmp);
22 | }
23 | free(cs);
24 | }
25 |
26 | command_t *init_command(COMMAND_TYPE t, COMMAND_PAYLOAD p) {
27 | command_t *c = malloc(sizeof(command_t));
28 | c->type = t;
29 | c->payload = p;
30 | c->next = NULL;
31 | c->prev = NULL;
32 | return c;
33 | }
34 |
35 | bool is_nav_command(command_t *c) {
36 | COMMAND_TYPE t = c->type;
37 | return t == HANDLE_MOVE || t == HANDLE_MOVE_TO_EDGE;
38 | }
39 |
40 | // -------------------------
41 | // -- History Stack methods
42 | // -------------------------
43 | command_t *append_command(command_stack_t *cs, command_t *c) {
44 | // assert valid state
45 | assert((!cs->top && !cs->bottom) || (cs->top && cs->bottom));
46 |
47 | if (!c) {
48 | return NULL;
49 | }
50 |
51 | // when cs is empty
52 | if (!cs->top && !cs->bottom) {
53 | cs->top = c;
54 | cs->bottom = c;
55 | return c;
56 | }
57 |
58 | c->prev = cs->top;
59 | cs->top->next = c;
60 | cs->top = c;
61 | return c;
62 | }
63 |
64 | command_t *pop_command(command_stack_t *cs) {
65 | assert((!cs->top && !cs->bottom) || (cs->top && cs->bottom));
66 |
67 | if (!cs->top && !cs->bottom) {
68 | return NULL;
69 | }
70 |
71 | command_t *popped = cs->top;
72 |
73 | // if there is only one command in stack
74 | if (popped->prev == NULL) {
75 | cs->top = NULL;
76 | cs->bottom = NULL;
77 | return popped;
78 | }
79 |
80 | cs->top = popped->prev;
81 | popped->prev->next = NULL;
82 | popped->prev = NULL;
83 | return popped;
84 | }
85 |
--------------------------------------------------------------------------------
/src/commands.h:
--------------------------------------------------------------------------------
1 | #ifndef COMMANDS_H
2 | #define COMMANDS_H
3 |
4 | #include "buffer.h"
5 |
6 | typedef enum COMMAND_TYPE {
7 | HANDLE_MOVE,
8 | HANDLE_MOVE_TO_EDGE,
9 | HANDLE_INSERT_CHAR,
10 | HANDLE_APPEND_ROW,
11 | HANDLE_PREPEND_ROW,
12 | HANDLE_DELETE_CHAR,
13 | HANDLE_DELETE_ROW,
14 | HANDLE_ENTER,
15 | HANDLE_BACKSPACE
16 | } COMMAND_TYPE;
17 |
18 | typedef union COMMAND_PAYLOAD {
19 | char c;
20 | char* line;
21 | DIRECTION d;
22 | } COMMAND_PAYLOAD;
23 |
24 | typedef struct command {
25 | COMMAND_TYPE type;
26 | COMMAND_PAYLOAD payload;
27 | struct command *next;
28 | struct command *prev;
29 | } command_t;
30 |
31 | typedef struct command_stack {
32 | struct command *bottom;
33 | struct command *top;
34 | } command_stack_t;
35 |
36 | command_stack_t *init_command_stack(void);
37 |
38 | void destroy_command_stack(command_stack_t *cs);
39 |
40 | command_t *init_command(COMMAND_TYPE t, COMMAND_PAYLOAD p);
41 |
42 | bool is_nav_command(command_t *c);
43 |
44 | command_t *append_command(command_stack_t *cs, command_t *c);
45 |
46 | command_t *pop_command(command_stack_t *cs);
47 |
48 | #endif
49 |
--------------------------------------------------------------------------------
/src/controller.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "screen.h"
7 | #include "buffer.h"
8 | #include "state.h"
9 | #include "commands.h"
10 | #include "controller.h"
11 |
12 | // -----------
13 | // Mutations
14 | // -----------
15 | void handle_move(state_t *st, DIRECTION d) {
16 | move_current(st->buf, d);
17 | }
18 |
19 | void handle_move_to_edge(state_t *st, DIRECTION d) {
20 | switch (d) {
21 | case UP:
22 | to_top(st->buf);
23 | st->to_refresh = true;
24 | break;
25 | case DOWN:
26 | to_bottom(st->buf);
27 | st->to_refresh = true;
28 | break;
29 | case LEFT:
30 | to_left(st->buf);
31 | break;
32 | case RIGHT:
33 | to_right(st->buf);
34 | break;
35 | default:
36 | break;
37 | }
38 | }
39 |
40 | void handle_insert_char(state_t *st, char c) {
41 | if (st->buf->mode == INSERT_FRONT) {
42 | prepend_char(st->buf, c);
43 | }
44 |
45 | if (st->buf->mode == INSERT_BACK) {
46 | append_char(st->buf, c);
47 | }
48 | }
49 |
50 | void handle_append_row(state_t *st) {
51 | append_row(st->buf, NULL);
52 | st->to_refresh = true;
53 | st->buf->mode = INSERT_BACK;
54 | }
55 |
56 | void handle_prepend_row(state_t *st) {
57 | prepend_row(st->buf, NULL);
58 | st->to_refresh = true;
59 | st->buf->mode = INSERT_BACK;
60 | }
61 |
62 | void handle_delete_char(state_t *st) {
63 | delete_char(st->buf);
64 | }
65 |
66 | void handle_delete_row(state_t *st) {
67 | delete_row(st->buf);
68 | st->to_refresh = true;
69 | }
70 |
71 | void handle_enter(state_t *st) {
72 | // Edge case: enter at the end of the line in insert_back mode
73 | if (st->buf->mode == INSERT_BACK && st->buf->current_char == st->buf->current->line_size - 1) {
74 | append_char(st->buf, '0');
75 | split_row(st->buf);
76 | delete_char(st->buf);
77 | st->to_refresh = true;
78 | return;
79 | }
80 |
81 | // in insert_back, cursor one char to the right of "current"
82 | // we always want to in insert_back mode when line is empty
83 | if (st->buf->mode == INSERT_BACK && st->buf->current->line_size != 0) {
84 | move_current(st->buf, RIGHT);
85 | st->buf->mode = INSERT_FRONT;
86 | }
87 |
88 | split_row(st->buf);
89 | st->to_refresh = true;
90 | }
91 |
92 | /*
93 | * Joine two lines when backspace at the beginning of the line
94 | * Otherwise delete char in front of the cursor
95 | */
96 | void handle_backspace(state_t *st) {
97 | buffer_t *buf = st->buf;
98 | row_t *r = buf->current;
99 |
100 | if (st->buf->mode == EX) {
101 | move_current(st->buf, LEFT);
102 | drop_char(st->status_row);
103 | }
104 |
105 | if (st->buf->mode == INSERT_FRONT) {
106 | if (!r->current->prev) {
107 | join_row(st->buf);
108 | st->to_refresh = true;
109 | return;
110 | }
111 |
112 | move_current(st->buf, LEFT);
113 | delete_char(buf);
114 | }
115 |
116 | if (st->buf->mode == INSERT_BACK) {
117 | if (!r->current) {
118 | join_row(st->buf);
119 | st->to_refresh = true;
120 | return;
121 | }
122 |
123 | if (buf->current_char == 0) {
124 | if (r->line_size == 1) {
125 | delete_char(st->buf);
126 | return;
127 | }
128 |
129 | // back insert mode cant handle this situation
130 | st->buf->mode = INSERT_FRONT;
131 | move_current(st->buf, RIGHT);
132 | handle_backspace(st);
133 | return;
134 | }
135 |
136 | delete_char(buf);
137 |
138 | if (r->current->next) {
139 | move_current(st->buf, LEFT);
140 | }
141 | }
142 | }
143 |
144 | void set_prev_key(state_t *st, char c) {
145 | st->prev_key = c;
146 | }
147 |
148 | void reset_prev_key(state_t *st) {
149 | st->prev_key = '\0';
150 | }
151 |
152 | // ------------
153 | // Undo & Redo
154 | // ------------
155 | static void dispatch_command(state_t *st, command_t *c) {
156 | switch (c->type) {
157 | case HANDLE_MOVE:
158 | handle_move(st, c->payload.d);
159 | break;
160 | case HANDLE_MOVE_TO_EDGE:
161 | handle_move_to_edge(st, c->payload.d);
162 | break;
163 | case HANDLE_INSERT_CHAR:
164 | handle_insert_char(st, c->payload.c);
165 | break;
166 | case HANDLE_APPEND_ROW:
167 | handle_append_row(st);
168 | break;
169 | case HANDLE_PREPEND_ROW:
170 | handle_prepend_row(st);
171 | break;
172 | case HANDLE_DELETE_CHAR:
173 | handle_delete_char(st);
174 | break;
175 | case HANDLE_DELETE_ROW:
176 | handle_delete_row(st);
177 | break;
178 | case HANDLE_ENTER:
179 | handle_enter(st);
180 | break;
181 | case HANDLE_BACKSPACE:
182 | handle_backspace(st);
183 | break;
184 | }
185 | }
186 |
187 | void replay_history(state_t *st) {
188 | command_stack_t *hs = st->hs;
189 | command_t *c = hs->bottom;
190 |
191 | // TODO copy init buffer but not reconstruct it
192 | buffer_t *old_buf = st->buf;
193 | st->buf = init_buffer(old_buf->filename);
194 | destroy_buffer(old_buf);
195 |
196 | while (c) {
197 | dispatch_command(st, c);
198 | c = c->next;
199 | }
200 | }
201 |
202 | /*
203 | * 1. create command
204 | * 2. push it to stack
205 | * 3. execute the command against state
206 | */
207 | void apply_command(state_t *st, COMMAND_TYPE t, COMMAND_PAYLOAD p) {
208 | command_t *c = init_command(t, p);
209 | append_command(st->hs, c);
210 | dispatch_command(st, c);
211 |
212 | // FIXME kill this when implemented redo
213 | destroy_command_stack(st->rs);
214 | st->rs = init_command_stack();
215 | }
216 |
217 | /*
218 | * 1. pop history stack
219 | * 2. push the result from 1. to redo stack
220 | * 3. Repeat step 1 if necessary
221 | * 4. replay_history()
222 | */
223 | void undo_command(state_t *st) {
224 | command_t *c = pop_command(st->hs);
225 | append_command(st->rs, c);
226 |
227 | // we only want to undo buffer-mutating commands
228 | while (c && is_nav_command(c)) {
229 | c = pop_command(st->hs);
230 | append_command(st->rs, c);
231 | }
232 |
233 | // if loop exits because stack is empty, we want to go back to the nearest
234 | // buffer-mutating command
235 | if (!c) {
236 | c = pop_command(st->rs);
237 | append_command(st->hs, c);
238 | while (c && is_nav_command(c)) {
239 | c = pop_command(st->rs);
240 | append_command(st->hs, c);
241 | }
242 |
243 | if (!c || !is_nav_command(c)) {
244 | return;
245 | }
246 | }
247 |
248 | replay_history(st);
249 | st->to_refresh = true;
250 | }
251 |
--------------------------------------------------------------------------------
/src/controller.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTROLLER_H
2 | #define CONTROLLER_H
3 |
4 | #include
5 | #include "state.h"
6 | #include "buffer.h"
7 | #include "commands.h"
8 |
9 | void handle_move(state_t *st, DIRECTION d);
10 |
11 | void handle_move_to_edge(state_t *st, DIRECTION d);
12 |
13 | void handle_insert_char(state_t *st, char c);
14 |
15 | void handle_append_row(state_t *st);
16 |
17 | void handle_prepend_row(state_t *st);
18 |
19 | void handle_delete_char(state_t *st);
20 |
21 | void handle_delete_row(state_t *st);
22 |
23 | void handle_enter(state_t *st);
24 |
25 | void handle_backspace(state_t *st);
26 |
27 | void set_prev_key(state_t *st, char c);
28 |
29 | void reset_prev_key(state_t *st);
30 |
31 | void apply_command(state_t *st, COMMAND_TYPE t, COMMAND_PAYLOAD p);
32 |
33 | void undo_command(state_t *st);
34 |
35 | void replay_history(state_t *st);
36 |
37 | #endif
38 |
--------------------------------------------------------------------------------
/src/ex.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "state.h"
5 | #include "screen.h"
6 | #include "ex.h"
7 |
8 | #define CMP1(s, s1) (strcmp(s, s1) == 0)
9 | #define CMP2(s, s1, s2) (strcmp(s, s1) == 0 || strcmp(s, s2) == 0)
10 |
11 | // linked list to array of chars
12 | static char *to_string(echar_t *head, size_t length) {
13 | char *result = malloc((length + 1) * sizeof(char));
14 | result[0] = '\0';
15 |
16 | echar_t *ec = head;
17 | for (size_t i = 0; ec && i < length; i++) {
18 | snprintf(result + i, length - i + 1, "%c", ec->c);
19 | ec = ec->next;
20 | }
21 |
22 | return result;
23 | }
24 |
25 | static char *rows_to_string(buffer_t *buf) {
26 | size_t buffer_size = buf->num_rows;
27 | row_t *r = buf->head;
28 |
29 | for (size_t i = 0; r && i < buf->num_rows; i ++) {
30 | buffer_size += r->line_size;
31 | r = r->next;
32 | }
33 |
34 | char *result = malloc(buffer_size * sizeof(char));
35 | r = buf->head;
36 |
37 | strcpy(result, "");
38 | while (r) {
39 | char *line = to_string(r->head, r->line_size);
40 | strcat(result, line);
41 |
42 | // no newline for the last line
43 | if (r->next) {
44 | strcat(result, "\n");
45 | }
46 | free(line);
47 | r = r->next;
48 | }
49 |
50 | return result;
51 | }
52 |
53 | static void quit(state_t *st) {
54 | destroy_state(st);
55 | endwin();
56 | exit(0);
57 | }
58 |
59 | static void save(state_t *st) {
60 | char *buf = rows_to_string(st->buf);
61 |
62 | FILE *fp = fopen(st->buf->filename, "w");
63 | fputs(buf, fp);
64 | free(buf);
65 | fclose(fp);
66 | }
67 |
68 | void ex_match_action(state_t *st) {
69 | // first char is ':'
70 | char *command = to_string(st->status_row->head->next, st->status_row->line_size);
71 |
72 | if (CMP2(command, "q", "quit")) {
73 | free(command);
74 | quit(st);
75 | } else if (CMP1(command, "w")) {
76 | free(command);
77 | save(st);
78 | } else if (CMP1(command, "wq")) {
79 | free(command);
80 | save(st);
81 | quit(st);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/ex.h:
--------------------------------------------------------------------------------
1 | #ifndef EX_H
2 | #define EX_H
3 |
4 | #include "state.h"
5 |
6 | void ex_match_action(state_t *st);
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/src/listeners.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "buffer.h"
3 | #include "screen.h"
4 | #include "render.h"
5 | #include "state.h"
6 | #include "controller.h"
7 | #include "ex.h"
8 | #include "listeners.h"
9 |
10 | void start_listener(state_t *st) {
11 | while (true) {
12 | update_state(st);
13 | render_update(st);
14 |
15 | switch (st->buf->mode) {
16 | case NORMAL:
17 | start_normal_listener(st);
18 | break;
19 | case INSERT_FRONT:
20 | case INSERT_BACK:
21 | start_insert_listener(st);
22 | break;
23 | case EX:
24 | start_ex_listener(st);
25 | break;
26 | case VISUAL:
27 | break;
28 | }
29 | }
30 | }
31 |
32 | void start_normal_listener(state_t *st) {
33 | int ch = getch();
34 | COMMAND_PAYLOAD p;
35 |
36 | switch (ch) {
37 | case 'j':
38 | case KEY_DOWN:
39 | p.d = DOWN;
40 | apply_command(st, HANDLE_MOVE, p);
41 | break;
42 | case 'k':
43 | case KEY_UP:
44 | p.d = UP;
45 | apply_command(st, HANDLE_MOVE, p);
46 | break;
47 | case 'h':
48 | case KEY_LEFT:
49 | p.d = LEFT;
50 | apply_command(st, HANDLE_MOVE, p);
51 | break;
52 | case 'l':
53 | case KEY_RIGHT:
54 | p.d = RIGHT;
55 | apply_command(st, HANDLE_MOVE, p);
56 | break;
57 | case '$':
58 | p.d = RIGHT;
59 | apply_command(st, HANDLE_MOVE_TO_EDGE, p);
60 | break;
61 | case '0':
62 | p.d = LEFT;
63 | apply_command(st, HANDLE_MOVE_TO_EDGE, p);
64 | break;
65 | case 'G':
66 | p.d = DOWN;
67 | apply_command(st, HANDLE_MOVE_TO_EDGE, p);
68 | break;
69 | case 'g':
70 | if (st->prev_key == 'g') {
71 | p.d = UP;
72 | apply_command(st, HANDLE_MOVE_TO_EDGE, p);
73 | reset_prev_key(st);
74 | } else {
75 | set_prev_key(st, (char) ch);
76 | }
77 | break;
78 | case 'x':
79 | apply_command(st, HANDLE_DELETE_CHAR, p);
80 | break;
81 | case 'd':
82 | if (st->prev_key == 'd') {
83 | apply_command(st, HANDLE_DELETE_ROW, p);
84 | reset_prev_key(st);
85 | } else {
86 | set_prev_key(st, (char) ch);
87 | }
88 | break;
89 | case 'I':
90 | p.d = LEFT;
91 | apply_command(st, HANDLE_MOVE_TO_EDGE, p);
92 | case 'i':
93 | if (st->buf->current->line_size == 0) {
94 | st->buf->mode = INSERT_BACK;
95 | } else {
96 | st->buf->mode = INSERT_FRONT;
97 | }
98 | break;
99 | case 'A':
100 | p.d = RIGHT;
101 | apply_command(st, HANDLE_MOVE_TO_EDGE, p);
102 | case 'a':
103 | st->buf->mode = INSERT_BACK;
104 | break;
105 | case 'o':
106 | apply_command(st, HANDLE_APPEND_ROW, p);
107 | break;
108 | case 'O':
109 | apply_command(st, HANDLE_PREPEND_ROW, p);
110 | break;
111 | case 'u':
112 | undo_command(st);
113 | break;
114 | case ':':
115 | st->buf->mode = EX;
116 | clear_row(st->status_row);
117 | add_char(st->status_row, ':');
118 | break;
119 | default:
120 | break;
121 | }
122 | }
123 |
124 | void start_ex_listener(state_t *st) {
125 | int ch = getch();
126 | switch (ch) {
127 | case '\n':
128 | ex_match_action(st);
129 | st->buf->mode = NORMAL;
130 | break;
131 | case KEY_BACKSPACE:
132 | handle_backspace(st);
133 | break;
134 | case KEY_ESC:
135 | st->buf->mode = NORMAL;
136 | break;
137 | default:
138 | add_char(st->status_row, (char) ch);
139 | break;
140 | }
141 |
142 | // exit EX mode when status bar is empty
143 | if (st->status_row->line_size == 0) {
144 | st->buf->mode = NORMAL;
145 | }
146 | }
147 |
148 | void start_insert_listener(state_t *st) {
149 | int ch = getch();
150 | switch (ch) {
151 | case '\n':
152 | handle_enter(st);
153 | break;
154 | case KEY_BACKSPACE:
155 | handle_backspace(st);
156 | break;
157 | case KEY_ESC:
158 | if (st->buf->mode == INSERT_FRONT) {
159 | handle_move(st, LEFT);
160 | }
161 |
162 | st->buf->mode = NORMAL;
163 | break;
164 | default:
165 | handle_insert_char(st, (char) ch);
166 | break;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/listeners.h:
--------------------------------------------------------------------------------
1 | #ifndef KEYPRESS_H
2 | #define KEYPRESS_H
3 |
4 | #ifdef __APPLE__
5 | #undef KEY_BACKSPACE
6 | #define KEY_BACKSPACE 127
7 | #endif
8 |
9 | #define KEY_ESC 27
10 |
11 | #include "controller.h"
12 | #include "buffer.h"
13 |
14 | // keypress listeners
15 | // There has to be exactly one listener running
16 |
17 | void start_listener(state_t *st);
18 |
19 | void start_normal_listener(state_t *st);
20 |
21 | void start_ex_listener(state_t *st);
22 |
23 | void start_insert_listener(state_t *st);
24 |
25 | #endif
26 |
--------------------------------------------------------------------------------
/src/main.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "listeners.h"
4 | #include "state.h"
5 |
6 | int main(int argc, char **argv) {
7 | if (argc != 2) {
8 | printf("Learn how to use an editor you noob!\n");
9 | exit(1);
10 | }
11 |
12 | char *filename = argv[1];
13 |
14 | // elim escape key delay
15 | putenv("ESCDELAY=0");
16 |
17 | // ncurses stuff
18 | initscr();
19 | raw();
20 | keypad(stdscr, TRUE);
21 | noecho();
22 | refresh();
23 |
24 | state_t *st = init_state(filename);
25 |
26 | start_listener(st);
27 |
28 | destroy_state(st);
29 | endwin();
30 | return 0;
31 | }
32 |
--------------------------------------------------------------------------------
/src/render.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "buffer.h"
3 | #include "screen.h"
4 | #include "render.h"
5 |
6 | void render_window(window_t *w, size_t padding_front) {
7 | // only want to render dirty windows ;)
8 | if (!w->r->is_dirty) {
9 | return;
10 | }
11 |
12 | werase(w->w);
13 |
14 | // render line number
15 | if (w->line_number > 0) {
16 | size_t num_digits = 0;
17 | size_t line_number = w->line_number;
18 |
19 | while (line_number > 0) {
20 | line_number /= 10;
21 | num_digits ++;
22 | }
23 |
24 | wattron(w->w, A_BOLD);
25 | for (size_t i = 0; i < padding_front - num_digits; i ++) {
26 | wprintw(w->w, "%c", ' ');
27 | }
28 | wprintw(w->w, "%zu ", w->line_number);
29 |
30 | wattroff(w->w, A_BOLD);
31 | }
32 |
33 | echar_t *ch = w->r->head;
34 | while (ch) {
35 | wprintw(w->w, "%c", ch->c);
36 | ch = ch->next;
37 | }
38 |
39 | wprintw(w->w, "\n");
40 | wrefresh(w->w);
41 | w->r->is_dirty = false;
42 | }
43 |
44 | void render_windows(state_t *st) {
45 | screen_t *scr = st->scr;
46 | render_window(scr->status_window, 0);
47 |
48 | for (size_t i = 0; i < scr->num_windows; i ++) {
49 | window_t *w = scr->windows[i];
50 |
51 | // If row points to NULL, display empty row with ~
52 | if (!w->r) {
53 | werase(w->w);
54 | wprintw(w->w, "%c\n", '~');
55 | wrefresh(w->w);
56 | } else {
57 | render_window(w, st->padding_front);
58 | }
59 | }
60 | }
61 |
62 | void render_update(state_t *st) {
63 | render_windows(st);
64 | move(st->cy, st->cx);
65 | refresh();
66 | }
67 |
--------------------------------------------------------------------------------
/src/render.h:
--------------------------------------------------------------------------------
1 | #ifndef RENDER_H
2 | #define RENDER_H
3 |
4 | #include "state.h"
5 |
6 | void render_window(window_t *w, size_t padding_front);
7 |
8 | void render_windows(state_t *st);
9 |
10 | void render_update(state_t *st);
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/src/screen.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "screen.h"
4 |
5 | static window_t **init_windows(size_t term_height) {
6 | // determine number of windows we want to display from terminal height
7 | size_t line_number = term_height - 1;
8 | const size_t height = 1, width = COLS;
9 | window_t **windows = malloc(line_number * sizeof(window_t *));
10 |
11 | for (size_t i = 0; i < line_number; i ++) {
12 | windows[i] = malloc(sizeof(window_t));
13 | windows[i]->w = newwin(height, width, i, 0);
14 | windows[i]->r = NULL;
15 | windows[i]->line_number = 0;
16 | }
17 |
18 | return windows;
19 | }
20 |
21 | static window_t *init_status_window(size_t term_height) {
22 | const size_t height = 1, width = COLS;
23 |
24 | window_t *status_window = malloc(sizeof(window_t));
25 | status_window->w = newwin(height, width, term_height - 1, 0);
26 | status_window->r = NULL;
27 | status_window->line_number = 0;
28 |
29 | return status_window;
30 | }
31 |
32 | static void destroy_window(window_t *w) {
33 | delwin(w->w);
34 | free(w);
35 | }
36 |
37 | static void destroy_windows(window_t **windows) {
38 | size_t line_number = LINES - 1;
39 | for (size_t i = 0; i < line_number; i ++) {
40 | destroy_window(windows[i]);
41 | }
42 |
43 | free(windows);
44 | }
45 |
46 | screen_t *init_screen(size_t term_height) {
47 | screen_t *scr = malloc(sizeof(screen_t));
48 |
49 | scr->num_windows = term_height - 1;
50 |
51 | scr->windows = init_windows(term_height);
52 | scr->status_window = init_status_window(term_height);
53 |
54 | return scr;
55 | }
56 |
57 | void destroy_screen(screen_t *scr) {
58 | destroy_windows(scr->windows);
59 | destroy_window(scr->status_window);
60 | free(scr);
61 | }
62 |
--------------------------------------------------------------------------------
/src/screen.h:
--------------------------------------------------------------------------------
1 | #ifndef SCREEN_H
2 | #define SCREEN_H
3 |
4 | #include
5 |
6 | typedef struct row row_t;
7 |
8 | typedef struct window {
9 | WINDOW *w;
10 | row_t *r;
11 | size_t line_number;
12 | } window_t;
13 |
14 | typedef struct screen {
15 | // total number of non status windows displayed
16 | size_t num_windows;
17 |
18 | // each ncurses window represents a line
19 | window_t **windows;
20 | // display current mode, as well as ex mode commands
21 | window_t *status_window;
22 | } screen_t;
23 |
24 | screen_t *init_screen(size_t term_height);
25 |
26 | void destroy_screen(screen_t *scr);
27 |
28 | #endif
29 |
--------------------------------------------------------------------------------
/src/state.c:
--------------------------------------------------------------------------------
1 | #include "state.h"
2 | #include "buffer.h"
3 | #include
4 |
5 | state_t *init_state(const char *filename) {
6 | state_t *st = malloc(sizeof(state_t));
7 |
8 | st->cx = 0;
9 | st->cy = 0;
10 | st->top_row = 0;
11 |
12 | st->to_refresh = true;
13 |
14 | st->buf = init_buffer(filename);
15 | st->scr = init_screen(LINES);
16 |
17 | // history stack
18 | st->hs = init_command_stack();
19 | // redo stack
20 | st->rs = init_command_stack();
21 |
22 | st->status_row = init_row(NULL);
23 | st->prev_key = '\0';
24 |
25 | update_state(st);
26 |
27 | return st;
28 | }
29 |
30 | void destroy_state(state_t *st) {
31 | destroy_buffer(st->buf);
32 | destroy_screen(st->scr);
33 | destroy_row(st->status_row);
34 | destroy_command_stack(st->hs);
35 | destroy_command_stack(st->rs);
36 | free(st);
37 | }
38 |
39 | // -----------
40 | // Getters
41 | // -----------
42 | static void update_top_row(state_t *st) {
43 | size_t current_row = st->buf->current_row;
44 | size_t num_windows = st->scr->num_windows;
45 |
46 | // when user is scrolling down
47 | if (current_row >= st->top_row + num_windows) {
48 | st->top_row = current_row - num_windows + 1;
49 | st->to_refresh = true;
50 | }
51 |
52 | // when user is scrolling up
53 | if (current_row < st->top_row) {
54 | st->top_row = current_row;
55 | st->to_refresh = true;
56 | }
57 | }
58 |
59 | /*
60 | * compute the space reserved for line number
61 | */
62 | static void update_padding_front(state_t *st) {
63 | size_t max_row = st->buf->num_rows;
64 | size_t num_digits = 0;
65 |
66 | while (max_row > 0) {
67 | max_row /= 10;
68 | num_digits ++;
69 | }
70 |
71 | st->padding_front = num_digits + 1;
72 | }
73 |
74 | /*
75 | * Display current mode on status row
76 | */
77 | static void update_mode_status(state_t *st) {
78 | const char *insert_mode = "-- INSERT --";
79 | const char *normal_mode = "-- NORMAL --";
80 |
81 | const char *text;
82 |
83 | if (st->buf->mode == INSERT_BACK || st->buf->mode == INSERT_FRONT) {
84 | text = insert_mode;
85 | } else if (st->buf->mode == NORMAL) {
86 | text = normal_mode;
87 | } else {
88 | return;
89 | }
90 |
91 | clear_row(st->status_row);
92 | for (size_t i = 0; i < strlen(text); i ++) {
93 | add_char(st->status_row, text[i]);
94 | }
95 | }
96 |
97 | /*
98 | * Cursor position can be computed from:
99 | * buf->current_row, buf->current_char, scr->top_row
100 | */
101 | static void update_cursor_position(state_t *st) {
102 | size_t line_size = st->buf->current->line_size;
103 | size_t current_row = st->buf->current_row;
104 | size_t current_char = st->buf->current_char;
105 | size_t top_row = st->top_row;
106 |
107 | // cuz cx and cy are "computed properties" ;)
108 | st->cx = 0;
109 | st->cy = 0;
110 |
111 | st->cy = current_row - top_row;
112 |
113 | if (line_size == 0) {
114 | st->cx = 0;
115 | } else if (st->cx >= line_size) {
116 | st->cx = line_size - 1;
117 | } else {
118 | st->cx = current_char;
119 | }
120 |
121 | if (st->buf->mode == INSERT_BACK && st->buf->current->line_size != 0) {
122 | st->cx ++;
123 | }
124 |
125 | if (st->buf->mode == EX) {
126 | st->cy = st->scr->num_windows;
127 | st->cx = st->status_row->line_size;
128 | } else {
129 | st->cx = st->cx + st->padding_front + 1;
130 | }
131 | }
132 |
133 | /*
134 | * Determine current rows to be displayed and update windows <-> rows links
135 | * Can be computed from:
136 | * st->cy, buf->current_row, scr->top_window, scr->num_windows
137 | *
138 | * We want to update the display when:
139 | * - scroll up/down (update_top_row() -> true)
140 | * - insert/delete row(s)
141 | * - insert at the bottom which triggers a "scroll"
142 | */
143 | static void update_scr_windows(state_t *st) {
144 | // link status window and its buffer
145 | if (!st->scr->status_window->r) {
146 | st->scr->status_window->r = st->status_row;
147 | }
148 |
149 | size_t current_row = st->buf->current_row;
150 | size_t top_row = st->top_row;
151 | size_t num_windows = st->scr->num_windows;
152 |
153 | window_t **windows = st->scr->windows;
154 |
155 | row_t *r = st->buf->current;
156 |
157 | for (size_t i = top_row; i <= current_row; i ++) {
158 | windows[current_row - i]->r = r;
159 | windows[current_row - i]->line_number = top_row + current_row - i + 1;
160 | r->is_dirty = true;
161 | r = r->prev;
162 | }
163 |
164 | r = st->buf->current->next;
165 |
166 | for (size_t i = current_row + 1; i < top_row + num_windows; i ++) {
167 | if (r) {
168 | windows[i - top_row]->r = r;
169 | windows[i - top_row]->line_number = i + 1;
170 | r->is_dirty = true;
171 | r = r->next;
172 | } else {
173 | windows[i - top_row]->r = NULL;
174 | windows[i - top_row]->line_number = 0;
175 | }
176 | }
177 | }
178 |
179 | void update_state(state_t *st) {
180 | update_mode_status(st);
181 | update_top_row(st);
182 | update_padding_front(st);
183 |
184 | if (st->to_refresh) {
185 | update_scr_windows(st);
186 | st->to_refresh = false;
187 | }
188 |
189 | update_cursor_position(st);
190 | }
191 |
--------------------------------------------------------------------------------
/src/state.h:
--------------------------------------------------------------------------------
1 | #ifndef STATE_H
2 | #define STATE_H
3 |
4 | #define LINE_LENGTH 80
5 |
6 | #include
7 | #include "screen.h"
8 | #include "buffer.h"
9 | #include "commands.h"
10 |
11 | typedef struct state {
12 | buffer_t *buf;
13 | screen_t *scr;
14 |
15 | command_stack_t *hs;
16 | command_stack_t *rs;
17 |
18 | size_t cx, cy;
19 | size_t top_row;
20 |
21 | // reserve some space in front of each line to display line number
22 | // This property depends on the # of digits of buf->num_rows
23 | size_t padding_front;
24 |
25 | // rerender all windows if it is true
26 | bool to_refresh;
27 |
28 | // support two char commands such as 'gg', 'dd'
29 | char prev_key;
30 |
31 | // buffer for the status line
32 | row_t *status_row;
33 | } state_t;
34 |
35 | state_t *init_state(const char *filename);
36 |
37 | void destroy_state(state_t *st);
38 |
39 | void update_state(state_t *st);
40 |
41 | #endif
42 |
--------------------------------------------------------------------------------
/tests/Makefile:
--------------------------------------------------------------------------------
1 | CC=gcc
2 | FLAGS=--std=gnu11 -lncurses
3 |
4 | buffer:
5 | @$(CC) $(FLAGS) ../src/buffer.c ./buffer-test.c -o test
6 | valgrind --leak-check=yes ./test
7 | @rm test
8 |
9 | commands:
10 | @$(CC) $(FLAGS) ../src/commands.c ./commands-test.c -o test
11 | valgrind --leak-check=yes ./test
12 | @rm test
13 |
14 | state:
15 | @$(CC) $(FLAGS) ../src/buffer.c ../src/screen.c ../src/state.c ./state-test.c -o test
16 | valgrind --leak-check=yes ./test
17 | @rm test
18 |
--------------------------------------------------------------------------------
/tests/buffer-test.c:
--------------------------------------------------------------------------------
1 | /*
2 | * ./tests/file.txt:
3 | *
4 | * --start--
5 | * one
6 | * two
7 | * three
8 | * four
9 | * five
10 | * --end--
11 | *
12 | */
13 | #include
14 | #include
15 | #include
16 |
17 | #include "../src/buffer.h"
18 |
19 | static void print_row(row_t *r) {
20 | echar_t *c = r->head;
21 | while (c) {
22 | printf("%c", c->c);
23 | c = c->next;
24 | }
25 | printf("\n");
26 | }
27 |
28 | static void print_buffer(buffer_t *buf) {
29 | row_t *r = buf->head;
30 | while (r) {
31 | print_row(r);
32 | r = r->next;
33 | }
34 | }
35 |
36 | static int mycmp(row_t *r, const char *str) {
37 | const size_t s = 100;
38 | char tmp[s];
39 | echar_t *c = r->head;
40 |
41 | for (size_t i = 0; c; i++) {
42 | snprintf(tmp + i, s - i, "%c", c->c);
43 | c = c->next;
44 | }
45 |
46 | return strcmp(tmp, str);
47 | }
48 |
49 | static int test_chars(const char *filename) {
50 | buffer_t *buf = init_buffer(filename);
51 |
52 | // start at first line, first char
53 | assert(mycmp(buf->current, "zero") == 0);
54 | assert(buf->current->current->c == 'z');
55 |
56 | prepend_char(buf, 'm');
57 | assert(mycmp(buf->current, "mzero") == 0);
58 | assert(buf->current->current->c == 'z');
59 |
60 | delete_char(buf);
61 | assert(mycmp(buf->current, "mero") == 0);
62 | assert(buf->current->current->c == 'e');
63 |
64 | append_char(buf, 'x');
65 | assert(mycmp(buf->current, "mexro") == 0);
66 | assert(buf->current->current->c == 'x');
67 |
68 | // delete x
69 | delete_char(buf);
70 | assert(buf->current->current->c == 'r');
71 | // delete r
72 | delete_char(buf);
73 | assert(buf->current->current->c == 'o');
74 | // delete o
75 | delete_char(buf);
76 | assert(buf->current->current->c == 'e');
77 |
78 | delete_char(buf);
79 | delete_char(buf);
80 | append_char(buf, 'x');
81 | assert(mycmp(buf->current, "x") == 0);
82 | assert(buf->current->current->c == 'x');
83 |
84 | destroy_buffer(buf);
85 |
86 | return 0;
87 | }
88 |
89 | static int test_rows(const char *filename) {
90 | buffer_t *buf = init_buffer(filename);
91 |
92 | assert(mycmp(buf->current, "zero") == 0);
93 | assert(buf->num_rows == 6);
94 |
95 | // prepend at head
96 | prepend_row(buf, "mr. goose");
97 | assert(mycmp(buf->current->next, "zero") == 0);
98 | assert(mycmp(buf->current, "mr. goose") == 0);
99 | assert(mycmp(buf->head, "mr. goose") == 0);
100 | assert(buf->num_rows == 7);
101 |
102 | // manually move current to bottom
103 | buf->current = buf->last;
104 |
105 | // append at last
106 | append_row(buf, "hehe");
107 | // make sure it is inserted after the old current
108 | assert(mycmp(buf->current->prev, "five") == 0);
109 | assert(mycmp(buf->current, "hehe") == 0);
110 | assert(mycmp(buf->last, "hehe") == 0);
111 | assert(buf->num_rows == 8);
112 |
113 | // delete last row (since current is last)
114 | delete_row(buf);
115 | // if next is null, we set current to prev
116 | assert(mycmp(buf->current, "five") == 0);
117 | assert(buf->num_rows == 7);
118 |
119 | buf->current = buf->head;
120 | // delete first row
121 | delete_row(buf);
122 | // set current to next
123 | assert(mycmp(buf->current, "zero") == 0);
124 | assert(buf->num_rows == 6);
125 |
126 | destroy_buffer(buf);
127 |
128 | return 0;
129 | }
130 |
131 | int test_move_rows(const char *filename) {
132 | buffer_t *buf = init_buffer(filename);
133 |
134 | assert(mycmp(buf->current, "zero") == 0);
135 | assert(buf->num_rows == 6);
136 |
137 | // if reach top do nothing
138 | move_current(buf, UP);
139 | assert(mycmp(buf->current, "zero") == 0);
140 | assert(buf->current_row == 0);
141 |
142 | move_current(buf, DOWN);
143 | assert(mycmp(buf->current, "one") == 0);
144 | assert(buf->current_row == 1);
145 |
146 | move_current(buf, DOWN);
147 | move_current(buf, DOWN);
148 | move_current(buf, DOWN);
149 | assert(mycmp(buf->current, "four") == 0);
150 | assert(buf->current_row == 4);
151 | move_current(buf, DOWN);
152 | assert(mycmp(buf->current, "five") == 0);
153 | assert(buf->current_row == 5);
154 | move_current(buf, DOWN);
155 | assert(mycmp(buf->current, "five") == 0);
156 | assert(buf->current_row == 5);
157 |
158 | destroy_buffer(buf);
159 | return 0;
160 | }
161 |
162 | int test_move_chars(const char *filename) {
163 | buffer_t *buf = init_buffer(filename);
164 |
165 | assert(buf->current->current->c == 'z');
166 | assert(buf->current_char == 0);
167 |
168 | move_current(buf, LEFT);
169 | assert(buf->current->current->c == 'z');
170 | assert(buf->current_char == 0);
171 |
172 | move_current(buf, RIGHT);
173 | assert(buf->current->current->c == 'e');
174 | assert(buf->current_char == 1);
175 | move_current(buf, RIGHT);
176 | move_current(buf, RIGHT);
177 | assert(buf->current->current->c == 'o');
178 | assert(buf->current_char == 3);
179 | move_current(buf, RIGHT);
180 | assert(buf->current->current->c == 'o');
181 | assert(buf->current_char == 3);
182 |
183 | move_current(buf, LEFT);
184 | assert(buf->current_char == 2);
185 |
186 | destroy_buffer(buf);
187 | return 0;
188 | }
189 |
190 | int main(void) {
191 | const char *filename = "./file.txt";
192 |
193 | test_chars(filename);
194 | test_rows(filename);
195 |
196 | test_move_rows(filename);
197 | test_move_chars(filename);
198 |
199 | return 0;
200 | }
201 |
--------------------------------------------------------------------------------
/tests/commands-test.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "../src/commands.h"
6 |
7 | static void test_command_stack(void) {
8 | command_stack_t *cs = init_command_stack();
9 | COMMAND_PAYLOAD p;
10 |
11 | COMMAND_TYPE t1 = HANDLE_APPEND_ROW;
12 | COMMAND_TYPE t2 = HANDLE_DELETE_CHAR;
13 | COMMAND_TYPE t3 = HANDLE_DELETE_ROW;
14 |
15 | command_t *c1 = init_command(t1, p);
16 | command_t *c2 = init_command(t2, p);
17 | command_t *c3 = init_command(t3, p);
18 |
19 | append_command(cs, c1);
20 | assert(cs->top->type == t1);
21 | assert(cs->bottom->type == t1);
22 | assert(c1->prev == NULL);
23 | assert(c1->next == NULL);
24 | assert(c2->prev == NULL);
25 | assert(c2->next == NULL);
26 | assert(c3->prev == NULL);
27 | assert(c3->next == NULL);
28 |
29 | append_command(cs, c2);
30 | assert(cs->top == c2);
31 | assert(cs->bottom == c1);
32 | assert(c1->prev == NULL);
33 | assert(c1->next == c2);
34 | assert(c2->prev == c1);
35 | assert(c2->next == NULL);
36 | assert(c3->prev == NULL);
37 | assert(c3->next == NULL);
38 |
39 | append_command(cs, c3);
40 | assert(cs->top == c3);
41 | assert(cs->bottom == c1);
42 | assert(c1->prev == NULL);
43 | assert(c1->next == c2);
44 | assert(c2->prev == c1);
45 | assert(c2->next == c3);
46 | assert(c3->prev == c2);
47 | assert(c3->next == NULL);
48 |
49 | command_t *tmp = pop_command(cs);
50 | assert(tmp == c3);
51 | assert(cs->top == c2);
52 | assert(cs->bottom == c1);
53 | assert(c1->prev == NULL);
54 | assert(c1->next == c2);
55 | assert(c2->prev == c1);
56 | assert(c2->next == NULL);
57 | assert(c3->prev == NULL);
58 | assert(c3->next == NULL);
59 | free(tmp);
60 |
61 | tmp = pop_command(cs);
62 | assert(tmp == c2);
63 | assert(cs->top == c1);
64 | assert(cs->bottom == c1);
65 | assert(c1->prev == NULL);
66 | assert(c1->next == NULL);
67 | assert(c2->prev == NULL);
68 | assert(c2->next == NULL);
69 | free(tmp);
70 |
71 | tmp = pop_command(cs);
72 | assert(tmp == c1);
73 | assert(cs->top == NULL);
74 | assert(cs->bottom == NULL);
75 | assert(c1->prev == NULL);
76 | assert(c1->next == NULL);
77 | free(tmp);
78 |
79 | destroy_command_stack(cs);
80 | }
81 |
82 | int main(void) {
83 | test_command_stack();
84 |
85 | return 0;
86 | }
87 |
--------------------------------------------------------------------------------
/tests/file.txt:
--------------------------------------------------------------------------------
1 | zero
2 | one
3 | two
4 | three
5 | four
6 | five
7 |
--------------------------------------------------------------------------------
/tests/state-test.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Integration test for state, buffer and screen
3 | */
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "../src/state.h"
9 |
10 | int test_update_cursor(const char *filename) {
11 | // cursor moved to an empty line
12 | state_t *st = init_state(filename);
13 | assert(st->cx == 0 && st->cy == 0);
14 |
15 | update_cursor_position(st);
16 | assert(st->cx == 0 && st->cy == 0);
17 |
18 | destroy_state(st);
19 | return 0;
20 | }
21 |
22 | int test_update_display(const char *filename) {
23 | state_t *st = init_state(filename);
24 | window_t **windows = st->scr->windows;
25 | row_t *head = st->buf->head;
26 |
27 | assert(st->scr->num_windows == (size_t) LINES - 1);
28 |
29 | update_display(st);
30 | for (size_t i = 0; i < st->scr->num_windows; i ++) {
31 | if (head) {
32 | assert(windows[i]->r == head);
33 | } else {
34 | assert(windows[i]->r == NULL);
35 | }
36 | }
37 |
38 | destroy_state(st);
39 | return 0;
40 | }
41 |
42 | int main(void) {
43 | const char *filename = "./file.txt";
44 |
45 | // ncurses stuff
46 | initscr();
47 | raw();
48 | keypad(stdscr, TRUE);
49 | noecho();
50 |
51 | test_update_cursor(filename);
52 | test_update_display(filename);
53 |
54 | endwin();
55 | return 0;
56 | }
57 |
--------------------------------------------------------------------------------