├── .gdbinit ├── .github └── workflows │ └── build.yml ├── .gitignore ├── AUTHORS ├── COPYING ├── CREDITS ├── Makefile ├── README.md ├── include ├── bitops.h ├── common.h ├── config.h ├── cstack.h ├── cstring.h ├── main.h ├── markdown.h ├── parser.h ├── url.h └── viewer.h ├── mdp.1 ├── mdp.cygport ├── mdp.sublime-project ├── sample.md └── src ├── Makefile ├── cstack.c ├── cstring.c ├── main.c ├── markdown.c ├── parser.c ├── url.c └── viewer.c /.gdbinit: -------------------------------------------------------------------------------- 1 | set args -d sample.md 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build CI 3 | 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | compiler: [gcc-12, gcc-13, clang-17, clang-18] 12 | env: 13 | PREFIX: /usr/local/bin 14 | CC: ${{ matrix.compiler }} 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Install Dependencies 19 | run: | 20 | sudo apt-get update -qq 21 | sudo apt-get install -y \ 22 | build-essential \ 23 | clang-17 \ 24 | clang-18 \ 25 | gcc-12 \ 26 | gcc-13 27 | - name: CC ver 28 | run: ${CC} -v 29 | - name: make 30 | run: make 31 | - name: make install 32 | run: sudo make install 33 | - name: ldd 34 | run: ldd $PREFIX/mdp 35 | - name: version 36 | run: $PREFIX/mdp -v 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # temporary compile results 2 | *.o 3 | 4 | # Binary excutions 5 | mdp 6 | mdp.exe 7 | 8 | # Other temporary files 9 | .DS_Store 10 | .idea 11 | *~ 12 | *.swp 13 | *.sublime-workspace 14 | *.out 15 | tags 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Author: visit1985 2 | 3 | Contributors: 4 | Pyrohh 5 | FreeBirdLjj 6 | ungureanuvladvictor 7 | lukaslueg 8 | mattn 9 | fredjean 10 | hspak 11 | dopsi 12 | alx741 13 | mnalt 14 | guobin2312 15 | lukebond 16 | namhyung 17 | ethanherbertson 18 | MrPicklePinosaur 19 | 20 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 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 | 635 | Copyright (C) 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 | Copyright (C) 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 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | Credits go to 2 | 3 | Joshua M. Clulow for his terminal-based presentation tool in node.js 4 | https://github.com/jclulow/vtmc 5 | 6 | David Parsons for his inspiring Markdown implementation in C 7 | https://github.com/Orc/discount 8 | 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # Copyright (C) 2018 Michael Goehler 4 | # 5 | # This file is part of mdp. 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | 21 | UNAME_S := $(shell uname -s 2>/dev/null || echo not) 22 | 23 | SOURCES = $(sort $(wildcard src/*.c)) 24 | OBJECTS = $(SOURCES:.c=.o) 25 | TARGET = mdp 26 | DESTDIR = 27 | PREFIX ?= /usr/local 28 | BINDIR ?= ${PREFIX}/bin 29 | MANDIR ?= ${PREFIX}/share/man 30 | 31 | CURSES = ncursesw 32 | LDFLAGS ?= -s 33 | CFLAGS ?= -O3 34 | CFLAGS += -Wall 35 | 36 | ifeq (Windows_NT,$(OS)) 37 | ifeq (,$(findstring CYGWIN,$(UNAME_S))) 38 | CURSES := pdcurses 39 | endif 40 | endif 41 | 42 | ifeq ($(UNAME_S),Darwin) 43 | CURSES := ncurses 44 | LDFLAGS := 45 | endif 46 | 47 | ifeq ($(DEBUG),1) 48 | CFLAGS := -O0 -Wall -g 49 | LDFLAGS := 50 | endif 51 | 52 | LDLIBS = -l$(CURSES) 53 | 54 | all: $(TARGET) 55 | 56 | $(TARGET): src 57 | $(CC) $(OBJECTS) $(LDLIBS) $(CFLAGS) $(LDFLAGS) -o $(TARGET) 58 | 59 | src: 60 | $(MAKE) $(MFLAGS) -C src 61 | 62 | clean: 63 | $(MAKE) -C src clean 64 | $(RM) $(TARGET) 65 | 66 | install: 67 | install -d $(DESTDIR)$(BINDIR) 68 | install -m 755 $(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET) 69 | install -d $(DESTDIR)$(MANDIR)/man1 70 | install -m 644 mdp.1 $(DESTDIR)$(MANDIR)/man1/$(TARGET).1 71 | 72 | uninstall: 73 | $(RM) $(DESTDIR)$(BINDIR)/$(TARGET) 74 | $(RM) $(DESTDIR)$(MANDIR)/man1/$(TARGET).1 75 | 76 | .PHONY: all clean install src uninstall 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## mdp - A command-line based markdown presentation tool. 3 | 4 | ![image](https://cloud.githubusercontent.com/assets/2237222/5810237/797c494c-a043-11e4-9dbd-959cab4055fa.gif) 5 | 6 | --- 7 | 8 | ***How to get started:*** 9 | 10 | mdp needs the ncursesw headers to compile. 11 | So make sure you have them installed: 12 | 13 | - on Raspbian (Raspberry Pi) you need `libncurses5-dev` and `libncursesw5-dev` 14 | - on Fedora you need `ncurses-devel` and `ncurses-c++-libs` 15 | 16 | Now download and install mdp: 17 | 18 | $ git clone https://github.com/visit1985/mdp.git 19 | $ cd mdp 20 | $ make 21 | $ make install 22 | $ mdp sample.md 23 | 24 | - On Arch Linux, you can use the existing [package](https://www.archlinux.org/packages/extra/x86_64/mdp/). 25 | - on Cygwin you can use the existing [package](https://cygwin.com/cgi-bin2/package-grep.cgi?grep=mdp.exe) from the setup program. 26 | - On Debian, you can use the existing [DEB package](https://tracker.debian.org/pkg/mdp-src), or run `apt-get install mdp`. 27 | - On FreeBSD, you can use the port [misc/mdp](http://www.freshports.org/misc/mdp). 28 | - On MacOS, use either the [Homebrew Formula](http://brewformulas.org/Mdp) by running `brew install mdp` or install with [MacPorts](https://ports.macports.org/port/mdp/) with `sudo port install mdp`. 29 | - On Slackware, grab the SlackBuild here: (http://slackbuilds.org/apps/mdp/), or run `sbopkg -i mdp`. 30 | - On Ubuntu, you can use the existing [DEB package](https://launchpad.net/ubuntu/+source/mdp-src), or run `apt-get install mdp`. 31 | 32 | Most terminals support 256 colors only if the TERM variable is 33 | set correctly. To enjoy mdp's color fading feature: 34 | 35 | $ export TERM=xterm-256color 36 | 37 | --- 38 | 39 | ***How to use it:*** 40 | 41 | Horizontal rulers are used as slide separator. 42 | 43 | Supports basic markdown formatting: 44 | 45 | - line wide markup 46 | - headlines 47 | - code 48 | - quotes 49 | - unordered list 50 | 51 | - in-line markup 52 | - bold text 53 | - underlined text 54 | - code 55 | 56 | Supports headers prefixed by @ symbol. 57 | 58 | - first two header lines are displayed as title and author 59 | in top and bottom bar 60 | 61 | Review sample.md for more details. 62 | 63 | --- 64 | 65 | ***Default controls:*** 66 | 67 | - h, j, k, l, Arrow keys, 68 | Space, Enter, Backspace, 69 | Page Up, Page Down - next/previous slide 70 | - Home, g - go to first slide 71 | - End, G - go to last slide 72 | - 1-9 - go to slide n 73 | - r - reload input file 74 | - q - exit 75 | 76 | --- 77 | 78 | ***Configuration***: 79 | 80 | A `config.h` configuration file is available in `include/`, change the settings you want and recompile. 81 | Colors, keybindings and list types are configurable as of now. Note that configuring colors only works in 8 color mode. 82 | 83 | --- 84 | 85 | ***How to debug it:*** 86 | 87 | To make a debug version of `mdp`, just type: 88 | 89 | $ make DEBUG=1 90 | 91 | ***Convert to PDF:*** 92 | 93 | - Install `md2pdf` by obtaining the [release](https://github.com/mandolyte/mdtopdf/releases) for your arch and OS or, if 94 | you have `go` installed, invoke: 95 | 96 | ```sh 97 | $ go install github.com/mandolyte/mdtopdf/cmd/md2pdf@latest 98 | ``` 99 | - If you require syntax highlighting, download the [gohighlight lexers](https://github.com/jessp01/gohighlight/tree/master/syntax_files) 100 | 101 | `md2pdf` supports all major `mdp` features and accepts local files, remote HTTP(s) URL and `STDIN` inputs. 102 | The below command will convert an `mdp` compatible markdown file to a PDF with a dark theme, 103 | syntax highlighting (you'll need to provide the language hint, of course), page/slide separation and a footer: 104 | 105 | ```sh 106 | md2pdf -i https://github.com/jessp01/crash-course-in/raw/main/courses/apt_dpkg_deb/apt_dpkg_deb.md \ 107 | -o apt_dpkg_deb.pdf \ 108 | -s ~/.config/zaje/syntax_files \ 109 | --theme dark \ 110 | --new-page-on-hr \ 111 | --with-footer \ 112 | --author "Jesse Portnoy " \ 113 | --title "A crash course on handling deb packages" 114 | ``` 115 | 116 | Since `markdown` does not support the centering escape sequences (i.e: `->` and `<-`), you will want to remove these before converting, for example: 117 | 118 | ```sh 119 | $ sed 's@^->\s*\(#.*\)\s*<-@\1@g' sample.md | ~/go/bin/md2pdf -o mdp.pdf \ 120 | --theme dark --new-page-on-hr 121 | ``` 122 | 123 | -------------------------------------------------------------------------------- /include/bitops.h: -------------------------------------------------------------------------------- 1 | #if !defined( BITOPS_H ) 2 | #define BITOPS_H 3 | 4 | /* 5 | * Macros to do bit operations on integer variables. 6 | * Copyright (C) 2018 Michael Goehler 7 | * 8 | * This file is part of mdp. 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | * 23 | * 24 | * macro: SET_BIT to set bit at pos of var to 1 25 | * macro: CLEAR_BIT to set bit at pos of var to 0 26 | * macro: TOGGLE_BIT to toggle bit at pos of var from 1 to 0 and vice versa 27 | * macro: CHECK_BIT returns > 0 if bit at pos of var is set to 1 28 | * 29 | * Example: 30 | * int i = 0; 31 | * i = SET_BIT(i, 2); 32 | * if(CHECK_BIT(i, 2)) 33 | * printf("bit 2 is set\n"); 34 | * printf("value of i is %d\n", i); 35 | * 36 | */ 37 | 38 | #define SET_BIT(var, pos) ((var) |= (1<<(pos))) 39 | #define CLEAR_BIT(var, pos) ((var) &= (~(1<<(pos)))) 40 | #define TOGGLE_BIT(var, pos) ((var) ^= (1<<(pos))) 41 | #define CHECK_BIT(var, pos) ((var) & (1<<(pos))) 42 | 43 | #endif // !defined( BITOPS_H ) 44 | -------------------------------------------------------------------------------- /include/common.h: -------------------------------------------------------------------------------- 1 | #if !defined( COMMON_H ) 2 | #define COMMON_H 3 | 4 | /* 5 | * Common macros. 6 | * Copyright (C) 2018 Michael Goehler 7 | * 8 | * This file is part of mdp. 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | * 23 | * 24 | * type: bool variable stores only true or false. 25 | * 26 | * macro: MAX returns the higher one of two input variables 27 | * macro: MIN returns the lower one of two input variables 28 | * 29 | */ 30 | 31 | #if defined( __STDC__ ) // for standard C compiler 32 | #if __STDC_VERSION__ >= 199901L // for C99 and later 33 | #include 34 | #else // __STDC_VERSION__ >= 199901L 35 | #if !defined( bool ) 36 | typedef enum { 37 | false = 0, 38 | true = 1 39 | } bool; 40 | #endif // !defined( bool ) 41 | #endif // __STDC_VERSION__ >= 199901L 42 | #else // defined( __STDC__ ) 43 | #define bool int 44 | #define true 1 45 | #define false 0 46 | #endif // defined( __STDC__ ) 47 | 48 | #define MAX(a, b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b? _a : _b; }) 49 | #define MIN(a, b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b? _a : _b; }) 50 | 51 | #endif // !defined( COMMON_H ) 52 | -------------------------------------------------------------------------------- /include/config.h: -------------------------------------------------------------------------------- 1 | #if !defined( CONFIG_H ) 2 | #define CONFIG_H 3 | 4 | /* 5 | * User configuration file 6 | * Copyright (C) 2018 Michael Goehler 7 | * 8 | * This file is part of mdp. 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | * 23 | */ 24 | 25 | // unordered list characters 26 | // 27 | // you can also override via env vars: 28 | // export MDP_LIST_OPEN1=" " MDP_LIST_OPEN2=" " MDP_LIST_OPEN3=" " 29 | // export MDP_LIST_HEAD1=" ■ " MDP_LIST_HEAD2=" ● " MDP_LIST_HEAD3=" ▫ " 30 | // or export MDP_LIST_OPEN to override all MDP_LIST_OPENx variables 31 | // and similarly for MDP_LIST_HEAD 32 | static const char *list_open1 = " | "; 33 | static const char *list_open2 = " | "; 34 | static const char *list_open3 = " | "; 35 | static const char *list_head1 = " +- "; 36 | static const char *list_head2 = " +- "; 37 | static const char *list_head3 = " +- "; 38 | 39 | #define FADE_DELAY 15000 // micro seconds 40 | #define GOTO_SLIDE_DELAY 5 // tenths of seconds 41 | 42 | // colors - you can only set in 8-bit color mode 43 | // 44 | /* Use the ncurses defined colors, here's a list of them: 45 | * COLOR_BLACK 46 | * COLOR_RED 47 | * COLOR_GREEN 48 | * COLOR_YELLOW 49 | * COLOR_BLUE 50 | * COLOR_MAGENTA 51 | * COLOR_CYAN 52 | * COLOR_WHITE 53 | */ 54 | #define FG_COLOR COLOR_WHITE 55 | #define BG_COLOR COLOR_BLACK 56 | #define TITLE_COLOR COLOR_YELLOW 57 | #define HEADER_COLOR COLOR_BLUE 58 | #define BOLD_COLOR COLOR_RED 59 | 60 | // color ramp for fading from black to color 61 | static short white_ramp[24] = { 16, 232, 233, 234, 235, 236, 62 | 237, 238, 239, 240, 241, 242, 63 | 244, 245, 246, 247, 248, 249, 64 | 250, 251, 252, 253, 254, 255 }; 65 | 66 | static short blue_ramp[24] = { 16, 17, 17, 18, 18, 19, 67 | 19, 20, 20, 21, 27, 33, 68 | 32, 39, 38, 45, 44, 44, 69 | 81, 81, 51, 51, 123, 123 }; 70 | 71 | static short red_ramp[24] = { 16, 52, 52, 53, 53, 89, 72 | 89, 90, 90, 126, 127, 127, 73 | 163, 163, 164, 164, 200, 200, 74 | 201, 201, 207, 207, 213, 213 }; 75 | 76 | // color ramp for fading from white to color 77 | static short white_ramp_invert[24] = { 15, 255, 254, 254, 252, 251, 78 | 250, 249, 248, 247, 246, 245, 79 | 243, 242, 241, 240, 239, 238, 80 | 237, 236, 235, 234, 233, 232 }; 81 | 82 | static short blue_ramp_invert[24] = { 15, 231, 231, 195, 195, 159, 83 | 159, 123, 123, 87, 51, 44, 84 | 45, 38, 39, 32, 33, 33, 85 | 26, 26, 27, 27, 21, 21 }; 86 | 87 | static short red_ramp_invert[24] = { 15, 231, 231, 224, 224, 225, 88 | 225, 218, 218, 219, 212, 213, 89 | 206, 207, 201, 200, 199, 199, 90 | 198, 198, 197, 197, 196, 196 }; 91 | 92 | // keybindings 93 | static const int prev_slide_binding[] = { 94 | KEY_UP, 95 | KEY_LEFT, 96 | KEY_PPAGE, 97 | 8, // BACKSPACE (ascii) 98 | 127, // BACKSPACE (xterm) 99 | 263, // BACKSPACE (getty) 100 | 'h', 101 | 'k', 102 | 0 103 | }; 104 | static const int next_slide_binding[] = { 105 | KEY_DOWN, 106 | KEY_RIGHT, 107 | KEY_NPAGE, 108 | '\n', // ENTER 109 | ' ', // SPACE 110 | 'j', 111 | 'l', 112 | 0 113 | }; 114 | static const int first_slide_binding[] = { 115 | 'g', 116 | KEY_HOME, 117 | 0 118 | }; 119 | static const int last_slide_binding[] = { 120 | 'G', 121 | KEY_END, 122 | 0 123 | }; 124 | static const int reload_binding[] = { 125 | 'r', 126 | 0 127 | }; 128 | static const int quit_binding[] = { 129 | 'q', 130 | 0 131 | }; 132 | 133 | #endif // !defined( CONFIG_H ) 134 | -------------------------------------------------------------------------------- /include/cstack.h: -------------------------------------------------------------------------------- 1 | #if !defined( CSTACK_H ) 2 | #define CSTACK_H 3 | 4 | /* 5 | * An implementation of a char stack in heap memory. 6 | * Copyright (C) 2018 Michael Goehler 7 | * 8 | * This file is part of mdp. 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | * 23 | * 24 | * struct: cstack_t which defines char stack type in heap memory 25 | * 26 | * function: cstack_init to initialize struct of type cstack_t 27 | * function: cstack_t->push to add one char on top of the stack 28 | * function: cstack_t->pop to remove the top char from the stack 29 | * function: cstack_t->top to test if the top char is a given char 30 | * function: cstack_t->empty to test if the stack is empty 31 | * function: cstack_t->delete to free the allocated memory 32 | * 33 | * Example: 34 | * cstack_t *p = cstack_init(); 35 | * (p->push)(p, 'X'); 36 | * printf("%c\n", (p->pop)(p)); 37 | * (p->delete)(p); 38 | * 39 | */ 40 | 41 | #include "common.h" 42 | 43 | typedef struct _cstack_t { 44 | wchar_t *content; 45 | size_t alloc; 46 | size_t size; 47 | int head; 48 | void (*push)(struct _cstack_t *self, wchar_t c); 49 | wchar_t (*pop)(struct _cstack_t *self); 50 | bool (*top)(struct _cstack_t *self, wchar_t c); 51 | bool (*empty)(struct _cstack_t *self); 52 | void (*delete)(struct _cstack_t *self); 53 | } cstack_t; 54 | 55 | cstack_t *cstack_init(); 56 | void cstack_push(cstack_t *self, wchar_t c); 57 | wchar_t cstack_pop(cstack_t *self); 58 | bool cstack_top(cstack_t *self, wchar_t c); 59 | bool cstack_empty(cstack_t *self); 60 | void cstack_delete(cstack_t *self); 61 | 62 | #endif // !defined( CSTACK_H ) 63 | -------------------------------------------------------------------------------- /include/cstring.h: -------------------------------------------------------------------------------- 1 | #if !defined( CSTRING_H ) 2 | #define CSTRING_H 3 | 4 | /* 5 | * An implementation of expandable c strings in heap memory. 6 | * Copyright (C) 2018 Michael Goehler 7 | * 8 | * This file is part of mdp. 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | * 23 | * 24 | * struct: cstring_t which defines a expandable c string type in heap memory 25 | * 26 | * function: cstring_init to initialize struct of type cstring_t 27 | * function: cstring_t->expand to add one character to the struct 28 | * function: cstring_t->expand_arr to add a string to the struct 29 | * function: cstring_t->strip to remove a substring 30 | * function: cstring_t->reset to clear and reuse the struct 31 | * function: cstring_t->delete to free the allocated memory 32 | * 33 | * Example: 34 | * cstring_t *p = cstring_init(); 35 | * (p->expand)(p, 'X'); 36 | * (p->delete)(p); 37 | * 38 | */ 39 | 40 | // The amount of memory allocated from heap when string expansion hits the 41 | // allocated memory limit 42 | #define REALLOC_ADD 10 43 | 44 | typedef struct _cstring_t { 45 | wchar_t *value; 46 | size_t size; 47 | size_t alloc; 48 | void (*expand)(struct _cstring_t *self, wchar_t x); 49 | void (*expand_arr)(struct _cstring_t *self, wchar_t *x); 50 | void (*strip)(struct _cstring_t *self, int pos, int len); 51 | void (*reset)(struct _cstring_t *self); 52 | void (*delete)(struct _cstring_t *self); 53 | } cstring_t; 54 | 55 | cstring_t *cstring_init(); 56 | void cstring_expand(cstring_t *self, wchar_t x); 57 | void cstring_expand_arr(cstring_t *self, wchar_t *x); 58 | void cstring_strip(cstring_t *self, int pos, int len); 59 | void cstring_reset(cstring_t *self); 60 | void cstring_delete(cstring_t *self); 61 | 62 | #endif // !defined( CSTRING_H ) 63 | -------------------------------------------------------------------------------- /include/main.h: -------------------------------------------------------------------------------- 1 | #if !defined( MAIN_H ) 2 | #define MAIN_H 3 | 4 | /* 5 | * mdp -- A command-line based markdown presentation tool. 6 | * Copyright (C) 2018 Michael Goehler 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | #include "parser.h" 24 | #include "viewer.h" 25 | 26 | #define MDP_VER_MAJOR 1 27 | #define MDP_VER_MINOR 0 28 | #define MDP_VER_REVISION 15 29 | 30 | #endif // !defined( MAIN_H ) 31 | -------------------------------------------------------------------------------- /include/markdown.h: -------------------------------------------------------------------------------- 1 | #if !defined( MARKDOWN_H ) 2 | #define MARKDOWN_H 3 | 4 | /* 5 | * An implementation of markdown objects. 6 | * Copyright (C) 2018 Michael Goehler 7 | * 8 | * This file is part of mdp. 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | * 23 | * 24 | * enum: line_bitmask which enumerates markdown formatting bits 25 | * 26 | * struct: deck_t the root object representing a deck of slides 27 | * struct: slide_t a linked list element of type slide contained in a deck 28 | * struct: line_t a linked list element of type line contained in a slide 29 | * 30 | * function: new_deck to initialize a new deck 31 | * function: new_slide to initialize a new linked list of type slide 32 | * function: next_slide to extend a linked list of type slide by one element 33 | * function: new_line to initialize a new linked list of type line 34 | * function: next_line to extend a linked list of type line by one element 35 | * function: free_line to free a line elements memory 36 | * function: free_deck to free a deck's memory 37 | * 38 | */ 39 | 40 | #include "cstring.h" 41 | #include "bitops.h" 42 | 43 | enum line_bitmask { 44 | IS_H1, 45 | IS_H1_ATX, 46 | IS_H2, 47 | IS_H2_ATX, 48 | IS_QUOTE, 49 | IS_CODE, 50 | IS_TILDE_CODE, 51 | IS_GFM_CODE, 52 | IS_HR, 53 | IS_UNORDERED_LIST_1, 54 | IS_UNORDERED_LIST_2, 55 | IS_UNORDERED_LIST_3, 56 | IS_UNORDERED_LIST_EXT, 57 | IS_CENTER, 58 | IS_STOP, 59 | IS_EMPTY 60 | }; 61 | 62 | typedef struct _line_t { 63 | cstring_t *text; 64 | struct _line_t *prev; 65 | struct _line_t *next; 66 | int bits; 67 | int length; 68 | int offset; 69 | } line_t; 70 | 71 | typedef struct _slide_t { 72 | line_t *line; 73 | struct _slide_t *prev; 74 | struct _slide_t *next; 75 | int lines; 76 | int stop; 77 | int lines_consumed; 78 | } slide_t; 79 | 80 | typedef struct _deck_t { 81 | line_t *header; 82 | slide_t *slide; 83 | int slides; 84 | int headers; 85 | } deck_t; 86 | 87 | line_t *new_line(); 88 | line_t *next_line(line_t *prev); 89 | slide_t *new_slide(); 90 | slide_t *next_slide(slide_t *prev); 91 | deck_t *new_deck(); 92 | void free_line(line_t *l); 93 | void free_deck(deck_t *); 94 | 95 | #endif // !defined( MARKDOWN_H ) 96 | -------------------------------------------------------------------------------- /include/parser.h: -------------------------------------------------------------------------------- 1 | #if !defined( PARSER_H ) 2 | #define PARSER_H 3 | 4 | /* 5 | * Functions necessary to parse a file and transform its content into 6 | * a deck of slides containing lines. All based on markdown formating 7 | * rules. 8 | * Copyright (C) 2018 Michael Goehler 9 | * 10 | * This file is part of mdp. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | * 26 | * function: markdown_load is the main function which reads a file handle, 27 | * and initializes deck, slides and lines 28 | * function: markdown_analyse which is used to identify line wide formatting 29 | * rules in given line 30 | * function: markdown_debug to print a report of the generated data structure 31 | * function: adjust_line_length to calculate line length excluding markup 32 | * function: is_utf8 detects multi-byte char 33 | * function: length_utf8 calculates the amount of bytes used for a multi-byte 34 | * char 35 | * function: next_nonblank, next_blank, next_word to calculate string offset's 36 | * 37 | */ 38 | 39 | #include "common.h" 40 | #include "markdown.h" 41 | #include "cstack.h" 42 | 43 | #if defined( CYGWIN ) 44 | #undef WEOF 45 | #define WEOF (0xffff) 46 | #endif // defined( CYGWIN ) 47 | 48 | #define EXPAND_TABS 4 49 | #define CODE_INDENT 4 50 | #define UNORDERED_LIST_MAX_LEVEL 3 51 | 52 | deck_t *markdown_load(FILE *input, int noexpand); 53 | int markdown_analyse(cstring_t *text, int prev); 54 | void markdown_debug(deck_t *deck, int debug); 55 | void expand_character_entities(line_t *line); 56 | void adjust_line_length(line_t *line); 57 | int next_nonblank(cstring_t *text, int i); 58 | int prev_blank(cstring_t *text, int i); 59 | int next_blank(cstring_t *text, int i); 60 | int next_word(cstring_t *text, int i); 61 | int next_nontilde(cstring_t *text, int i); 62 | int next_nonbacktick(cstring_t *text, int i); 63 | 64 | #endif // !defined( PARSER_H ) 65 | -------------------------------------------------------------------------------- /include/url.h: -------------------------------------------------------------------------------- 1 | #if !defined( URL_H ) 2 | #define URL_H 3 | 4 | /* 5 | * An object to store all urls of a slide. 6 | * Copyright (C) 2018 Michael Goehler 7 | * 8 | * This file is part of mdp. 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | * 23 | * function: url_init to initialize a new url object 24 | */ 25 | 26 | typedef struct _url_t { 27 | wchar_t *link_name; 28 | wchar_t *target; 29 | int x; 30 | int y; 31 | struct _url_t *next; 32 | } url_t; 33 | 34 | void url_init(void); 35 | int url_add(const wchar_t *link_name, int link_name_length, const wchar_t *target, int target_length, int x, int y); 36 | wchar_t* url_get_target(int index); 37 | wchar_t* url_get_name(int index); 38 | int url_get_amount(void); 39 | void url_purge(void); 40 | void url_dump(void); 41 | int url_count_inline(const wchar_t *line); 42 | int url_len_inline(const wchar_t *value); 43 | 44 | #endif // !defined( URL_H ) 45 | -------------------------------------------------------------------------------- /include/viewer.h: -------------------------------------------------------------------------------- 1 | #if !defined( VIEWER_H ) 2 | #define VIEWER_H 3 | 4 | /* 5 | * Functions necessary to display a deck of slides in different color modes 6 | * using ncurses. Only white, red, and blue are supported, as they can be 7 | * faded in 256 color mode. 8 | * Copyright (C) 2018 Michael Goehler 9 | * 10 | * This file is part of mdp. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | * 26 | * function: ncurses_display initializes ncurses, defines colors, calculates 27 | * window geometry and handles key strokes 28 | * function: add_line detects inline markdown formatting and prints line char 29 | * by char 30 | * function: fade_in, fade_out implementing color fading in 256 color mode 31 | * function: int_length to calculate decimal length of slide count 32 | * 33 | */ 34 | 35 | #define _GNU_SOURCE // enable ncurses wchar support 36 | #define _XOPEN_SOURCE_EXTENDED 1 // enable ncurses wchar support 37 | 38 | #if defined( WIN32 ) 39 | #include 40 | #else 41 | #include 42 | #endif 43 | 44 | #include "common.h" 45 | #include "parser.h" 46 | #include "cstack.h" 47 | #include "url.h" 48 | 49 | #define CP_WHITE 1 // 255 50 | #define CP_BLUE 2 // 123 51 | #define CP_RED 3 // 213 52 | #define CP_YELLOW 4 // 208 53 | #define CP_BLACK 5 // CP_WHITE with foreground and background swapped 54 | 55 | int ncurses_display(deck_t *deck, int notrans, int nofade, int invert, int reload, int noreload, int slidenum, int nocodebg); 56 | void add_line(WINDOW *window, int y, int x, line_t *line, int max_cols, int colors, int nocodebg); 57 | void inline_display(WINDOW *window, const wchar_t *c, const int colors, int nocodebg); 58 | void fade_out(WINDOW *window, int trans, int colors, int invert); 59 | void fade_in(WINDOW *window, int trans, int colors, int invert); 60 | int int_length (int val); 61 | int get_slide_number(char init); 62 | void setup_list_strings(void); 63 | bool evaluate_binding(const int bindings[], char c); 64 | 65 | #endif // !defined( VIEWER_H ) 66 | -------------------------------------------------------------------------------- /mdp.1: -------------------------------------------------------------------------------- 1 | .\" This is the groff documentation source for MDP 2 | .\" 3 | .\" Preview with: groff -man -Tascii mdp.1 4 | .\" or: man -l mdp.1 5 | .\" 6 | . 7 | . 8 | .TH MDP 1 "2016-04-02" "User Commands" 9 | .SH NAME 10 | mdp \- A command-line based 11 | markdown presentation tool 12 | .SH SYNOPSIS 13 | .B mdp 14 | .RI [ OPTION ].\|.\|.\| 15 | .RI [ FILE ] 16 | . 17 | .SH DESCRIPTION 18 | .B mdp 19 | is a command-line program that allows you to make elegant presentations from 20 | .B markdown formatted 21 | .IR FILE s. 22 | .PP 23 | It is as easy as write your presentation content in the text editor of your 24 | preference and launch the presentation from the command-line. 25 | . 26 | .SH OPTIONS 27 | .SS "Input Control" 28 | .TP 29 | .IR FILE 30 | The input file from which the presentation is read. If no file is specified, 31 | or if the file name is 32 | .BR \- "," 33 | the presentation is read from standard input. 34 | .SS "Output Control" 35 | .TP 36 | .BR \-c ", " \-\^\-nocodebg 37 | Don't change the background color of code blocks. 38 | .TP 39 | .BR \-e ", " \-\^\-expand 40 | Enable character entity expansion (e.g. '>' becomes '>'). 41 | .TP 42 | .BR \-f ", " \-\^\-nofade 43 | Disable color fading in 256 color mode. 44 | .TP 45 | .BR \-i ", " \-\^\-invert 46 | Swap black and white color. 47 | .TP 48 | .BR \-s ", " \-\^\-noslidenum 49 | Do not show slide number at the bottom. 50 | .TP 51 | .BR \-t ", " \-\^\-notrans 52 | Disable transparency in transparent terminal. 53 | .TP 54 | .BR \-x ", " \-\^\-noslidemax 55 | Show slide number, but not total number of slides. 56 | . 57 | .SS "Miscellaneous Options" 58 | .TP 59 | .BR \-d ", " \-\^\-debug 60 | Enable debug messages on STDERR. Add multiple times to increases debug level. 61 | .TP 62 | .BR \-h ", " \-\^\-help 63 | Display usage message and exit. 64 | .TP 65 | .BR \-v ", " \-\^\-version 66 | Display version and license information. 67 | . 68 | .SH ENVIRONMENT VARIABLES 69 | .SS "Output Control" 70 | .TP 71 | .BR MDP_LIST_HEAD[1-3],\ MDP_LIST_OPEN[1-3] 72 | Controls the list characters of unordered lists. 73 | 74 | The default is equivalent to: 75 | .br 76 | MDP_LIST_OPEN1=' | ' 77 | .br 78 | MDP_LIST_OPEN2=' | ' 79 | .br 80 | MDP_LIST_OPEN3=' | ' 81 | .br 82 | MDP_LIST_HEAD1=' +- ' 83 | .br 84 | MDP_LIST_HEAD2=' +- ' 85 | .br 86 | MDP_LIST_HEAD3=' +- ' 87 | . 88 | .SH MARKDOWN FORMATTING 89 | For a complete list of supported markups, refer the sample presentation 90 | (sample.md) provided alongside 91 | .BR mdp ,\| 92 | or online available at 93 | .IR https://github.com/visit1985/mdp . 94 | .SS "Slides" 95 | The input 96 | .IR FILE 97 | is split into multiple slides by horizontal rules. Each consisting of at least 98 | 3 99 | .B \-\-\- 100 | or 101 | .B *** 102 | characters on a single line. 103 | .B This line must be prefixed by an completely empty line. 104 | It can also contain spaces but no other characters. 105 | .PP 106 | If any slide is too large to fit into your current screen, an error message 107 | will be displayed at the moment the presentation is launched. 108 | . 109 | .SS "Line-by-Line Mode" 110 | .SS "Block-by-Block Mode" 111 | A single 112 | .BR "
" ", " "
" " or " "^" 113 | on an otherwise empty line signals 114 | .B mdp 115 | to stop output of the current slide (stop point) and wait for a key-press by 116 | the user. 117 | .PP 118 | This enables the user to display bullet points or list items one by one 119 | (line by line) or block by block. 120 | . 121 | .SS "Headers" 122 | .B mdp 123 | supports header lines in the format of 124 | .BR @ "[DESCRIPTION] " [VALUE] 125 | The first two header lines are displayed as title and author in top and 126 | bottom bar. 127 | .PP 128 | Headers are only recognized at the top of the input 129 | .IR FILE . 130 | . 131 | .SS "Line spanning markup" 132 | Supported are headlines, code blocks, quotes and unordered lists. 133 | . 134 | .SS "In-line markup" 135 | As well as bold text, underlined text and in-line code. 136 | . 137 | .SH COLOR SUPPORT 138 | Most terminals are able to display 256 colors these days. But some of them 139 | enable only 16 colors by default. To enjoy 140 | .BR mdp "'s" 141 | full capabilities, these terminals need to be signaled to enable 256 color 142 | mode. This is usually done by setting the TERM environment variable. 143 | .PP 144 | .BR "export TERM=xterm-256color" 145 | . 146 | .SH KEYBOARD CONTROLS 147 | .TP 148 | .BR "h, j, k, l, Arrow keys, Space, Enter, Backspace, Page Up, Page Down" 149 | Display next/previous slide or continue after a stop point. 150 | .TP 151 | .BR "g, Home" 152 | Jump to first slide. 153 | .TP 154 | .BR "G, End" 155 | Jump to last slide. 156 | .TP 157 | .BR "1..N" 158 | Jump to 159 | .BR N "th" 160 | slide. 161 | .TP 162 | .BR "r" 163 | Reload the input 164 | .IR FILE .\| 165 | This key is disabled if input was read from standard input. 166 | .TP 167 | .BR "q" 168 | Exit 169 | .BR mdp "." 170 | . 171 | .SH CUSTOMIZATION 172 | .B mdp 173 | can be configured by modifying config.h and recompiling. 174 | .SH AUTHOR 175 | Written by Michael Goehler and others, see 176 | .IR https://github.com/visit1985/mdp/blob/master/AUTHORS "." 177 | .SH COPYRIGHT 178 | Copyright (C) 2018 Michael Goehler 179 | .PP 180 | This is free software; see the source for copying conditions. There is NO 181 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 182 | -------------------------------------------------------------------------------- /mdp.cygport: -------------------------------------------------------------------------------- 1 | # package name 2 | NAME="mdp" 3 | VERSION=1.0.9 4 | RELEASE=1 5 | 6 | # .hint generation 7 | CATEGORY="Utils" 8 | SUMMARY="A command-line based markdown presentation tool" 9 | DESCRIPTION="A ncurses-based command-line presentation tool, which makes 10 | it easy to create slides using the popular markdown format." 11 | 12 | # source and patch files 13 | SRC_URI="https://github.com/visit1985/mdp/archive/${VERSION}.tar.gz" 14 | DOCS="sample.md" 15 | 16 | # Build dependencies only 17 | DEPEND="gcc-core libncurses-devel make" 18 | # runtime deps to go in setup.hint 19 | #REQUIRES="libncursesw10" 20 | 21 | # custom src_compile, src_install and src_test 22 | 23 | src_compile() { 24 | cd ${S} 25 | cygmake 26 | } 27 | 28 | src_install() { 29 | cd ${S} 30 | PREFIX=/usr cyginstall 31 | } 32 | 33 | src_test() { :; } 34 | 35 | -------------------------------------------------------------------------------- /mdp.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "settings": 3 | { 4 | "tab_size": 4, 5 | "translate_tabs_to_spaces": true, 6 | "use_tab_stops": false 7 | } 8 | ,"folders": 9 | [ 10 | { 11 | "follow_symlinks": true, 12 | "path": ".", 13 | "file_exclude_patterns": [".*", "mdp", "mdp.sublime-project", "mdp.sublime-workspace"] 14 | } 15 | ] 16 | ,"build_systems": 17 | [ 18 | { 19 | "name": "mdp", 20 | "env": { 21 | "DEBUG": "1" 22 | }, 23 | "cmd": ["make"], 24 | "working_dir": "${project_path:${folder}}", 25 | 26 | "variants": 27 | [ 28 | { 29 | "name": "Clean", 30 | "cmd": ["make", "clean"] 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /sample.md: -------------------------------------------------------------------------------- 1 | %title: mdp - Sample Presentation 2 | %author: visit1985 3 | %date: 2016-02-07 4 | 5 | -> mdp <- 6 | ========= 7 | 8 | -> A command-line based markdown presentation tool. <- 9 | 10 | _Basic controls:_ 11 | 12 | next slide *Enter*, *Space*, *Page Down*, *j*, *l*, 13 | *Down Arrow*, *Right Arrow* 14 | 15 | previous slide *Backspace*, *Page Up*, *h*, *k*, 16 | *Up Arrow*, *Left Arrow* 17 | 18 | quit *q* 19 | reload *r* 20 | slide N *1..9* 21 | first slide *Home*, *g* 22 | last slide *End*, *G* 23 | 24 | ------------------------------------------------- 25 | 26 | -> # Supported markdown formatting <- 27 | 28 | The input file is split into multiple slides by 29 | horizontal rules (hr). A hr consisting of at 30 | least 3 *\** or *-*. It can also contain spaces but 31 | no other characters. 32 | 33 | Each of these represents the start of a new slide. 34 | 35 | \* \* \* 36 | \--- 37 | \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* 38 | \- - - 39 | 40 | ------------------------------------------------- 41 | 42 | -> # Supported markdown formatting <- 43 | 44 | First-level headers can be prefixed by single *#* 45 | or underlined by *===*. 46 | 47 | \# first-level 48 | 49 | becomes 50 | 51 | # first-level 52 | 53 | ------------------------------------------------- 54 | 55 | -> # Supported markdown formatting <- 56 | 57 | Second-level headers can be prefixed by *##* or 58 | underlined by *---*. 59 | 60 | second-level 61 | \------------ 62 | 63 | becomes 64 | 65 | second-level 66 | ------------ 67 | 68 | 69 | ------------------------------------------------- 70 | 71 | -> # Supported markdown formatting's <- 72 | 73 | Inline codes are surrounded with backticks. 74 | 75 | C program starts with \`main()\`. 76 | 77 | becomes 78 | 79 | C program starts with `main()`. 80 | 81 | ------------------------------------------------- 82 | 83 | -> # Supported markdown formatting <- 84 | 85 | Code blocks are automatically detected by 4 spaces 86 | at the beginning of a line. 87 | 88 | Tabs are automatically expanded to 4 spaces while 89 | parsing the input. 90 | 91 | \ int main(int argc, char \*argv[]) { 92 | \ printf("%s\\n", "Hello world!"); 93 | \ } 94 | 95 | becomes 96 | 97 | int main(int argc, char *argv[]) { 98 | printf("%s\n", "Hello world!"); 99 | } 100 | 101 | ------------------------------------------------- 102 | 103 | -> # Supported markdown formatting <- 104 | 105 | You can also use [pandoc](https://pandoc.org/MANUAL.html#fenced-code-blocks)'s fenced code block 106 | extension. Use at least three ~ chars to open and 107 | at least as many or more ~ for closing. 108 | 109 | \~~~ {.numberLines} 110 | \int main(int argc, char \*argv[]) { 111 | \ printf("%s\\n", "Hello world!"); 112 | \} 113 | \~~~~~~~~~~~~~~~~~~ 114 | 115 | becomes 116 | 117 | ~~~ {.numberLines} 118 | int main(int argc, char *argv[]) { 119 | printf("%s\n", "Hello world!"); 120 | } 121 | ~~~~~~~~~~~~~~~~~~ 122 | 123 | Pandoc attributes (like ".numberlines" etc.) 124 | will be ignored 125 | 126 | ------------------------------------------------- 127 | 128 | -> # Supported markdown formatting <- 129 | 130 | You can also use [github](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) flavored markdown's 131 | code block. Use at least three backticks to open 132 | and at least as many or more backticks for closing. 133 | 134 | \``` 135 | \int main(int argc, char \*argv[]) { 136 | \ printf("%s\\n", "Hello world!"); 137 | \} 138 | \``` 139 | 140 | becomes 141 | 142 | ``` 143 | int main(int argc, char *argv[]) { 144 | printf("%s\n", "Hello world!"); 145 | } 146 | ``` 147 | 148 | Language hint will be ignored 149 | 150 | ------------------------------------------------- 151 | 152 | -> # Supported markdown formatting <- 153 | 154 | Quotes are auto-detected by preceding *>*. 155 | 156 | Multiple *>* are interpreted as nested quotes. 157 | 158 | \> quote 159 | \>> nested quote 1 160 | \> > nested quote 2 161 | 162 | becomes 163 | 164 | > quote 165 | >> nested quote 1 166 | > > nested quote 2 167 | 168 | ------------------------------------------------- 169 | 170 | -> # Supported markdown formatting <- 171 | 172 | Inline highlighting is supported as followed: 173 | 174 | \- *\** colors text as red 175 | \- *\_* underlines text 176 | 177 | \_some\_ \*highlighted\* \_\*text\*\_ 178 | 179 | becomes 180 | 181 | _some_ *highlighted* _*text*_ 182 | 183 | ------------------------------------------------- 184 | 185 | -> # Supported markdown formatting <- 186 | 187 | Backslashes force special markdown characters 188 | like *\**, *\_*, *#* and *>* to be printed as 189 | normal characters. 190 | 191 | \\\*special\\\* 192 | 193 | becomes 194 | 195 | \*special\* 196 | 197 | ------------------------------------------------- 198 | 199 | -> # Supported markdown formatting <- 200 | 201 | Leading *\** or *-* indicate lists. 202 | 203 | list 204 | \* major 205 | \ - minor 206 | \ - \*important\* 207 | \ detail 208 | \ - minor 209 | 210 | becomes 211 | 212 | list 213 | * major 214 | - minor 215 | - *important* 216 | detail 217 | - minor 218 | 219 | ------------------------------------------------- 220 | 221 | -> # Supported markdown formatting <- 222 | 223 | A single *\* or *^* in a line indicates mdp 224 | to stop the output on that position. 225 | 226 | This can be used to show bullet points 227 | line by line. 228 | 229 | *\* is also not displayed in HTML converted 230 | output. 231 | 232 | Agenda 233 |
234 | * major 235 |
236 | * minor 237 |
238 | * major 239 | ^ 240 | * minor 241 | ^ 242 | * detail 243 | 244 | ------------------------------------------------- 245 | 246 | -> # Supported markdown formatting <- 247 | 248 | Leading *->* indicates centering. 249 | 250 | \-> # test <- 251 | \-> ## test <- 252 | \-> test 253 | \-> \_\*test\*\_ <- 254 | 255 | becomes 256 | 257 | -> # test <- 258 | -> ## test <- 259 | -> test 260 | -> _*test*_ <- 261 | 262 | ------------------------------------------------- 263 | 264 | -> # Supported markdown formatting <- 265 | 266 | URL in pandoc style are supported: 267 | 268 | \[Google](http://www.google.com/) 269 | 270 | becomes 271 | 272 | [Google](http://www.google.com/) 273 | 274 | ------------------------------------------------- 275 | 276 | -> ## More information about markdown <- 277 | 278 | can be found in the [markdown documentation](http://daringfireball.net/projects/markdown/). 279 | 280 | ------------------------------------------------- 281 | 282 | -> # Support for UTF-8 special characters <- 283 | 284 | Here are some examples. 285 | 286 | ae = ä, oe = ö, ue = ü, ss = ß 287 | upsilon = Ʊ, phi = ɸ 288 | 289 | ▛▀▀▀▀▀▀▀▀▀▜ 290 | ▌rectangle▐ 291 | ▙▄▄▄▄▄▄▄▄▄▟ 292 | 293 | 294 | ------------------------------------------------- 295 | 296 | -> # Suspend your presentation for hands-on examples <- 297 | 298 | Use *Ctrl + z* to suspend the presentation. 299 | 300 | Use *fg* to resume it. 301 | 302 | ------------------------------------------------- 303 | 304 | -> # Convert your presentation to PDF <- 305 | 306 | To publish your presentation later on, you may 307 | want to convert it to PDF. 308 | 309 | This can be achieved by two additional tools: 310 | 311 | \- *markdown* to convert to HTML 312 | \- *wkhtmltopdf* to convert from HTML to PDF 313 | 314 | After installing them, you can simply type: 315 | 316 | $ markdown sample.md | wkhtmltopdf - sample.pdf 317 | 318 | ------------------------------------------------- 319 | 320 | -> ## Last words <- 321 | 322 | I hope you like *mdp*. 323 | 324 | If you observe strange behavior, feel free to 325 | open an issue on [GitHub](https://github.com/visit1985/mdp). 326 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # Copyright (C) 2018 Michael Goehler 4 | # 5 | # This file is part of mdp. 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | 21 | UNAME_S := $(shell uname -s 2>/dev/null || echo not) 22 | 23 | SOURCES = $(wildcard *.c) 24 | OBJECTS = $(SOURCES:.c=.o) 25 | CFLAGS ?= -O3 26 | CFLAGS += -Wall 27 | CPPFLAGS += -I../include 28 | 29 | ifeq ($(DEBUG),1) 30 | CFLAGS := -O0 -Wall -g 31 | endif 32 | 33 | ifeq ($(OS),Windows_NT) 34 | ifeq (,$(findstring CYGWIN,$(UNAME_S))) 35 | CPPFLAGS += -DWIN32 36 | else 37 | CPPFLAGS += -DCYGWIN 38 | endif 39 | endif 40 | 41 | ifeq ($(UNAME_S),Linux) 42 | LSB_RELEASE := $(shell lsb_release -si 2>/dev/null || echo not) 43 | ifneq ($(filter $(LSB_RELEASE),Debian Ubuntu LinuxMint CrunchBang),) 44 | CPPFLAGS += -I/usr/include/ncursesw 45 | endif 46 | endif 47 | 48 | all: $(OBJECTS) 49 | 50 | clean: 51 | $(RM) $(OBJECTS) 52 | 53 | -------------------------------------------------------------------------------- /src/cstack.c: -------------------------------------------------------------------------------- 1 | /* 2 | * An implementation of a char stack in heap memory. 3 | * Copyright (C) 2018 Michael Goehler 4 | * 5 | * This file is part of mdp. 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | * 20 | */ 21 | 22 | #include 23 | #include // fprintf 24 | #include // malloc, realloc 25 | 26 | #include "cstack.h" 27 | 28 | cstack_t *cstack_init() { 29 | cstack_t *stack = NULL; 30 | if((stack = malloc(sizeof(cstack_t))) != NULL) { 31 | stack->content = NULL; 32 | stack->alloc = stack->size = 0; 33 | stack->head = -1; 34 | stack->push = cstack_push; 35 | stack->pop = cstack_pop; 36 | stack->top = cstack_top; 37 | stack->empty = cstack_empty; 38 | stack->delete = cstack_delete; 39 | } else { 40 | fprintf(stderr, "%s\n", "cstack_init() failed to allocate memory."); 41 | exit(EXIT_FAILURE); 42 | } 43 | return stack; 44 | } 45 | 46 | void cstack_push(cstack_t *self, wchar_t c) { 47 | if(self->size + sizeof(c) > self->alloc) { 48 | self->alloc += (sizeof(wchar_t)); 49 | if((self->content = realloc(self->content, self->alloc)) == NULL) { 50 | fprintf(stderr, "%s\n", "cstack_push() failed to reallocate memory."); 51 | exit(EXIT_FAILURE); 52 | } 53 | } 54 | self->content[++self->head] = c; 55 | self->size += (sizeof(wchar_t)); 56 | } 57 | 58 | wchar_t cstack_pop(cstack_t *self) { 59 | self->size -= (sizeof(wchar_t)); 60 | return self->content[self->head--]; 61 | } 62 | 63 | bool cstack_top(cstack_t *self, wchar_t c) { 64 | return self->head >= 0 && self->content[self->head] == c; 65 | } 66 | 67 | bool cstack_empty(cstack_t *self) { 68 | return self->head == -1; 69 | } 70 | 71 | void cstack_delete(cstack_t *self) { 72 | free(self->content); 73 | free(self); 74 | } 75 | -------------------------------------------------------------------------------- /src/cstring.c: -------------------------------------------------------------------------------- 1 | /* 2 | * An implementation of expandable c strings in heap memory. 3 | * Copyright (C) 2018 Michael Goehler 4 | * 5 | * This file is part of mdp. 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | * 20 | */ 21 | 22 | #include // wcslen, wcscat, wmemmove 23 | #include // fprintf 24 | #include // malloc, realloc 25 | 26 | #include "cstring.h" 27 | 28 | cstring_t *cstring_init() { 29 | cstring_t *x = NULL; 30 | if((x = malloc(sizeof(cstring_t))) != NULL) { 31 | x->value = NULL; 32 | x->size = x->alloc = 0; 33 | x->expand = cstring_expand; 34 | x->expand_arr = cstring_expand_arr; 35 | x->strip = cstring_strip; 36 | x->reset = cstring_reset; 37 | x->delete = cstring_delete; 38 | } else { 39 | fprintf(stderr, "%s\n", "cstring_init() failed to allocate memory."); 40 | exit(EXIT_FAILURE); 41 | } 42 | return x; 43 | } 44 | 45 | void cstring_expand(cstring_t *self, wchar_t x) { 46 | if((self->size + 2) * sizeof(wchar_t) > self->alloc) { 47 | self->alloc += (REALLOC_ADD * sizeof(wchar_t)); 48 | if((self->value = realloc(self->value, self->alloc)) == NULL) { 49 | fprintf(stderr, "%s\n", "cstring_expand() failed to reallocate memory."); 50 | exit(EXIT_FAILURE); 51 | } 52 | } 53 | self->value[self->size] = x; 54 | self->value[self->size+1] = L'\0'; 55 | self->size = wcslen(self->value); 56 | } 57 | 58 | void cstring_expand_arr(cstring_t *self, wchar_t *x) { 59 | if((self->size + wcslen(x) + 1) * sizeof(wchar_t) > self->alloc) { 60 | self->alloc = ((self->size + wcslen(x) + 1) * sizeof(wchar_t)); 61 | if((self->value = realloc(self->value, self->alloc)) == NULL) { 62 | fprintf(stderr, "%s\n", "cstring_expand_arr() failed to reallocate memory."); 63 | exit(EXIT_FAILURE); 64 | } 65 | } 66 | self->value = wcscat(self->value, x); 67 | self->size = wcslen(self->value); 68 | self->value[self->size+1] = L'\0'; 69 | } 70 | 71 | void cstring_strip(cstring_t *self, int pos, int len) { 72 | if(pos + len >= self->size) { 73 | if(pos <= self->size) { 74 | self->value[pos] = L'\0'; 75 | self->size = pos; 76 | } 77 | return; 78 | } 79 | wmemmove(&self->value[pos], &self->value[pos+len], self->size - pos - len+1); 80 | self->size -= len; 81 | } 82 | 83 | void cstring_reset(cstring_t *self) { 84 | free(self->value); 85 | self->value = NULL; 86 | self->size = self->alloc = 0; 87 | } 88 | 89 | void cstring_delete(cstring_t *self) { 90 | free(self->value); 91 | free(self); 92 | } 93 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mdp -- A command-line based markdown presentation tool. 3 | * Copyright (C) 2018 Michael Goehler 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include // setlocale 23 | #include 24 | #include 25 | #include 26 | 27 | #include "main.h" 28 | 29 | void usage() { 30 | fprintf(stderr, "%s", "Usage: mdp [OPTION]... [FILE]\n"); 31 | fprintf(stderr, "%s", "A command-line based markdown presentation tool.\n\n"); 32 | fprintf(stderr, "%s", " -d, --debug enable debug messages on STDERR\n"); 33 | fprintf(stderr, "%s", " add it multiple times to increases debug level\n"); 34 | fprintf(stderr, "%s", " -e, --expand enable character entity expansion\n"); 35 | fprintf(stderr, "%s", " -f, --nofade disable color fading in 256 color mode\n"); 36 | fprintf(stderr, "%s", " -h, --help display this help and exit\n"); 37 | fprintf(stderr, "%s", " -i, --invert swap black and white color\n"); 38 | fprintf(stderr, "%s", " -t, --notrans disable transparency in transparent terminal\n"); 39 | fprintf(stderr, "%s", " -s, --noslidenum do not show slide number at the bottom\n"); 40 | fprintf(stderr, "%s", " -v, --version display the version number and license\n"); 41 | fprintf(stderr, "%s", " -x, --noslidemax show slide number, but not total number of slides\n"); 42 | fprintf(stderr, "%s", " -c, --nocodebg don't change the background color of code blocks\n"); 43 | fprintf(stderr, "%s", "\nWith no FILE, or when FILE is -, read standard input.\n\n"); 44 | exit(EXIT_FAILURE); 45 | } 46 | 47 | void version() { 48 | printf("mdp %d.%d.%d\n", MDP_VER_MAJOR, MDP_VER_MINOR, MDP_VER_REVISION); 49 | printf("Copyright (C) 2018 Michael Goehler\n"); 50 | printf("License GPLv3+: GNU GPL version 3 or later .\n"); 51 | printf("This is free software: you are free to change and redistribute it.\n"); 52 | printf("There is NO WARRANTY, to the extent permitted by law.\n"); 53 | printf("\nWritten by Michael Goehler and others, see .\n"); 54 | exit(EXIT_SUCCESS); 55 | } 56 | 57 | int main(int argc, char *argv[]) { 58 | int notrans = 0; // disable transparency 59 | int nofade = 0; // disable fading 60 | int invert = 0; // invert color (black on white) 61 | int noexpand = 1; // disable character entity expansion 62 | int reload = 0; // reload page N (0 means no reload) 63 | int noreload = 1; // reload disabled until we know input is a file 64 | int slidenum = 2; // 0:don't show; 1:show #; 2:show #/# 65 | int nocodebg = 0; // 0:show code bg as inverted; 1: don't invert code bg 66 | 67 | // define command-line options 68 | struct option longopts[] = { 69 | { "debug", no_argument, 0, 'd' }, 70 | { "expand", no_argument, 0, 'e' }, 71 | { "nofade", no_argument, 0, 'f' }, 72 | { "help", no_argument, 0, 'h' }, 73 | { "invert", no_argument, 0, 'i' }, 74 | { "notrans", no_argument, 0, 't' }, 75 | { "version", no_argument, 0, 'v' }, 76 | { "noslidenum", no_argument, 0, 's' }, 77 | { "noslidemax", no_argument, 0, 'x' }, 78 | { "nocodebg", no_argument, 0, 'c' }, 79 | { 0, 0, 0, 0 } 80 | }; 81 | 82 | // parse command-line options 83 | int opt, debug = 0; 84 | while ((opt = getopt_long(argc, argv, ":defhitvsxc", longopts, NULL)) != -1) { 85 | switch(opt) { 86 | case 'd': debug += 1; break; 87 | case 'e': noexpand = 0; break; 88 | case 'f': nofade = 1; break; 89 | case 'h': usage(); break; 90 | case 'i': invert = 1; break; 91 | case 't': notrans = 1; break; 92 | case 'v': version(); break; 93 | case 's': slidenum = 0; break; 94 | case 'x': slidenum = 1; break; 95 | case 'c': nocodebg = 1; break; 96 | case ':': fprintf(stderr, "%s: '%c' requires an argument\n", argv[0], optopt); usage(); break; 97 | case '?': 98 | default : fprintf(stderr, "%s: option '%c' is invalid\n", argv[0], optopt); usage(); break; 99 | } 100 | } 101 | 102 | // set locale to that of the environment, so that ncurses properly renders 103 | // UTF-8 characters if the system supports it 104 | setlocale(LC_CTYPE, ""); 105 | 106 | // setup list string 107 | setup_list_strings(); 108 | 109 | // open file or set input to STDIN 110 | char *file = NULL; 111 | FILE *input; 112 | if (optind < argc) { 113 | do { 114 | file = argv[optind]; 115 | } while(++optind < argc); 116 | 117 | if(!strcmp(file, "-")) { 118 | input = stdin; 119 | } else { 120 | input = fopen(file,"r"); 121 | if(!input) { 122 | fprintf(stderr, "%s: %s: %s\n", argv[0], file, strerror(errno)); 123 | exit(EXIT_FAILURE); 124 | } 125 | // enable reload because input is a file 126 | noreload = 0; 127 | } 128 | } else { 129 | input = stdin; 130 | } 131 | 132 | // reload loop 133 | do { 134 | 135 | // reopen input file on reload 136 | if(noreload == 0 && reload > 0) { 137 | if(file) { 138 | input = fopen(file,"r"); 139 | if(!input) { 140 | fprintf(stderr, "%s: %s: %s\n", argv[0], file, strerror(errno)); 141 | exit(EXIT_FAILURE); 142 | } 143 | } else { 144 | fprintf(stderr, "%s: %s\n", argv[0], "no input file"); 145 | exit(EXIT_FAILURE); 146 | } 147 | } 148 | 149 | // load deck object from input 150 | deck_t *deck; 151 | deck = markdown_load(input, noexpand); 152 | 153 | // close file 154 | fclose(input); 155 | 156 | // replace stdin with current tty if input was a pipe 157 | // if input was a pipe reload is disabled, so we simply check that 158 | if(noreload == 1) { 159 | input = freopen("/dev/tty", "rw", stdin); 160 | if(!input) { 161 | fprintf(stderr, "%s: %s: %s\n", argv[0], "/dev/tty", strerror(errno)); 162 | exit(EXIT_FAILURE); 163 | } 164 | } 165 | 166 | if(debug > 0) { 167 | markdown_debug(deck, debug); 168 | } 169 | 170 | reload = ncurses_display(deck, notrans, nofade, invert, reload, noreload, slidenum, nocodebg); 171 | 172 | free_deck(deck); 173 | 174 | // reload if supported and requested 175 | } while(noreload == 0 && reload > 0); 176 | 177 | return EXIT_SUCCESS; 178 | } 179 | -------------------------------------------------------------------------------- /src/markdown.c: -------------------------------------------------------------------------------- 1 | /* 2 | * An implementation of markdown objects. 3 | * Copyright (C) 2018 Michael Goehler 4 | * 5 | * This file is part of mdp. 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | * 20 | */ 21 | 22 | #include 23 | #include 24 | 25 | #include "markdown.h" 26 | 27 | line_t *new_line() { 28 | line_t *x = malloc(sizeof(line_t)); 29 | x->text = NULL; 30 | x->prev = x->next = NULL; 31 | x->bits = x->length = x->offset = 0; 32 | return x; 33 | } 34 | 35 | line_t *next_line(line_t *prev) { 36 | line_t *x = new_line(); 37 | x->prev = prev; 38 | prev->next = x; 39 | return x; 40 | } 41 | 42 | slide_t *new_slide() { 43 | slide_t *x = malloc(sizeof(slide_t)); 44 | x->line = NULL; 45 | x->prev = x->next = NULL; 46 | x->lines = x->stop = 0; 47 | return x; 48 | } 49 | 50 | slide_t *next_slide(slide_t *prev) { 51 | slide_t *x = new_slide(); 52 | x->prev = prev; 53 | prev->next = x; 54 | return x; 55 | } 56 | 57 | deck_t *new_deck() { 58 | deck_t *x = malloc(sizeof(deck_t)); 59 | x->header = NULL; 60 | x->slide = new_slide(); 61 | x->slides = x->headers = 0; 62 | return x; 63 | } 64 | 65 | void free_line(line_t *line) { 66 | line_t *next; 67 | while (line) { 68 | next = line->next; 69 | if(line->text) 70 | (line->text->delete)(line->text); 71 | free(line); 72 | line = next; 73 | } 74 | } 75 | 76 | void free_deck(deck_t *deck) { 77 | slide_t *slide, *next; 78 | if (deck == NULL) 79 | return; 80 | slide = deck->slide; 81 | while (slide) { 82 | free_line(slide->line); 83 | next = slide->next; 84 | free(slide); 85 | slide = next; 86 | } 87 | free_line(deck->header); 88 | free(deck); 89 | } 90 | -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Functions necessary to parse a file and transform its content into 3 | * a deck of slides containing lines. All based on markdown formating 4 | * rules. 5 | * Copyright (C) 2018 Michael Goehler 6 | * 7 | * This file is part of mdp. 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "parser.h" 33 | 34 | // char entry translation table 35 | static struct named_character_entity { 36 | wchar_t ucs; 37 | const wchar_t *name; 38 | } named_character_entities[] = { 39 | { L'\x0022', L"quot" }, 40 | { L'\x0026', L"amp" }, 41 | { L'\x0027', L"apos" }, 42 | { L'\x003C', L"lt" }, 43 | { L'\x003E', L"gt" }, 44 | { L'\x00A2', L"cent" }, 45 | { L'\x00A3', L"pound" }, 46 | { L'\x00A5', L"yen" }, 47 | { L'\x00A7', L"sect" }, 48 | { L'\x00A9', L"copy" }, 49 | { L'\x00AA', L"laquo" }, 50 | { L'\x00AE', L"reg" }, 51 | { L'\x00B0', L"deg" }, 52 | { L'\x00B1', L"plusmn" }, 53 | { L'\x00B2', L"sup2" }, 54 | { L'\x00B3', L"sup3" }, 55 | { L'\x00B6', L"para" }, 56 | { L'\x00B9', L"sup1" }, 57 | { L'\x00BB', L"raquo" }, 58 | { L'\x00BC', L"frac14" }, 59 | { L'\x00BD', L"frac12" }, 60 | { L'\x00BE', L"frac34" }, 61 | { L'\x00D7', L"times" }, 62 | { L'\x00F7', L"divide" }, 63 | { L'\x2018', L"lsquo" }, 64 | { L'\x2019', L"rsquo" }, 65 | { L'\x201C', L"ldquo" }, 66 | { L'\x201D', L"rdquo" }, 67 | { L'\x2020', L"dagger" }, 68 | { L'\x2021', L"Dagger" }, 69 | { L'\x2022', L"bull" }, 70 | { L'\x2026', L"hellip" }, 71 | { L'\x2030', L"permil" }, 72 | { L'\x2032', L"prime" }, 73 | { L'\x2033', L"Prime" }, 74 | { L'\x2039', L"lsaquo" }, 75 | { L'\x203A', L"rsaquo" }, 76 | { L'\x20AC', L"euro" }, 77 | { L'\x2122', L"trade" }, 78 | { L'\x2190', L"larr" }, 79 | { L'\x2191', L"uarr" }, 80 | { L'\x2192', L"rarr" }, 81 | { L'\x2193', L"darr" }, 82 | { L'\x2194', L"harr" }, 83 | { L'\x21B5', L"crarr" }, 84 | { L'\x21D0', L"lArr" }, 85 | { L'\x21D1', L"uArr" }, 86 | { L'\x21D2', L"rArr" }, 87 | { L'\x21D3', L"dArr" }, 88 | { L'\x21D4', L"hArr" }, 89 | { L'\x221E', L"infin" }, 90 | { L'\x2261', L"equiv" }, 91 | { L'\x2308', L"lceil" }, 92 | { L'\x2309', L"rceil" }, 93 | { L'\x230A', L"lfloor" }, 94 | { L'\x230B', L"rfloor" }, 95 | { L'\x25CA', L"loz" }, 96 | { L'\x2660', L"spades" }, 97 | { L'\x2663', L"clubs" }, 98 | { L'\x2665', L"hearts" }, 99 | { L'\x2666', L"diams" }, 100 | { L'\0', NULL }, 101 | }; 102 | 103 | deck_t *markdown_load(FILE *input, int noexpand) { 104 | 105 | wchar_t c = L'\0'; // char 106 | int i = 0; // increment 107 | int hc = 0; // header count 108 | int lc = 0; // line count 109 | int sc = 1; // slide count 110 | int bits = 0; // markdown bits 111 | int prev = 0; // markdown bits of previous line 112 | 113 | deck_t *deck = new_deck(); 114 | slide_t *slide = deck->slide; 115 | line_t *line = NULL; 116 | line_t *tmp = NULL; 117 | cstring_t *text = cstring_init(); 118 | 119 | // initialize bits as empty line 120 | SET_BIT(bits, IS_EMPTY); 121 | 122 | while ((c = fgetwc(input)) != WEOF) { 123 | if (ferror(input)) { 124 | fprintf(stderr, "markdown_load() failed to read input: %s\n", strerror(errno)); 125 | exit(EXIT_FAILURE); 126 | } 127 | 128 | if(c == L'\n') { 129 | 130 | // markdown analyse 131 | prev = bits; 132 | bits = markdown_analyse(text, prev); 133 | 134 | // if first line in file is markdown hr 135 | if(!line && CHECK_BIT(bits, IS_HR)) { 136 | 137 | // clear text 138 | (text->reset)(text); 139 | 140 | } else if(line && CHECK_BIT(bits, IS_STOP)) { 141 | 142 | // set stop bit on last line 143 | SET_BIT(line->bits, IS_STOP); 144 | 145 | // clear text 146 | (text->reset)(text); 147 | 148 | // if text is markdown hr 149 | } else if(CHECK_BIT(bits, IS_HR) && 150 | CHECK_BIT(line->bits, IS_EMPTY)) { 151 | 152 | slide->lines = lc; 153 | 154 | // clear text 155 | (text->reset)(text); 156 | 157 | // create next slide 158 | slide = next_slide(slide); 159 | sc++; 160 | 161 | } else if((CHECK_BIT(bits, IS_TILDE_CODE) || 162 | CHECK_BIT(bits, IS_GFM_CODE)) && 163 | CHECK_BIT(bits, IS_EMPTY)) { 164 | // remove tilde code markers 165 | (text->reset)(text); 166 | 167 | } else { 168 | 169 | // if slide ! has line 170 | if(!slide->line || !line) { 171 | 172 | // create new line 173 | line = new_line(); 174 | slide->line = line; 175 | lc = 1; 176 | 177 | } else { 178 | 179 | // create next line 180 | line = next_line(line); 181 | lc++; 182 | 183 | } 184 | 185 | // add text to line 186 | line->text = text; 187 | 188 | // add bits to line 189 | line->bits = bits; 190 | 191 | // calc offset 192 | line->offset = next_nonblank(text, 0); 193 | 194 | // expand character entities if enabled 195 | if(line->text->value && 196 | !noexpand && 197 | !CHECK_BIT(line->bits, IS_CODE)) 198 | expand_character_entities(line); 199 | 200 | // adjust line length dynamicaly - excluding markup 201 | if(line->text->value) 202 | adjust_line_length(line); 203 | 204 | // new text 205 | text = cstring_init(); 206 | } 207 | 208 | } else if(c == L'\t') { 209 | 210 | // expand tab to spaces 211 | for (i = 0; i < EXPAND_TABS; i++) { 212 | (text->expand)(text, L' '); 213 | } 214 | 215 | } else if(c == L'\\') { 216 | 217 | // add char to line 218 | (text->expand)(text, c); 219 | 220 | // if !IS_CODE add next char to line 221 | // and do not increase line count 222 | if(next_nonblank(text, 0) < CODE_INDENT) { 223 | 224 | c = fgetwc(input); 225 | (text->expand)(text, c); 226 | } 227 | 228 | } else if(iswprint(c) || iswspace(c)) { 229 | 230 | // add char to line 231 | (text->expand)(text, c); 232 | } 233 | } 234 | (text->delete)(text); 235 | 236 | slide->lines = lc; 237 | deck->slides = sc; 238 | 239 | // detect header 240 | line = deck->slide->line; 241 | if(line && line->text->size > 0 && line->text->value[0] == L'%') { 242 | 243 | // assign header to deck 244 | deck->header = line; 245 | 246 | // find first non-header line 247 | while(line && line->text->size > 0 && line->text->value[0] == L'%') { 248 | hc++; 249 | line = line->next; 250 | } 251 | 252 | // only split header if any non-header line is found 253 | if(line) { 254 | 255 | // split linked list 256 | line->prev->next = NULL; 257 | line->prev = NULL; 258 | 259 | // remove header lines from slide 260 | deck->slide->line = line; 261 | 262 | // adjust counts 263 | deck->headers += hc; 264 | deck->slide->lines -= hc; 265 | } else { 266 | 267 | // remove header from deck 268 | deck->header = NULL; 269 | } 270 | } 271 | 272 | slide = deck->slide; 273 | while(slide) { 274 | line = slide->line; 275 | 276 | // ignore mdpress format attributes 277 | if(line && 278 | slide->lines > 1 && 279 | !CHECK_BIT(line->bits, IS_EMPTY) && 280 | line->text->value[line->offset] == L'=' && 281 | line->text->value[line->offset + 1] == L' ') { 282 | 283 | // remove line from linked list 284 | slide->line = line->next; 285 | line->next->prev = NULL; 286 | 287 | // maintain loop condition 288 | tmp = line; 289 | line = line->next; 290 | 291 | // adjust line count 292 | slide->lines -= 1; 293 | 294 | // delete line 295 | (tmp->text->delete)(tmp->text); 296 | free(tmp); 297 | } 298 | 299 | while(line) { 300 | // combine underlined H1/H2 in single line 301 | if((CHECK_BIT(line->bits, IS_H1) || 302 | CHECK_BIT(line->bits, IS_H2)) && 303 | CHECK_BIT(line->bits, IS_EMPTY) && 304 | line->prev && 305 | !CHECK_BIT(line->prev->bits, IS_EMPTY)) { 306 | 307 | 308 | // remove line from linked list 309 | line->prev->next = line->next; 310 | if(line->next) 311 | line->next->prev = line->prev; 312 | 313 | // set bits on previous line 314 | if(CHECK_BIT(line->bits, IS_H1)) { 315 | SET_BIT(line->prev->bits, IS_H1); 316 | } else { 317 | SET_BIT(line->prev->bits, IS_H2); 318 | } 319 | 320 | // adjust line count 321 | slide->lines -= 1; 322 | 323 | // maintain loop condition 324 | tmp = line; 325 | line = line->prev; 326 | 327 | // delete line 328 | (tmp->text->delete)(tmp->text); 329 | free(tmp); 330 | 331 | // pass enclosing flag IS_UNORDERED_LIST_3 332 | // to nested levels for unordered lists 333 | } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) { 334 | tmp = line->next; 335 | line_t *list_last_level_3 = line; 336 | 337 | while(tmp && 338 | CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3)) { 339 | if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3)) { 340 | list_last_level_3 = tmp; 341 | } 342 | tmp = tmp->next; 343 | } 344 | 345 | for(tmp = line; tmp != list_last_level_3; tmp = tmp->next) { 346 | SET_BIT(tmp->bits, IS_UNORDERED_LIST_3); 347 | } 348 | 349 | // pass enclosing flag IS_UNORDERED_LIST_2 350 | // to nested levels for unordered lists 351 | } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) { 352 | tmp = line->next; 353 | line_t *list_last_level_2 = line; 354 | 355 | while(tmp && 356 | (CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2) || 357 | CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3))) { 358 | if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2)) { 359 | list_last_level_2 = tmp; 360 | } 361 | tmp = tmp->next; 362 | } 363 | 364 | for(tmp = line; tmp != list_last_level_2; tmp = tmp->next) { 365 | SET_BIT(tmp->bits, IS_UNORDERED_LIST_2); 366 | } 367 | 368 | // pass enclosing flag IS_UNORDERED_LIST_1 369 | // to nested levels for unordered lists 370 | } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) { 371 | tmp = line->next; 372 | line_t *list_last_level_1 = line; 373 | 374 | while(tmp && 375 | (CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_1) || 376 | CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_2) || 377 | CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_3))) { 378 | if(CHECK_BIT(tmp->bits, IS_UNORDERED_LIST_1)) { 379 | list_last_level_1 = tmp; 380 | } 381 | tmp = tmp->next; 382 | } 383 | 384 | for(tmp = line; tmp != list_last_level_1; tmp = tmp->next) { 385 | SET_BIT(tmp->bits, IS_UNORDERED_LIST_1); 386 | } 387 | } 388 | 389 | line = line->next; 390 | } 391 | slide = slide->next; 392 | } 393 | 394 | return deck; 395 | } 396 | 397 | int markdown_analyse(cstring_t *text, int prev) { 398 | 399 | // static variables can not be redeclaired, but changed outside of a declaration 400 | // the program remembers their value on every function calls 401 | static int unordered_list_level = 0; 402 | static int unordered_list_level_offset[] = {-1, -1, -1, -1}; 403 | static int num_tilde_characters = 0; 404 | static int num_backticks = 0; 405 | 406 | int i = 0; // increment 407 | int bits = 0; // markdown bits 408 | int offset = 0; // text offset 409 | int eol = 0; // end of line 410 | 411 | int equals = 0, hashes = 0, 412 | stars = 0, minus = 0, 413 | spaces = 0, other = 0; // special character counts 414 | 415 | const int unordered_list_offset = unordered_list_level_offset[unordered_list_level]; 416 | 417 | // return IS_EMPTY on null pointers 418 | if(!text || !text->value) { 419 | SET_BIT(bits, IS_EMPTY); 420 | 421 | // continue fenced code blocks across empty lines 422 | if(num_tilde_characters > 0) 423 | SET_BIT(bits, IS_CODE); 424 | 425 | return bits; 426 | } 427 | 428 | // count leading spaces 429 | offset = next_nonblank(text, 0); 430 | 431 | // IS_TILDE_CODE 432 | if (wcsncmp(text->value, L"~~~", 3) == 0) { 433 | int tildes_in_line = next_nontilde(text, 0); 434 | if (tildes_in_line >= num_tilde_characters) { 435 | if (num_tilde_characters > 0) { 436 | num_tilde_characters = 0; 437 | } else { 438 | num_tilde_characters = tildes_in_line; 439 | } 440 | SET_BIT(bits, IS_EMPTY); 441 | SET_BIT(bits, IS_TILDE_CODE); 442 | return bits; 443 | } 444 | } 445 | 446 | if (num_tilde_characters > 0) { 447 | SET_BIT(bits, IS_CODE); 448 | SET_BIT(bits, IS_TILDE_CODE); 449 | return bits; 450 | } 451 | 452 | // IS_GFM_CODE 453 | if (wcsncmp(text->value, L"```", 3) == 0) { 454 | int backticks_in_line = next_nonbacktick(text, 0); 455 | if (backticks_in_line >= num_backticks) { 456 | if (num_backticks > 0) { 457 | num_backticks = 0; 458 | } else { 459 | num_backticks = backticks_in_line; 460 | } 461 | SET_BIT(bits, IS_EMPTY); 462 | SET_BIT(bits, IS_GFM_CODE); 463 | return bits; 464 | } 465 | } 466 | 467 | if (num_backticks > 0) { 468 | SET_BIT(bits, IS_CODE); 469 | SET_BIT(bits, IS_GFM_CODE); 470 | return bits; 471 | } 472 | 473 | // IS_STOP 474 | if((offset < CODE_INDENT || !CHECK_BIT(prev, IS_CODE)) && 475 | (!wcsncmp(&text->value[offset], L"
", 4) || 476 | !wcsncmp(&text->value[offset], L"
", 4) || 477 | !wcsncmp(&text->value[offset], L"^", 1))) { 478 | SET_BIT(bits, IS_STOP); 479 | return bits; 480 | } 481 | 482 | // strip trailing spaces 483 | for(eol = text->size; eol > offset && iswspace(text->value[eol - 1]); eol--); 484 | text->size = eol; 485 | 486 | // IS_UNORDERED_LIST_# 487 | if(text->size >= offset + 2 && 488 | (text->value[offset] == L'*' || text->value[offset] == L'-') && 489 | iswspace(text->value[offset + 1])) { 490 | 491 | // if different from last lines offset 492 | if(offset != unordered_list_offset) { 493 | 494 | // test if offset matches a lower indent level 495 | for(i = unordered_list_level; i >= 0; i--) { 496 | if(unordered_list_level_offset[i] == offset) { 497 | unordered_list_level = i; 498 | break; 499 | } 500 | } 501 | // if offset doesn't match any previously stored indent level 502 | if(i != unordered_list_level) { 503 | unordered_list_level = MIN(unordered_list_level + 1, UNORDERED_LIST_MAX_LEVEL); 504 | // memorize the offset as next bigger indent level 505 | unordered_list_level_offset[unordered_list_level] = offset; 506 | } 507 | } 508 | 509 | // if no previous indent level matches, this must be the first line of the list 510 | if(unordered_list_level == 0) { 511 | unordered_list_level = 1; 512 | unordered_list_level_offset[1] = offset; 513 | } 514 | 515 | switch(unordered_list_level) { 516 | case 1: SET_BIT(bits, IS_UNORDERED_LIST_1); break; 517 | case 2: SET_BIT(bits, IS_UNORDERED_LIST_2); break; 518 | case 3: SET_BIT(bits, IS_UNORDERED_LIST_3); break; 519 | default: break; 520 | } 521 | } 522 | 523 | if(!CHECK_BIT(bits, IS_UNORDERED_LIST_1) && 524 | !CHECK_BIT(bits, IS_UNORDERED_LIST_2) && 525 | !CHECK_BIT(bits, IS_UNORDERED_LIST_3)) { 526 | 527 | // continue list if indent level is still the same as in previous line 528 | if ((CHECK_BIT(prev, IS_UNORDERED_LIST_1) || 529 | CHECK_BIT(prev, IS_UNORDERED_LIST_2) || 530 | CHECK_BIT(prev, IS_UNORDERED_LIST_3)) && 531 | offset >= unordered_list_offset) { 532 | 533 | switch(unordered_list_level) { 534 | case 1: SET_BIT(bits, IS_UNORDERED_LIST_1); break; 535 | case 2: SET_BIT(bits, IS_UNORDERED_LIST_2); break; 536 | case 3: SET_BIT(bits, IS_UNORDERED_LIST_3); break; 537 | default: break; 538 | } 539 | 540 | // this line extends the previous list item 541 | SET_BIT(bits, IS_UNORDERED_LIST_EXT); 542 | 543 | // or reset indent level 544 | } else { 545 | unordered_list_level = 0; 546 | } 547 | } 548 | 549 | if(!CHECK_BIT(bits, IS_UNORDERED_LIST_1) && 550 | !CHECK_BIT(bits, IS_UNORDERED_LIST_2) && 551 | !CHECK_BIT(bits, IS_UNORDERED_LIST_3)) { 552 | 553 | // IS_CODE 554 | if(offset >= CODE_INDENT && 555 | (CHECK_BIT(prev, IS_EMPTY) || 556 | CHECK_BIT(prev, IS_CODE) || 557 | CHECK_BIT(prev, IS_STOP))) { 558 | SET_BIT(bits, IS_CODE); 559 | 560 | } else { 561 | 562 | // IS_QUOTE 563 | if(text->value[offset] == L'>') { 564 | SET_BIT(bits, IS_QUOTE); 565 | } 566 | 567 | // IS_CENTER 568 | if(text->size >= offset + 3 && 569 | text->value[offset] == L'-' && 570 | text->value[offset + 1] == L'>' && 571 | iswspace(text->value[offset + 2])) { 572 | SET_BIT(bits, IS_CENTER); 573 | 574 | // remove start tag 575 | (text->strip)(text, offset, 3); 576 | eol -= 3; 577 | 578 | if(text->size >= offset + 3 && 579 | text->value[eol - 1] == L'-' && 580 | text->value[eol - 2] == L'<' && 581 | iswspace(text->value[eol - 3])) { 582 | 583 | // remove end tags 584 | (text->strip)(text, eol - 3, 3); 585 | 586 | // adjust end of line 587 | for(eol = text->size; eol > offset && iswspace(text->value[eol - 1]); eol--); 588 | 589 | } 590 | } 591 | 592 | for(i = offset; i < eol; i++) { 593 | 594 | if(iswspace(text->value[i])) { 595 | spaces++; 596 | 597 | } else { 598 | switch(text->value[i]) { 599 | case L'=': equals++; break; 600 | case L'#': hashes++; break; 601 | case L'*': stars++; break; 602 | case L'-': minus++; break; 603 | case L'\\': other++; i++; break; 604 | default: other++; break; 605 | } 606 | } 607 | } 608 | 609 | // IS_H1 610 | if(equals > 0 && 611 | hashes + stars + minus + spaces + other == 0) { 612 | SET_BIT(bits, IS_H1); 613 | } 614 | if(text->value[offset] == L'#' && 615 | iswspace(text->value[offset+1])) { 616 | SET_BIT(bits, IS_H1); 617 | SET_BIT(bits, IS_H1_ATX); 618 | } 619 | 620 | // IS_H2 621 | if(minus > 0 && 622 | equals + hashes + stars + spaces + other == 0) { 623 | SET_BIT(bits, IS_H2); 624 | } 625 | if(text->value[offset] == L'#' && 626 | text->value[offset+1] == L'#' && 627 | iswspace(text->value[offset+2])) { 628 | SET_BIT(bits, IS_H2); 629 | SET_BIT(bits, IS_H2_ATX); 630 | } 631 | 632 | // IS_HR 633 | if((minus >= 3 && equals + hashes + stars + other == 0) || 634 | (stars >= 3 && equals + hashes + minus + other == 0)) { 635 | 636 | SET_BIT(bits, IS_HR); 637 | } 638 | 639 | // IS_EMPTY 640 | if(other == 0) { 641 | SET_BIT(bits, IS_EMPTY); 642 | } 643 | } 644 | } 645 | 646 | return bits; 647 | } 648 | 649 | void markdown_debug(deck_t *deck, int debug) { 650 | 651 | int sc = 0; // slide count 652 | int lc = 0; // line count 653 | 654 | int offset; 655 | line_t *header; 656 | 657 | if(debug == 1) { 658 | fwprintf(stderr, L"headers: %i\nslides: %i\n", deck->headers, deck->slides); 659 | 660 | } else if(debug > 1) { 661 | 662 | // print header to STDERR 663 | if(deck->header) { 664 | header = deck->header; 665 | while(header && 666 | header->length > 0 && 667 | header->text->value[0] == L'%') { 668 | 669 | // skip descriptor word (e.g. %title:) 670 | offset = next_blank(header->text, 0) + 1; 671 | 672 | fwprintf(stderr, L"header: %S\n", &header->text->value[offset]); 673 | header = header->next; 674 | } 675 | } 676 | } 677 | 678 | slide_t *slide = deck->slide; 679 | line_t *line; 680 | 681 | // print slide/line count to STDERR 682 | while(slide) { 683 | sc++; 684 | 685 | if(debug == 1) { 686 | fwprintf(stderr, L" slide %i: %i lines\n", sc, slide->lines); 687 | 688 | } else if(debug > 1) { 689 | 690 | // also print bits and line length 691 | fwprintf(stderr, L" slide %i:\n", sc); 692 | line = slide->line; 693 | lc = 0; 694 | while(line) { 695 | lc++; 696 | fwprintf(stderr, L" line %i: bits = %i, length = %i\n", lc, line->bits, line->length); 697 | line = line->next; 698 | } 699 | } 700 | 701 | slide = slide->next; 702 | } 703 | } 704 | 705 | void expand_character_entities(line_t *line) 706 | { 707 | wchar_t *ampersand; 708 | wchar_t *prev, *curr; 709 | 710 | ampersand = NULL; 711 | curr = &line->text->value[0]; 712 | 713 | // for each char in line 714 | for(prev = NULL; *curr; prev = curr++) { 715 | if (*curr == L'&' && (prev == NULL || *prev != L'\\')) { 716 | ampersand = curr; 717 | continue; 718 | } 719 | if (ampersand == NULL) { 720 | continue; 721 | } 722 | if (*curr == L'#') { 723 | if (prev == ampersand) 724 | continue; 725 | ampersand = NULL; 726 | continue; 727 | } 728 | if (iswalpha(*curr) || iswxdigit(*curr)) { 729 | continue; 730 | } 731 | if (*curr == L';') { 732 | int cnt; 733 | wchar_t ucs = L'\0'; 734 | if (ampersand + 1 >= curr || ampersand + 16 < curr) { // what is a good limit? 735 | ampersand = NULL; 736 | continue; 737 | } 738 | if (ampersand[1] == L'#') { // &#nnnn; or &#xhhhh; 739 | if (ampersand + 2 >= curr) { 740 | ampersand = NULL; 741 | continue; 742 | } 743 | if (ampersand[2] != L'x') { // &#nnnn; 744 | cnt = wcsspn(&ersand[2], L"0123456789"); 745 | if (ampersand + 2 + cnt != curr) { 746 | ampersand = NULL; 747 | continue; 748 | } 749 | ucs = wcstoul(&ersand[2], NULL, 10); 750 | } else { // &#xhhhh; 751 | if (ampersand + 3 >= curr) { 752 | ampersand = NULL; 753 | continue; 754 | } 755 | cnt = wcsspn(&ersand[3], L"0123456789abcdefABCDEF"); 756 | if (ampersand + 3 + cnt != curr) { 757 | ampersand = NULL; 758 | continue; 759 | } 760 | ucs = wcstoul(&ersand[3], NULL, 16); 761 | } 762 | } else { // &name; 763 | for (cnt = 0; cnt < sizeof(named_character_entities)/sizeof(named_character_entities[0]); ++cnt) { 764 | if (wcsncmp(named_character_entities[cnt].name, &ersand[1], curr - ampersand - 1)) 765 | continue; 766 | ucs = named_character_entities[cnt].ucs; 767 | break; 768 | } 769 | if (ucs == L'\0') { 770 | ampersand = NULL; 771 | continue; 772 | } 773 | } 774 | *ampersand = ucs; 775 | cstring_strip(line->text, ampersand + 1 - &line->text->value[0], curr - ampersand); 776 | curr = ampersand; 777 | ampersand = NULL; 778 | } 779 | } 780 | } 781 | 782 | void adjust_line_length(line_t *line) { 783 | int l = 0; 784 | const static wchar_t *special = L"\\*_`"; // list of interpreted chars 785 | const wchar_t *c = &line->text->value[0]; 786 | cstack_t *stack = cstack_init(); 787 | 788 | // for each char in line 789 | for(; *c; c++) { 790 | // if char is in special char list 791 | if(wcschr(special, *c)) { 792 | 793 | // closing special char (or second backslash) 794 | if((stack->top)(stack, *c)) { 795 | if(*c == L'\\') l++; 796 | (stack->pop)(stack); 797 | 798 | // treat special as regular char 799 | } else if((stack->top)(stack, L'\\')) { 800 | l++; 801 | (stack->pop)(stack); 802 | 803 | // opening special char 804 | } else { 805 | (stack->push)(stack, *c); 806 | } 807 | 808 | } else { 809 | // remove backslash from stack 810 | if((stack->top)(stack, L'\\')) 811 | (stack->pop)(stack); 812 | l++; 813 | } 814 | } 815 | 816 | if(CHECK_BIT(line->bits, IS_H1_ATX)) 817 | l -= 2; 818 | if(CHECK_BIT(line->bits, IS_H2_ATX)) 819 | l -= 3; 820 | 821 | line->length = l; 822 | 823 | (stack->delete)(stack); 824 | } 825 | 826 | int next_nonblank(cstring_t *text, int i) { 827 | while ((i < text->size) && iswspace((text->value)[i])) 828 | i++; 829 | 830 | return i; 831 | } 832 | 833 | int prev_blank(cstring_t *text, int i) { 834 | while ((i > 0) && !iswspace((text->value)[i])) 835 | i--; 836 | 837 | return i; 838 | } 839 | 840 | int next_blank(cstring_t *text, int i) { 841 | while ((i < text->size) && !iswspace((text->value)[i])) 842 | i++; 843 | 844 | return i; 845 | } 846 | 847 | int next_word(cstring_t *text, int i) { 848 | return next_nonblank(text, next_blank(text, i)); 849 | } 850 | 851 | int next_nontilde(cstring_t *text, int i) { 852 | while ((i < text->size) && text->value[i] == L'~') 853 | i++; 854 | 855 | return i; 856 | } 857 | 858 | int next_nonbacktick(cstring_t *text, int i) { 859 | while ((i < text->size) && text->value[i] == L'`') 860 | i++; 861 | 862 | return i; 863 | } 864 | 865 | -------------------------------------------------------------------------------- /src/url.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Functions necessary to handle pandoc URLs. 3 | * Copyright (C) 2018 Michael Goehler 4 | * 5 | * This file is part of mdp. 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | * 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "url.h" 28 | 29 | static void url_del_elem(url_t *elem); 30 | static void url_print(url_t *u); 31 | 32 | static url_t *list; 33 | static int index_max; 34 | static int init_ok; 35 | 36 | void url_init(void) { 37 | list = NULL; 38 | index_max = 0; 39 | init_ok = 1; 40 | } 41 | 42 | int url_add(const wchar_t *link_name, int link_name_length, const wchar_t *target, int target_length, int x, int y) { 43 | if (!init_ok) return -1; 44 | 45 | url_t *tmp = NULL; 46 | int i = 0; 47 | 48 | if (list) { 49 | tmp = list; 50 | while (tmp->next) { 51 | tmp = tmp->next; 52 | i++; 53 | } 54 | tmp->next = malloc(sizeof(url_t)); 55 | assert(tmp->next); 56 | tmp = tmp->next; 57 | } else { 58 | list = malloc(sizeof(url_t)); 59 | tmp = list; 60 | assert(tmp); 61 | } 62 | 63 | tmp -> link_name = calloc(link_name_length+1, sizeof(wchar_t)); 64 | assert(tmp->link_name); 65 | wcsncpy(tmp->link_name, link_name, link_name_length); 66 | tmp->link_name[link_name_length] = '\0'; 67 | 68 | tmp->target = calloc(target_length+1, sizeof(wchar_t)); 69 | assert(tmp->target); 70 | wcsncpy(tmp->target, target, target_length); 71 | tmp->target[target_length] = '\0'; 72 | 73 | tmp->x = x; 74 | tmp->y = y; 75 | tmp->next = NULL; 76 | 77 | index_max++; 78 | 79 | return index_max-1; 80 | } 81 | 82 | wchar_t * url_get_target(int index) { 83 | if (!init_ok) return NULL; 84 | 85 | url_t *tmp = list; 86 | 87 | if (!tmp) return NULL; 88 | 89 | while (index > 0 && tmp && tmp->next) { 90 | tmp = tmp->next; 91 | index--; 92 | } 93 | 94 | if (!index) { 95 | return tmp->target; 96 | } else return NULL; 97 | } 98 | 99 | wchar_t * url_get_name(int index) { 100 | url_t *tmp = list; 101 | 102 | while (index > 0 && tmp && tmp->next) { 103 | tmp = tmp->next; 104 | index --; 105 | } 106 | 107 | if (!index) { 108 | return tmp->link_name; 109 | } else return NULL; 110 | } 111 | 112 | void url_purge() { 113 | url_del_elem(list); 114 | list = NULL; 115 | index_max = 0; 116 | init_ok = 0; 117 | } 118 | 119 | static void url_del_elem(url_t *elem) { 120 | if (!elem) return; 121 | 122 | if (elem->next) { 123 | url_del_elem(elem->next); 124 | elem->next = NULL; 125 | } 126 | 127 | if (elem->target) { 128 | free(elem->target); 129 | elem->target = NULL; 130 | } 131 | 132 | if (elem->link_name) { 133 | free(elem->link_name); 134 | elem->link_name = NULL; 135 | } 136 | 137 | free(elem); 138 | } 139 | 140 | void url_dump(void) { 141 | if (!list) return; 142 | 143 | url_t *tmp = list; 144 | 145 | while (tmp) { 146 | url_print(tmp); 147 | if (tmp->next) 148 | tmp = tmp->next; 149 | else break; 150 | } 151 | } 152 | 153 | static void url_print(url_t *u) { 154 | printf("url_t @ %p\n", u); 155 | } 156 | 157 | int url_get_amount(void) { 158 | return index_max; 159 | } 160 | 161 | int url_count_inline(const wchar_t *line) { 162 | int count = 0; 163 | const wchar_t *i = line; 164 | 165 | for (; *i; i++) { 166 | if (*i == '\\') { 167 | i++; 168 | } else if ( *i == '[' && *(i+1) && *(i+1) != ']') { 169 | while (*i && *i != ']') i++; 170 | i++; 171 | if (*i == '(' && wcschr(i, ')')) { 172 | count ++; 173 | i = wcschr(i, ')'); 174 | } 175 | } 176 | } 177 | 178 | return count; 179 | } 180 | 181 | int url_len_inline(const wchar_t *value) { 182 | int count = 0; 183 | const wchar_t *i = value; 184 | 185 | for (; *i; i++) { 186 | if (*i == '\\') { 187 | i++; 188 | } else if ( *i == '[' && *(i+1) && *(i+1) != ']') { 189 | while (*i && *i != ']') i++; 190 | i++; 191 | if (*i == '(' && wcschr(i, ')')) { 192 | while (*i && *i != ')') { 193 | count++; 194 | i++; 195 | } 196 | } 197 | } 198 | } 199 | 200 | return count; 201 | } 202 | -------------------------------------------------------------------------------- /src/viewer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Functions necessary to display a deck of slides in different color modes 3 | * using ncurses. Only white, red, and blue are supported, as they can be 4 | * faded in 256 color mode. 5 | * Copyright (C) 2018 Michael Goehler 6 | * 7 | * This file is part of mdp. 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #include // isalnum 25 | #include // wcschr 26 | #include // iswalnum 27 | #include // strcpy 28 | #include // usleep 29 | #include // getenv 30 | #include "viewer.h" 31 | #include "config.h" 32 | 33 | int ncurses_display(deck_t *deck, int notrans, int nofade, int invert, int reload, int noreload, int slidenum, int nocodebg) { 34 | 35 | int c = 0; // char 36 | int i = 0; // iterate 37 | int l = 0; // line number 38 | int lc = 0; // line count 39 | int sc = 1; // slide count 40 | int colors = 0; // amount of colors supported 41 | int fade = 0; // disable color fading by default 42 | int trans = -1; // enable transparency if term supports it 43 | int max_lines = 0; // max lines per slide 44 | int max_lines_slide = -1; // the slide that has the most lines 45 | int max_cols = 0; // max columns per line 46 | int offset; // text offset 47 | int stop = 0; // passed stop bits per slide 48 | 49 | // header line 1 is displayed at the top 50 | int bar_top = (deck->headers > 0) ? 1 : 0; 51 | // header line 2 is displayed at the bottom 52 | // anyway we display the slide number at the bottom 53 | int bar_bottom = (slidenum || deck->headers > 1)? 1 : 0; 54 | 55 | slide_t *slide = deck->slide; 56 | line_t *line; 57 | 58 | // init ncurses 59 | initscr(); 60 | 61 | while(slide) { 62 | lc = 0; 63 | line = slide->line; 64 | 65 | while(line && line->text) { 66 | 67 | if (line->text->value) { 68 | lc += url_count_inline(line->text->value); 69 | line->length -= url_len_inline(line->text->value); 70 | } 71 | 72 | if(line->length > COLS) { 73 | i = line->length; 74 | offset = 0; 75 | while(i > COLS) { 76 | 77 | i = prev_blank(line->text, offset + COLS) - offset; 78 | 79 | // single word is > COLS 80 | if(!i) { 81 | // calculate min_width 82 | i = next_blank(line->text, offset + COLS) - offset; 83 | 84 | // disable ncurses 85 | endwin(); 86 | 87 | // print error 88 | fwprintf(stderr, L"Error: Terminal width (%i columns) too small. Need at least %i columns.\n", COLS, i); 89 | fwprintf(stderr, L"You may need to shorten some lines by inserting line breaks.\n"); 90 | 91 | // no reload 92 | return 0; 93 | } 94 | 95 | // set max_cols 96 | max_cols = MAX(i, max_cols); 97 | 98 | // iterate to next line 99 | offset = prev_blank(line->text, offset + COLS); 100 | i = line->length - offset; 101 | lc++; 102 | } 103 | // set max_cols one last time 104 | max_cols = MAX(i, max_cols); 105 | } else { 106 | // set max_cols 107 | max_cols = MAX(line->length, max_cols); 108 | } 109 | lc++; 110 | line = line->next; 111 | } 112 | 113 | max_lines = MAX(lc, max_lines); 114 | if (lc == max_lines) { 115 | max_lines_slide = sc; 116 | } 117 | 118 | slide->lines_consumed = lc; 119 | slide = slide->next; 120 | ++sc; 121 | } 122 | 123 | // not enough lines 124 | if(max_lines + bar_top + bar_bottom > LINES) { 125 | 126 | // disable ncurses 127 | endwin(); 128 | 129 | // print error 130 | fwprintf(stderr, L"Error: Terminal height (%i lines) too small. Need at least %i lines for slide #%i.\n", LINES, max_lines + bar_top + bar_bottom, max_lines_slide); 131 | fwprintf(stderr, L"You may need to add additional horizontal rules (---) to split your file in shorter slides.\n"); 132 | 133 | // no reload 134 | return 0; 135 | } 136 | 137 | // disable cursor 138 | curs_set(0); 139 | 140 | // disable output of keyboard typing 141 | noecho(); 142 | 143 | // make getch() process one char at a time 144 | cbreak(); 145 | 146 | // enable arrow keys 147 | keypad(stdscr,TRUE); 148 | 149 | // set colors 150 | if(has_colors() == TRUE) { 151 | start_color(); 152 | use_default_colors(); 153 | 154 | // 256 color mode 155 | if(COLORS == 256) { 156 | 157 | if(notrans) { 158 | if(invert) { 159 | trans = 15; // white in 256 color mode 160 | } else { 161 | trans = 16; // black in 256 color mode 162 | } 163 | } 164 | 165 | if(invert) { 166 | init_pair(CP_WHITE, 232, trans); 167 | init_pair(CP_BLUE, 21, trans); 168 | init_pair(CP_RED, 196, trans); 169 | init_pair(CP_BLACK, 15, 232); 170 | } else { 171 | init_pair(CP_WHITE, 255, trans); 172 | init_pair(CP_BLUE, 123, trans); 173 | init_pair(CP_RED, 213, trans); 174 | init_pair(CP_BLACK, 16, 255); 175 | } 176 | init_pair(CP_YELLOW, 208, trans); 177 | 178 | // enable color fading 179 | if(!nofade) 180 | fade = true; 181 | 182 | // 8 color mode 183 | } else { 184 | 185 | if(notrans) { 186 | if(invert) { 187 | trans = FG_COLOR; // white in 8 color mode 188 | } else { 189 | trans = BG_COLOR; // black in 8 color mode 190 | } 191 | } 192 | 193 | if(invert) { 194 | init_pair(CP_WHITE, BG_COLOR, trans); 195 | init_pair(CP_BLACK, FG_COLOR, BG_COLOR); 196 | } else { 197 | init_pair(CP_WHITE, FG_COLOR, trans); 198 | init_pair(CP_BLACK, BG_COLOR, FG_COLOR); 199 | } 200 | init_pair(CP_BLUE, HEADER_COLOR, trans); 201 | init_pair(CP_RED, BOLD_COLOR, trans); 202 | init_pair(CP_YELLOW, TITLE_COLOR, trans); 203 | } 204 | 205 | colors = 1; 206 | } 207 | 208 | // set background color for main window 209 | if(colors) 210 | wbkgd(stdscr, COLOR_PAIR(CP_WHITE)); 211 | 212 | // setup content window 213 | WINDOW *content = newwin(LINES - bar_top - bar_bottom, COLS, 0 + bar_top, 0); 214 | 215 | // set background color of content window 216 | if(colors) 217 | wbkgd(content, COLOR_PAIR(CP_WHITE)); 218 | 219 | slide = deck->slide; 220 | 221 | // find slide to reload 222 | sc = 1; 223 | while(reload > 1 && reload <= deck->slides) { 224 | slide = slide->next; 225 | sc++; 226 | reload--; 227 | } 228 | 229 | // reset reload indicator 230 | reload = 0; 231 | 232 | while(slide) { 233 | 234 | url_init(); 235 | 236 | // clear windows 237 | werase(content); 238 | werase(stdscr); 239 | 240 | // always resize window in case terminal geometry has changed 241 | wresize(content, LINES - bar_top - bar_bottom, COLS); 242 | 243 | // set main window text color 244 | if(colors) 245 | wattron(stdscr, COLOR_PAIR(CP_YELLOW)); 246 | 247 | // setup header 248 | if(bar_top) { 249 | line = deck->header; 250 | offset = next_blank(line->text, 0) + 1; 251 | // add text to header 252 | mvwaddwstr(stdscr, 253 | 0, (COLS - line->length + offset) / 2, 254 | &line->text->value[offset]); 255 | } 256 | 257 | // setup footer 258 | if(deck->headers > 1) { 259 | line = deck->header->next; 260 | offset = next_blank(line->text, 0) + 1; 261 | switch(slidenum) { 262 | case 0: // add text to center footer 263 | mvwaddwstr(stdscr, 264 | LINES - 1, (COLS - line->length + offset) / 2, 265 | &line->text->value[offset]); 266 | break; 267 | case 1: 268 | case 2: // add text to left footer 269 | mvwaddwstr(stdscr, 270 | LINES - 1, 3, 271 | &line->text->value[offset]); 272 | break; 273 | } 274 | } 275 | 276 | // add slide number to right footer 277 | switch(slidenum) { 278 | case 1: // show slide number only 279 | mvwprintw(stdscr, 280 | LINES - 1, COLS - int_length(sc) - 3, 281 | "%d", sc); 282 | break; 283 | case 2: // show current slide & number of slides 284 | mvwprintw(stdscr, 285 | LINES - 1, COLS - int_length(deck->slides) - int_length(sc) - 6, 286 | "%d / %d", sc, deck->slides); 287 | break; 288 | } 289 | 290 | // copy changed lines in main window to virtual screen 291 | wnoutrefresh(stdscr); 292 | 293 | line = slide->line; 294 | l = stop = 0; 295 | 296 | // print lines 297 | while(line) { 298 | add_line(content, l + ((LINES - slide->lines_consumed - bar_top - bar_bottom) / 2), 299 | (COLS - max_cols) / 2, line, max_cols, colors, nocodebg); 300 | 301 | // raise stop counter if we pass a line having a stop bit 302 | if(CHECK_BIT(line->bits, IS_STOP)) 303 | stop++; 304 | 305 | l += (line->length / COLS) + 1; 306 | line = line->next; 307 | 308 | // only stop here if we didn't stop here recently 309 | if(stop > slide->stop) 310 | break; 311 | } 312 | 313 | // print pandoc URL references 314 | // only if we already printed all lines of the current slide (or output is stopped) 315 | if(!line || 316 | stop > slide->stop) { 317 | int i, ymax; 318 | getmaxyx( content, ymax, i ); 319 | for (i = 0; i < url_get_amount(); i++) { 320 | mvwprintw(content, ymax - url_get_amount() - 1 + i, 3, 321 | "[%d] ", i); 322 | waddwstr(content, url_get_target(i)); 323 | } 324 | } 325 | 326 | // copy changed lines in content window to virtual screen 327 | wnoutrefresh(content); 328 | 329 | // compare virtual screen to physical screen and does the actual updates 330 | doupdate(); 331 | 332 | // fade in 333 | if(fade) 334 | fade_in(content, trans, colors, invert); 335 | 336 | // re-enable fading after any undefined key press 337 | if(COLORS == 256 && !nofade) 338 | fade = true; 339 | 340 | // wait for user input 341 | c = getch(); 342 | 343 | // evaluate user input 344 | i = 0; 345 | 346 | if (evaluate_binding(prev_slide_binding, c)) { 347 | // show previous slide or stop bit 348 | if(stop > 1 || (stop == 1 && !line)) { 349 | // show current slide again 350 | // but stop one stop bit earlier 351 | slide->stop--; 352 | fade = false; 353 | } else { 354 | if(slide->prev) { 355 | // show previous slide 356 | slide = slide->prev; 357 | sc--; 358 | //stop on first bullet point always 359 | if(slide->stop > 0) 360 | slide->stop = 0; 361 | } else { 362 | // do nothing 363 | fade = false; 364 | } 365 | } 366 | } else if (evaluate_binding(next_slide_binding, c)) { 367 | // show next slide or stop bit 368 | if(stop && line) { 369 | // show current slide again 370 | // but stop one stop bit later (or at end of slide) 371 | slide->stop++; 372 | fade = false; 373 | } else { 374 | if(slide->next) { 375 | // show next slide 376 | slide = slide->next; 377 | sc++; 378 | } else { 379 | // do nothing 380 | fade = false; 381 | } 382 | } 383 | } else if (isdigit(c) && c != '0') { 384 | // show slide n 385 | i = get_slide_number(c); 386 | if(i > 0 && i <= deck->slides) { 387 | while(sc != i) { 388 | // search forward 389 | if(sc < i) { 390 | if(slide->next) { 391 | slide = slide->next; 392 | sc++; 393 | } 394 | // search backward 395 | } else { 396 | if(slide->prev) { 397 | slide = slide->prev; 398 | sc--; 399 | } 400 | } 401 | } 402 | } else { 403 | // disable fading if slide n doesn't exist 404 | fade = false; 405 | } 406 | } else if (evaluate_binding(first_slide_binding, c)) { 407 | // show first slide 408 | slide = deck->slide; 409 | sc = 1; 410 | } else if (evaluate_binding(last_slide_binding, c)) { 411 | // show last slide 412 | for(i = sc; i <= deck->slides; i++) { 413 | if(slide->next) { 414 | slide = slide->next; 415 | sc++; 416 | } 417 | } 418 | } else if (evaluate_binding(reload_binding, c)) { 419 | // reload 420 | if(noreload == 0) { 421 | // reload slide N 422 | reload = sc; 423 | slide = NULL; 424 | } else { 425 | // disable fading if reload is not possible 426 | fade = false; 427 | } 428 | } else if (evaluate_binding(quit_binding, c)) { 429 | // quit 430 | // do not fade out on exit 431 | fade = false; 432 | // do not reload 433 | reload = 0; 434 | slide = NULL; 435 | } else { 436 | // disable fading on undefined key press 437 | fade = false; 438 | } 439 | 440 | // fade out 441 | if(fade) 442 | fade_out(content, trans, colors, invert); 443 | 444 | url_purge(); 445 | } 446 | 447 | // disable ncurses 448 | endwin(); 449 | 450 | // free ncurses memory 451 | delwin(content); 452 | if(reload == 0) 453 | delwin(stdscr); 454 | 455 | // return reload indicator (0 means no reload) 456 | return reload; 457 | } 458 | 459 | void setup_list_strings(void) 460 | { 461 | const char *str; 462 | 463 | /* utf8 can require 6 bytes */ 464 | if ((str = getenv("MDP_LIST_OPEN")) != NULL && strlen(str) <= 4*6) { 465 | list_open1 = list_open2 = list_open3 = str; 466 | } else { 467 | if ((str = getenv("MDP_LIST_OPEN1")) != NULL && strlen(str) <= 4*6) 468 | list_open1 = str; 469 | if ((str = getenv("MDP_LIST_OPEN2")) != NULL && strlen(str) <= 4*6) 470 | list_open2 = str; 471 | if ((str = getenv("MDP_LIST_OPEN3")) != NULL && strlen(str) <= 4*6) 472 | list_open3 = str; 473 | } 474 | if ((str = getenv("MDP_LIST_HEAD")) != NULL && strlen(str) <= 4*6) { 475 | list_head1 = list_head2 = list_head3 = str; 476 | } else { 477 | if ((str = getenv("MDP_LIST_HEAD1")) != NULL && strlen(str) <= 4*6) 478 | list_head1 = str; 479 | if ((str = getenv("MDP_LIST_HEAD2")) != NULL && strlen(str) <= 4*6) 480 | list_head2 = str; 481 | if ((str = getenv("MDP_LIST_HEAD3")) != NULL && strlen(str) <= 4*6) 482 | list_head3 = str; 483 | } 484 | } 485 | 486 | void add_line(WINDOW *window, int y, int x, line_t *line, int max_cols, int colors, int nocodebg) { 487 | 488 | int i; // increment 489 | int offset = 0; // text offset 490 | 491 | // move the cursor in position 492 | wmove(window, y, x); 493 | 494 | if(!line->text->value) { 495 | 496 | // fill rest off line with spaces if we are in a code block 497 | if(CHECK_BIT(line->bits, IS_CODE) && colors) { 498 | if(colors && !nocodebg) 499 | wattron(window, COLOR_PAIR(CP_BLACK)); 500 | for(i = getcurx(window) - x; i < max_cols; i++) 501 | wprintw(window, "%s", " "); 502 | } 503 | 504 | // do nothing 505 | return; 506 | } 507 | 508 | // IS_UNORDERED_LIST_3 509 | if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) { 510 | offset = next_nonblank(line->text, 0); 511 | char prompt[13 * 6]; 512 | int pos = 0, len, cnt; 513 | len = sizeof(prompt) - pos; 514 | cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? list_open1 : " "); 515 | pos += (cnt > len - 1 ? len - 1 : cnt); 516 | len = sizeof(prompt) - pos; 517 | cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)? list_open2 : " "); 518 | pos += (cnt > len - 1 ? len - 1 : cnt); 519 | len = sizeof(prompt) - pos; 520 | 521 | if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) { 522 | snprintf(&prompt[pos], len, "%s", line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_3)? list_open3 : " "); 523 | } else { 524 | snprintf(&prompt[pos], len, "%s", list_head3); 525 | offset += 2; 526 | } 527 | 528 | wprintw(window, 529 | "%s", prompt); 530 | 531 | if(!CHECK_BIT(line->bits, IS_CODE)) 532 | inline_display(window, &line->text->value[offset], colors, nocodebg); 533 | 534 | // IS_UNORDERED_LIST_2 535 | } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) { 536 | offset = next_nonblank(line->text, 0); 537 | char prompt[9 * 6]; 538 | int pos = 0, len, cnt; 539 | len = sizeof(prompt) - pos; 540 | cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? list_open1 : " "); 541 | pos += (cnt > len - 1 ? len - 1 : cnt); 542 | len = sizeof(prompt) - pos; 543 | 544 | if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) { 545 | snprintf(&prompt[pos], len, "%s", line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_2)? list_open2 : " "); 546 | } else { 547 | snprintf(&prompt[pos], len, "%s", list_head2); 548 | offset += 2; 549 | } 550 | 551 | wprintw(window, 552 | "%s", prompt); 553 | 554 | if(!CHECK_BIT(line->bits, IS_CODE)) 555 | inline_display(window, &line->text->value[offset], colors, nocodebg); 556 | 557 | // IS_UNORDERED_LIST_1 558 | } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) { 559 | offset = next_nonblank(line->text, 0); 560 | char prompt[5 * 6]; 561 | 562 | if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) { 563 | strcpy(&prompt[0], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_1)? list_open1 : " "); 564 | } else { 565 | strcpy(&prompt[0], list_head1); 566 | offset += 2; 567 | } 568 | 569 | wprintw(window, 570 | "%s", prompt); 571 | 572 | if(!CHECK_BIT(line->bits, IS_CODE)) 573 | inline_display(window, &line->text->value[offset], colors, nocodebg); 574 | } 575 | 576 | // IS_CODE 577 | if(CHECK_BIT(line->bits, IS_CODE)) { 578 | 579 | if (!CHECK_BIT(line->bits, IS_TILDE_CODE) && 580 | !CHECK_BIT(line->bits, IS_GFM_CODE)) { 581 | // set static offset for code 582 | offset = CODE_INDENT; 583 | } 584 | 585 | // reverse color for code blocks 586 | if(colors && !nocodebg) 587 | wattron(window, COLOR_PAIR(CP_BLACK)); 588 | 589 | // print whole lines 590 | waddwstr(window, &line->text->value[offset]); 591 | } 592 | 593 | if(!CHECK_BIT(line->bits, IS_UNORDERED_LIST_1) && 594 | !CHECK_BIT(line->bits, IS_UNORDERED_LIST_2) && 595 | !CHECK_BIT(line->bits, IS_UNORDERED_LIST_3) && 596 | !CHECK_BIT(line->bits, IS_CODE)) { 597 | 598 | // IS_QUOTE 599 | if(CHECK_BIT(line->bits, IS_QUOTE)) { 600 | while(line->text->value[offset] == '>') { 601 | // print a reverse color block 602 | if(colors) { 603 | wattron(window, COLOR_PAIR(CP_BLACK)); 604 | wprintw(window, "%s", " "); 605 | wattron(window, COLOR_PAIR(CP_WHITE)); 606 | wprintw(window, "%s", " "); 607 | } else { 608 | wprintw(window, "%s", ">"); 609 | } 610 | 611 | // find next quote or break 612 | offset++; 613 | if(line->text->value[offset] == ' ') 614 | offset = next_word(line->text, offset); 615 | } 616 | 617 | inline_display(window, &line->text->value[offset], colors, nocodebg); 618 | } else { 619 | 620 | // IS_CENTER 621 | if(CHECK_BIT(line->bits, IS_CENTER)) { 622 | if(line->length < max_cols) { 623 | wmove(window, y, x + ((max_cols - line->length) / 2)); 624 | } 625 | } 626 | 627 | // IS_H1 || IS_H2 628 | if(CHECK_BIT(line->bits, IS_H1) || CHECK_BIT(line->bits, IS_H2)) { 629 | 630 | // set headline color 631 | if(colors) 632 | wattron(window, COLOR_PAIR(CP_BLUE)); 633 | 634 | // enable underline for H1 635 | if(CHECK_BIT(line->bits, IS_H1)) 636 | wattron(window, A_UNDERLINE); 637 | 638 | // skip hashes 639 | while(line->text->value[offset] == '#') 640 | offset = next_word(line->text, offset); 641 | 642 | // print whole lines 643 | waddwstr(window, &line->text->value[offset]); 644 | 645 | wattroff(window, A_UNDERLINE); 646 | 647 | // no line-wide markdown 648 | } else { 649 | 650 | inline_display(window, &line->text->value[offset], colors, nocodebg); 651 | } 652 | } 653 | } 654 | 655 | // fill rest off line with spaces 656 | // we only need this if the color is inverted (e.g. code-blocks), 657 | // to ensure the background fades too 658 | if(CHECK_BIT(line->bits, IS_CODE)) 659 | for(i = getcurx(window) - x; i < max_cols; i++) 660 | wprintw(window, "%s", " "); 661 | 662 | // reset to default color 663 | if(colors) 664 | wattron(window, COLOR_PAIR(CP_WHITE)); 665 | wattroff(window, A_UNDERLINE); 666 | } 667 | 668 | void inline_display(WINDOW *window, const wchar_t *c, const int colors, int nocodebg) { 669 | const static wchar_t *special = L"\\*_`!["; // list of interpreted chars 670 | const wchar_t *i = c; // iterator 671 | const wchar_t *start_link_name, *start_url; 672 | int length_link_name, url_num; 673 | cstack_t *stack = cstack_init(); 674 | 675 | 676 | // for each char in line 677 | for(; *i; i++) { 678 | 679 | // if char is in special char list 680 | if(wcschr(special, *i)) { 681 | 682 | // closing special char (or second backslash) 683 | // only if not followed by :alnum: 684 | if((stack->top)(stack, *i) && 685 | (!iswalnum(i[1]) || *(i + 1) == L'\0' || *i == L'\\')) { 686 | 687 | switch(*i) { 688 | // print escaped backslash 689 | case L'\\': 690 | waddnwstr(window, i, 1); 691 | break; 692 | // disable highlight 693 | case L'*': 694 | if(colors) 695 | wattron(window, COLOR_PAIR(CP_WHITE)); 696 | break; 697 | // disable underline 698 | case L'_': 699 | wattroff(window, A_UNDERLINE); 700 | break; 701 | // disable inline code 702 | case L'`': 703 | if(colors) 704 | wattron(window, COLOR_PAIR(CP_WHITE)); 705 | break; 706 | } 707 | 708 | // remove top special char from stack 709 | (stack->pop)(stack); 710 | 711 | // treat special as regular char 712 | } else if((stack->top)(stack, L'\\')) { 713 | waddnwstr(window, i, 1); 714 | 715 | // remove backslash from stack 716 | (stack->pop)(stack); 717 | 718 | // opening special char 719 | } else { 720 | 721 | // emphasis or code span can start after new-line or space only 722 | // and of cause after another emphasis markup 723 | //TODO this condition looks ugly 724 | if(i == c || 725 | iswspace(*(i - 1)) || 726 | ((iswspace(*(i - 1)) || *(i - 1) == L'*' || *(i - 1) == L'_') && 727 | ((i - 1) == c || iswspace(*(i - 2)))) || 728 | *i == L'\\') { 729 | 730 | // url in pandoc style 731 | if ((*i == L'[' && wcschr(i, L']')) || 732 | (*i == L'!' && *(i + 1) == L'[' && wcschr(i, L']'))) { 733 | 734 | if (*i == L'!') i++; 735 | 736 | if (wcschr(i, L']')[1] == L'(' && wcschr(i, L')')) { 737 | i++; 738 | 739 | // turn higlighting and underlining on 740 | if (colors) 741 | wattron(window, COLOR_PAIR(CP_BLUE)); 742 | wattron(window, A_UNDERLINE); 743 | 744 | start_link_name = i; 745 | 746 | // print the content of the label 747 | // the label is printed as is 748 | do { 749 | waddnwstr(window, i, 1); 750 | i++; 751 | } while (*i != L']'); 752 | 753 | length_link_name = i - 1 - start_link_name; 754 | 755 | i++; 756 | i++; 757 | 758 | start_url = i; 759 | 760 | while (*i != L')') i++; 761 | 762 | url_num = url_add(start_link_name, length_link_name, start_url, i - start_url, 0, 0); 763 | 764 | wprintw(window, " [%d]", url_num); 765 | 766 | // turn highlighting and underlining off 767 | wattroff(window, A_UNDERLINE); 768 | wattron(window, COLOR_PAIR(CP_WHITE)); 769 | 770 | } else { 771 | wprintw(window, "["); 772 | } 773 | 774 | } else switch(*i) { 775 | // enable highlight 776 | case L'*': 777 | if(colors) 778 | wattron(window, COLOR_PAIR(CP_RED)); 779 | break; 780 | // enable underline 781 | case L'_': 782 | wattron(window, A_UNDERLINE); 783 | break; 784 | // enable inline code 785 | case L'`': 786 | if(colors && !nocodebg) 787 | wattron(window, COLOR_PAIR(CP_BLACK)); 788 | break; 789 | // do nothing for backslashes 790 | } 791 | 792 | // push special char to stack 793 | (stack->push)(stack, *i); 794 | 795 | } else { 796 | waddnwstr(window, i, 1); 797 | } 798 | } 799 | 800 | } else { 801 | // remove backslash from stack 802 | if((stack->top)(stack, L'\\')) 803 | (stack->pop)(stack); 804 | 805 | // print regular char 806 | waddnwstr(window, i, 1); 807 | } 808 | } 809 | 810 | // pop stack until empty to prevent formated trailing spaces 811 | while(!(stack->empty)(stack)) { 812 | switch((stack->pop)(stack)) { 813 | // disable highlight 814 | case L'*': 815 | if(colors) 816 | wattron(window, COLOR_PAIR(CP_WHITE)); 817 | break; 818 | // disable underline 819 | case L'_': 820 | wattroff(window, A_UNDERLINE); 821 | break; 822 | // disable inline code 823 | case L'`': 824 | if(colors) 825 | wattron(window, COLOR_PAIR(CP_WHITE)); 826 | break; 827 | // do nothing for backslashes 828 | } 829 | } 830 | 831 | (stack->delete)(stack); 832 | } 833 | 834 | void fade_out(WINDOW *window, int trans, int colors, int invert) { 835 | int i; // increment 836 | if(colors && COLORS == 256) { 837 | for(i = 22; i >= 0; i--) { 838 | 839 | // dim color pairs 840 | if(invert) { 841 | init_pair(CP_WHITE, white_ramp_invert[i], trans); 842 | init_pair(CP_BLUE, blue_ramp_invert[i], trans); 843 | init_pair(CP_RED, red_ramp_invert[i], trans); 844 | init_pair(CP_BLACK, 15, white_ramp_invert[i]); 845 | } else { 846 | init_pair(CP_WHITE, white_ramp[i], trans); 847 | init_pair(CP_BLUE, blue_ramp[i], trans); 848 | init_pair(CP_RED, red_ramp[i], trans); 849 | init_pair(CP_BLACK, 16, white_ramp[i]); 850 | } 851 | 852 | // refresh virtual screen with new color 853 | wnoutrefresh(window); 854 | 855 | // compare virtual screen to physical screen and does the actual updates 856 | doupdate(); 857 | 858 | // delay for our eyes to recognize the change 859 | usleep(FADE_DELAY); 860 | } 861 | } 862 | } 863 | 864 | void fade_in(WINDOW *window, int trans, int colors, int invert) { 865 | int i; // increment 866 | if(colors && COLORS == 256) { 867 | for(i = 0; i <= 23; i++) { 868 | 869 | // brighten color pairs 870 | if(invert) { 871 | init_pair(CP_WHITE, white_ramp_invert[i], trans); 872 | init_pair(CP_BLUE, blue_ramp_invert[i], trans); 873 | init_pair(CP_RED, red_ramp_invert[i], trans); 874 | init_pair(CP_BLACK, 15, white_ramp_invert[i]); 875 | } else { 876 | init_pair(CP_WHITE, white_ramp[i], trans); 877 | init_pair(CP_BLUE, blue_ramp[i], trans); 878 | init_pair(CP_RED, red_ramp[i], trans); 879 | init_pair(CP_BLACK, 16, white_ramp[i]); 880 | } 881 | 882 | // refresh virtual screen with new color 883 | wnoutrefresh(window); 884 | 885 | // compare virtual screen to physical screen and does the actual updates 886 | doupdate(); 887 | 888 | // delay for our eyes to recognize the change 889 | usleep(FADE_DELAY); 890 | } 891 | } 892 | } 893 | 894 | int int_length (int val) { 895 | int l = 1; 896 | while(val > 9) { 897 | l++; 898 | val /= 10; 899 | } 900 | return l; 901 | } 902 | 903 | int get_slide_number(char init) { 904 | int retval = init - '0'; 905 | int c; 906 | // block for tenths of a second when using getch, ERR if no input 907 | halfdelay(GOTO_SLIDE_DELAY); 908 | while((c = getch()) != ERR) { 909 | if (c < '0' || c > '9') { 910 | retval = -1; 911 | break; 912 | } 913 | retval = (retval * 10) + (c - '0'); 914 | } 915 | nocbreak(); // cancel half delay mode 916 | cbreak(); // go back to cbreak 917 | return retval; 918 | } 919 | 920 | bool evaluate_binding(const int bindings[], char c) { 921 | int binding; 922 | int ind = 0; 923 | while((binding = bindings[ind]) != 0) { 924 | if (c == binding) return true; 925 | ind++; 926 | } 927 | return false; 928 | } 929 | 930 | --------------------------------------------------------------------------------