├── Makefile ├── README.md ├── __init__.py ├── app.py ├── css ├── github.css └── light.css ├── data ├── html └── screenshot.png ├── icons ├── gmarkdown-128.png ├── gmarkdown-48.png └── icon.svg ├── markdown.plugin.m4 ├── parser.py ├── settings.py ├── settings.ui └── win.py /Makefile: -------------------------------------------------------------------------------- 1 | PLUGIN = markdown 2 | 3 | VERSION = 0.2.0 4 | 5 | OUTPUT_SOURCES = app.py __init__.py parser.py settings.py settings.ui win.py css/ data/ 6 | OUTPUT = markdown.plugin 7 | 8 | ICON_128_DIR = ~/.local/share/icons/hicolor/128x128/apps 9 | ICON_48_DIR = ~/.local/share/icons/hicolor/48x48/apps 10 | 11 | all: markdown.plugin 12 | 13 | markdown.plugin: markdown.plugin.m4 Makefile 14 | @ type m4 > /dev/null || ( echo 'm4 is missing and is required to build gedit-markdown. ' ; exit 1 ) 15 | m4 -DVERSION='$(VERSION)' markdown.plugin.m4 > markdown.plugin 16 | 17 | install: 18 | @ [ `whoami` != "root" ] || ( echo 'Run make install as yourself, not as root.' ; exit 1 ) 19 | # make all directories 20 | mkdir -p ~/.local/share/gedit/plugins 21 | mkdir -p ~/.local/share/gedit/plugins/gedit-markdown 22 | mkdir -p $(ICON_128_DIR) 23 | mkdir -p $(ICON_48_DIR) 24 | # copy needed files 25 | cp $(OUTPUT) ~/.local/share/gedit/plugins 26 | cp -R $(OUTPUT_SOURCES) ~/.local/share/gedit/plugins/gedit-markdown 27 | cp -p icons/gmarkdown-128.png $(ICON_128_DIR)/gmarkdown.png 28 | cp -p icons/gmarkdown-48.png $(ICON_48_DIR)/gmarkdown.png 29 | 30 | uninstall: 31 | rm -rf ~/.local/share/gedit/plugins/gedit-markdown/* 32 | rm -f ~/.local/share/gedit/plugins/$(OUTPUT) 33 | rm -f $(ICON_128_DIR)/gmarkdown.png 34 | rm -f $(ICON_48_DIR)/gmarkdown.png 35 | 36 | clean: 37 | rm -f markdown.plugin 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Markdown plugin for Gedit 2 | 3 | *gedit-markdown* is a simple gedit plugin written in python. 4 | 5 | ![Screenshot](data/screenshot.png) 6 | 7 | **Current Features**: 8 | 9 | * As-you-type Markdown preview 10 | * Custom CSS support 11 | * Built-in & Separated window views (highly unstable - disabled by now) 12 | 13 | ## Usage 14 | 15 | Using *gedit-markdown* is easy: open a .md file and preview it. 16 | 17 | A menu entry is found at 18 | 19 | [Gear menu] > Markdown > Preview Markdown files 20 | 21 | Alternativelly, you can use the keyboard shortcut: 22 | 23 | M 24 | 25 | ## Installation 26 | 27 | To install it, simply run as user: 28 | 29 | $ make && make install 30 | 31 | After that, it'll be installed in .local/share/gedit/plugins. 32 | 33 | ## System Requirements 34 | 35 | * Gedit >= 3.11.8 36 | * python-markdown 37 | 38 | ## Development 39 | 40 | You can check on the current development status of the plugin at: 41 | 42 | https://github.com/GeorgesStavracas/gedit-markdown 43 | 44 | Comments, ideas and (most of all) bug reports (and especially patches) are very welcome. 45 | 46 | ## Current status 47 | 48 | - [x] Preview Markdown files 49 | - [x] As-you-type preview 50 | - [x] Support custom CSS 51 | - [ ] Markdown extensions 52 | - [ ] Export as HTML 53 | 54 | ##TODO 55 | * Remove python-markdown dependency (self containable) 56 | * Support Markdown extensions 57 | * Test it to the death 58 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .app import MdAppActivatable 2 | from .win import MdWinActivatable 3 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from gi.repository import GObject, Gio, Gedit 2 | 3 | class MdAppActivatable(GObject.Object, Gedit.AppActivatable): 4 | 5 | app = GObject.property(type=Gedit.App) 6 | 7 | def __init__(self): 8 | GObject.Object.__init__(self) 9 | 10 | def do_activate(self): 11 | self.app.add_accelerator("M", "win.markdown_preview", None) 12 | self.app.add_accelerator("M", "win.markdown_settings", None) 13 | 14 | self.menu_extension = self.extend_menu("tools-section") 15 | item = Gio.MenuItem.new(_("Markdown"), None) 16 | 17 | menu = Gio.Menu.new() 18 | sub1 = Gio.MenuItem.new(_("Preview File"), "win.markdown_preview") 19 | sub2 = Gio.MenuItem.new(_("Settings"), "win.markdown_settings") 20 | 21 | menu.append_item(sub1) 22 | menu.append_item(sub2) 23 | 24 | item.set_submenu(menu) 25 | 26 | self.menu_extension.append_menu_item(item) 27 | 28 | def do_deactivate(self): 29 | self.app.remove_accelerator("win.markdown_preview", None) 30 | self.menu_extension = None 31 | 32 | 33 | -------------------------------------------------------------------------------- /css/github.css: -------------------------------------------------------------------------------- 1 | /* GitHub */ 2 | 3 | /* This file was taken from 4 | * https://git.gnome.org/browse/gnome-builder/tree/src/resources/css/markdown.css?id=bfee1c3cc732f9dd07f4ac32fb324ae0acf9c11a 5 | * 6 | * References for it were found on GNOME Builder source tree. 7 | * 8 | * All credits go to the respective authors. 9 | */ 10 | html { 11 | -webkit-font-smoothing: subpixel-antialiased; 12 | } 13 | 14 | @font-face { 15 | font-family: octicons-anchor; 16 | src: url(data:font/woff;charset=utf-8;base64,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) format('woff'); 17 | } 18 | 19 | .markdown-body { 20 | font-family: sans-serif; 21 | -ms-text-size-adjust: 100%; 22 | -webkit-text-size-adjust: 100%; 23 | color: #333333; 24 | overflow: hidden; 25 | font-family: "Lato", "Open Sans", "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif; 26 | font-size: 16px; 27 | line-height: 1.6; 28 | word-wrap: break-word; 29 | } 30 | 31 | .markdown-body a { 32 | background: transparent; 33 | } 34 | 35 | .markdown-body a:active, 36 | .markdown-body a:hover { 37 | outline: 0; 38 | } 39 | 40 | .markdown-body strong { 41 | font-weight: bold; 42 | } 43 | 44 | .markdown-body h1 { 45 | font-size: 2em; 46 | margin: 0.67em 0; 47 | } 48 | 49 | .markdown-body img { 50 | border: 0; 51 | } 52 | 53 | .markdown-body hr { 54 | -moz-box-sizing: content-box; 55 | box-sizing: content-box; 56 | height: 0; 57 | } 58 | 59 | .markdown-body pre { 60 | overflow: auto; 61 | } 62 | 63 | .markdown-body code, 64 | .markdown-body kbd, 65 | .markdown-body pre { 66 | font-family: monospace, monospace; 67 | font-size: 1em; 68 | } 69 | 70 | .markdown-body input { 71 | color: inherit; 72 | font: inherit; 73 | margin: 0; 74 | } 75 | 76 | .markdown-body html input[disabled] { 77 | cursor: default; 78 | } 79 | 80 | .markdown-body input { 81 | line-height: normal; 82 | } 83 | 84 | .markdown-body input[type="checkbox"] { 85 | box-sizing: border-box; 86 | padding: 0; 87 | } 88 | 89 | .markdown-body table { 90 | border-collapse: collapse; 91 | border-spacing: 0; 92 | } 93 | 94 | .markdown-body td, 95 | .markdown-body th { 96 | padding: 0; 97 | } 98 | 99 | .markdown-body * { 100 | -moz-box-sizing: border-box; 101 | box-sizing: border-box; 102 | } 103 | 104 | .markdown-body input { 105 | font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"; 106 | line-height: 1.4; 107 | } 108 | 109 | .markdown-body a { 110 | color: #4183c4; 111 | text-decoration: none; 112 | } 113 | 114 | .markdown-body a:hover, 115 | .markdown-body a:focus, 116 | .markdown-body a:active { 117 | text-decoration: underline; 118 | } 119 | 120 | .markdown-body hr { 121 | height: 0; 122 | margin: 15px 0; 123 | overflow: hidden; 124 | background: transparent; 125 | border: 0; 126 | border-bottom: 1px solid #ddd; 127 | } 128 | 129 | .markdown-body hr:before, 130 | .markdown-body hr:after { 131 | display: table; 132 | content: " "; 133 | } 134 | 135 | .markdown-body hr:after { 136 | clear: both; 137 | } 138 | 139 | .markdown-body h1, 140 | .markdown-body h2, 141 | .markdown-body h3, 142 | .markdown-body h4, 143 | .markdown-body h5, 144 | .markdown-body h6 { 145 | margin-top: 15px; 146 | margin-bottom: 15px; 147 | line-height: 1.1; 148 | } 149 | 150 | .markdown-body h1 { 151 | font-size: 30px; 152 | } 153 | 154 | .markdown-body h2 { 155 | font-size: 21px; 156 | } 157 | 158 | .markdown-body h3 { 159 | font-size: 16px; 160 | } 161 | 162 | .markdown-body h4 { 163 | font-size: 14px; 164 | } 165 | 166 | .markdown-body h5 { 167 | font-size: 12px; 168 | } 169 | 170 | .markdown-body h6 { 171 | font-size: 11px; 172 | } 173 | 174 | .markdown-body blockquote { 175 | margin: 0; 176 | } 177 | 178 | .markdown-body ul, 179 | .markdown-body ol { 180 | padding: 0; 181 | margin-top: 0; 182 | margin-bottom: 0; 183 | } 184 | 185 | .markdown-body ol ol, 186 | .markdown-body ul ol { 187 | list-style-type: lower-roman; 188 | } 189 | 190 | .markdown-body ul ul ol, 191 | .markdown-body ul ol ol, 192 | .markdown-body ol ul ol, 193 | .markdown-body ol ol ol { 194 | list-style-type: lower-alpha; 195 | } 196 | 197 | .markdown-body dd { 198 | margin-left: 0; 199 | } 200 | 201 | .markdown-body code, 202 | .markdown-body pre { 203 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 204 | font-size: 12px; 205 | } 206 | 207 | .markdown-body pre { 208 | margin-top: 0; 209 | margin-bottom: 0; 210 | } 211 | 212 | .markdown-body kbd { 213 | background-color: #e7e7e7; 214 | background-image: -moz-linear-gradient(#fefefe, #e7e7e7); 215 | background-image: -webkit-linear-gradient(#fefefe, #e7e7e7); 216 | background-image: linear-gradient(#fefefe, #e7e7e7); 217 | background-repeat: repeat-x; 218 | border-radius: 2px; 219 | border: 1px solid #cfcfcf; 220 | color: #000; 221 | padding: 3px 5px; 222 | line-height: 10px; 223 | font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; 224 | display: inline-block; 225 | } 226 | 227 | .markdown-body>*:first-child { 228 | margin-top: 0 !important; 229 | } 230 | 231 | .markdown-body>*:last-child { 232 | margin-bottom: 0 !important; 233 | } 234 | 235 | .markdown-body .anchor { 236 | position: absolute; 237 | top: 0; 238 | bottom: 0; 239 | left: 0; 240 | display: block; 241 | padding-right: 6px; 242 | padding-left: 30px; 243 | margin-left: -30px; 244 | } 245 | 246 | .markdown-body .anchor:focus { 247 | outline: none; 248 | } 249 | 250 | .markdown-body h1, 251 | .markdown-body h2, 252 | .markdown-body h3, 253 | .markdown-body h4, 254 | .markdown-body h5, 255 | .markdown-body h6 { 256 | position: relative; 257 | margin-top: 1em; 258 | margin-bottom: 16px; 259 | font-weight: bold; 260 | line-height: 1.4; 261 | } 262 | 263 | .markdown-body h1 .octicon-link, 264 | .markdown-body h2 .octicon-link, 265 | .markdown-body h3 .octicon-link, 266 | .markdown-body h4 .octicon-link, 267 | .markdown-body h5 .octicon-link, 268 | .markdown-body h6 .octicon-link { 269 | display: none; 270 | color: #000; 271 | vertical-align: middle; 272 | } 273 | 274 | .markdown-body h1:hover .anchor, 275 | .markdown-body h2:hover .anchor, 276 | .markdown-body h3:hover .anchor, 277 | .markdown-body h4:hover .anchor, 278 | .markdown-body h5:hover .anchor, 279 | .markdown-body h6:hover .anchor { 280 | height: 1em; 281 | padding-left: 8px; 282 | margin-left: -30px; 283 | line-height: 1; 284 | text-decoration: none; 285 | } 286 | 287 | .markdown-body h1:hover .anchor .octicon-link, 288 | .markdown-body h2:hover .anchor .octicon-link, 289 | .markdown-body h3:hover .anchor .octicon-link, 290 | .markdown-body h4:hover .anchor .octicon-link, 291 | .markdown-body h5:hover .anchor .octicon-link, 292 | .markdown-body h6:hover .anchor .octicon-link { 293 | display: inline-block; 294 | } 295 | 296 | .markdown-body h1 { 297 | padding-bottom: 0.3em; 298 | font-size: 2.25em; 299 | line-height: 1.2; 300 | border-bottom: 1px solid #eee; 301 | } 302 | 303 | .markdown-body h2 { 304 | padding-bottom: 0.3em; 305 | font-size: 1.75em; 306 | line-height: 1.225; 307 | border-bottom: 1px solid #eee; 308 | } 309 | 310 | .markdown-body h3 { 311 | font-size: 1.5em; 312 | line-height: 1.43; 313 | } 314 | 315 | .markdown-body h4 { 316 | font-size: 1.25em; 317 | } 318 | 319 | .markdown-body h5 { 320 | font-size: 1em; 321 | } 322 | 323 | .markdown-body h6 { 324 | font-size: 1em; 325 | color: #777; 326 | } 327 | 328 | .markdown-body p, 329 | .markdown-body blockquote, 330 | .markdown-body ul, 331 | .markdown-body ol, 332 | .markdown-body dl, 333 | .markdown-body table, 334 | .markdown-body pre { 335 | margin-top: 0; 336 | margin-bottom: 16px; 337 | } 338 | 339 | .markdown-body hr { 340 | height: 4px; 341 | padding: 0; 342 | margin: 16px 0; 343 | background-color: #e7e7e7; 344 | border: 0 none; 345 | } 346 | 347 | .markdown-body ul, 348 | .markdown-body ol { 349 | padding-left: 2em; 350 | } 351 | 352 | .markdown-body ul ul, 353 | .markdown-body ul ol, 354 | .markdown-body ol ol, 355 | .markdown-body ol ul { 356 | margin-top: 0; 357 | margin-bottom: 0; 358 | } 359 | 360 | .markdown-body li>p { 361 | margin-top: 16px; 362 | } 363 | 364 | .markdown-body dl { 365 | padding: 0; 366 | } 367 | 368 | .markdown-body dl dt { 369 | padding: 0; 370 | margin-top: 16px; 371 | font-size: 1em; 372 | font-style: italic; 373 | font-weight: bold; 374 | } 375 | 376 | .markdown-body dl dd { 377 | padding: 0 16px; 378 | margin-bottom: 16px; 379 | } 380 | 381 | .markdown-body blockquote { 382 | padding: 0 15px; 383 | color: #777; 384 | border-left: 4px solid #ddd; 385 | } 386 | 387 | .markdown-body blockquote>:first-child { 388 | margin-top: 0; 389 | } 390 | 391 | .markdown-body blockquote>:last-child { 392 | margin-bottom: 0; 393 | } 394 | 395 | .markdown-body table { 396 | display: block; 397 | width: 100%; 398 | overflow: auto; 399 | word-break: normal; 400 | word-break: keep-all; 401 | } 402 | 403 | .markdown-body table th { 404 | font-weight: bold; 405 | } 406 | 407 | .markdown-body table th, 408 | .markdown-body table td { 409 | padding: 6px 13px; 410 | border: 1px solid #ddd; 411 | } 412 | 413 | .markdown-body table tr { 414 | background-color: #fff; 415 | border-top: 1px solid #ccc; 416 | } 417 | 418 | .markdown-body table tr:nth-child(2n) { 419 | background-color: #f8f8f8; 420 | } 421 | 422 | .markdown-body img { 423 | max-width: 100%; 424 | -moz-box-sizing: border-box; 425 | box-sizing: border-box; 426 | } 427 | 428 | .markdown-body code { 429 | padding: 0; 430 | padding-top: 0.2em; 431 | padding-bottom: 0.2em; 432 | margin: 0; 433 | font-size: 85%; 434 | background-color: rgba(0,0,0,0.04); 435 | border-radius: 3px; 436 | } 437 | 438 | .markdown-body code:before, 439 | .markdown-body code:after { 440 | letter-spacing: -0.2em; 441 | content: "\00a0"; 442 | } 443 | 444 | .markdown-body pre>code { 445 | padding: 0; 446 | margin: 0; 447 | font-size: 100%; 448 | word-break: normal; 449 | white-space: pre; 450 | background: transparent; 451 | border: 0; 452 | } 453 | 454 | .markdown-body .highlight { 455 | margin-bottom: 16px; 456 | } 457 | 458 | .markdown-body .highlight pre, 459 | .markdown-body pre { 460 | padding: 16px; 461 | overflow: auto; 462 | font-size: 85%; 463 | line-height: 1.45; 464 | background-color: #f7f7f7; 465 | border-radius: 3px; 466 | } 467 | 468 | .markdown-body .highlight pre { 469 | margin-bottom: 0; 470 | word-break: normal; 471 | } 472 | 473 | .markdown-body pre { 474 | word-wrap: normal; 475 | } 476 | 477 | .markdown-body pre code { 478 | display: inline; 479 | max-width: initial; 480 | padding: 0; 481 | margin: 0; 482 | overflow: initial; 483 | line-height: inherit; 484 | word-wrap: normal; 485 | background-color: transparent; 486 | border: 0; 487 | } 488 | 489 | .markdown-body pre code:before, 490 | .markdown-body pre code:after { 491 | content: normal; 492 | } 493 | 494 | .markdown-body .highlight { 495 | background: #ffffff; 496 | } 497 | 498 | .markdown-body .highlight .c { 499 | color: #999988; 500 | font-style: italic; 501 | } 502 | 503 | .markdown-body .highlight .err { 504 | color: #a61717; 505 | background-color: #e3d2d2; 506 | } 507 | 508 | .markdown-body .highlight .k { 509 | font-weight: bold; 510 | } 511 | 512 | .markdown-body .highlight .o { 513 | font-weight: bold; 514 | } 515 | 516 | .markdown-body .highlight .cm { 517 | color: #999988; 518 | font-style: italic; 519 | } 520 | 521 | .markdown-body .highlight .cp { 522 | color: #999999; 523 | font-weight: bold; 524 | } 525 | 526 | .markdown-body .highlight .c1 { 527 | color: #999988; 528 | font-style: italic; 529 | } 530 | 531 | .markdown-body .highlight .cs { 532 | color: #999999; 533 | font-weight: bold; 534 | font-style: italic; 535 | } 536 | 537 | .markdown-body .highlight .gd { 538 | color: #000000; 539 | background-color: #ffdddd; 540 | } 541 | 542 | .markdown-body .highlight .gd .x { 543 | color: #000000; 544 | background-color: #ffaaaa; 545 | } 546 | 547 | .markdown-body .highlight .ge { 548 | font-style: italic; 549 | } 550 | 551 | .markdown-body .highlight .gr { 552 | color: #aa0000; 553 | } 554 | 555 | .markdown-body .highlight .gh { 556 | color: #999999; 557 | } 558 | 559 | .markdown-body .highlight .gi { 560 | color: #000000; 561 | background-color: #ddffdd; 562 | } 563 | 564 | .markdown-body .highlight .gi .x { 565 | color: #000000; 566 | background-color: #aaffaa; 567 | } 568 | 569 | .markdown-body .highlight .go { 570 | color: #888888; 571 | } 572 | 573 | .markdown-body .highlight .gp { 574 | color: #555555; 575 | } 576 | 577 | .markdown-body .highlight .gs { 578 | font-weight: bold; 579 | } 580 | 581 | .markdown-body .highlight .gu { 582 | color: #800080; 583 | font-weight: bold; 584 | } 585 | 586 | .markdown-body .highlight .gt { 587 | color: #aa0000; 588 | } 589 | 590 | .markdown-body .highlight .kc { 591 | font-weight: bold; 592 | } 593 | 594 | .markdown-body .highlight .kd { 595 | font-weight: bold; 596 | } 597 | 598 | .markdown-body .highlight .kn { 599 | font-weight: bold; 600 | } 601 | 602 | .markdown-body .highlight .kp { 603 | font-weight: bold; 604 | } 605 | 606 | .markdown-body .highlight .kr { 607 | font-weight: bold; 608 | } 609 | 610 | .markdown-body .highlight .kt { 611 | color: #445588; 612 | font-weight: bold; 613 | } 614 | 615 | .markdown-body .highlight .m { 616 | color: #009999; 617 | } 618 | 619 | .markdown-body .highlight .s { 620 | color: #dd1144; 621 | } 622 | 623 | .markdown-body .highlight .n { 624 | color: #333333; 625 | } 626 | 627 | .markdown-body .highlight .na { 628 | color: teal; 629 | } 630 | 631 | .markdown-body .highlight .nb { 632 | color: #0086b3; 633 | } 634 | 635 | .markdown-body .highlight .nc { 636 | color: #445588; 637 | font-weight: bold; 638 | } 639 | 640 | .markdown-body .highlight .no { 641 | color: teal; 642 | } 643 | 644 | .markdown-body .highlight .ni { 645 | color: purple; 646 | } 647 | 648 | .markdown-body .highlight .ne { 649 | color: #990000; 650 | font-weight: bold; 651 | } 652 | 653 | .markdown-body .highlight .nf { 654 | color: #990000; 655 | font-weight: bold; 656 | } 657 | 658 | .markdown-body .highlight .nn { 659 | color: #555555; 660 | } 661 | 662 | .markdown-body .highlight .nt { 663 | color: navy; 664 | } 665 | 666 | .markdown-body .highlight .nv { 667 | color: teal; 668 | } 669 | 670 | .markdown-body .highlight .ow { 671 | font-weight: bold; 672 | } 673 | 674 | .markdown-body .highlight .w { 675 | color: #bbbbbb; 676 | } 677 | 678 | .markdown-body .highlight .mf { 679 | color: #009999; 680 | } 681 | 682 | .markdown-body .highlight .mh { 683 | color: #009999; 684 | } 685 | 686 | .markdown-body .highlight .mi { 687 | color: #009999; 688 | } 689 | 690 | .markdown-body .highlight .mo { 691 | color: #009999; 692 | } 693 | 694 | .markdown-body .highlight .sb { 695 | color: #dd1144; 696 | } 697 | 698 | .markdown-body .highlight .sc { 699 | color: #dd1144; 700 | } 701 | 702 | .markdown-body .highlight .sd { 703 | color: #dd1144; 704 | } 705 | 706 | .markdown-body .highlight .s2 { 707 | color: #dd1144; 708 | } 709 | 710 | .markdown-body .highlight .se { 711 | color: #dd1144; 712 | } 713 | 714 | .markdown-body .highlight .sh { 715 | color: #dd1144; 716 | } 717 | 718 | .markdown-body .highlight .si { 719 | color: #dd1144; 720 | } 721 | 722 | .markdown-body .highlight .sx { 723 | color: #dd1144; 724 | } 725 | 726 | .markdown-body .highlight .sr { 727 | color: #009926; 728 | } 729 | 730 | .markdown-body .highlight .s1 { 731 | color: #dd1144; 732 | } 733 | 734 | .markdown-body .highlight .ss { 735 | color: #990073; 736 | } 737 | 738 | .markdown-body .highlight .bp { 739 | color: #999999; 740 | } 741 | 742 | .markdown-body .highlight .vc { 743 | color: teal; 744 | } 745 | 746 | .markdown-body .highlight .vg { 747 | color: teal; 748 | } 749 | 750 | .markdown-body .highlight .vi { 751 | color: teal; 752 | } 753 | 754 | .markdown-body .highlight .il { 755 | color: #009999; 756 | } 757 | 758 | .markdown-body .highlight .gc { 759 | color: #999; 760 | background-color: #EAF2F5; 761 | } 762 | 763 | .markdown-body .octicon { 764 | font: normal normal 16px octicons-anchor; 765 | line-height: 1; 766 | display: inline-block; 767 | text-decoration: none; 768 | -webkit-font-smoothing: antialiased; 769 | -moz-osx-font-smoothing: grayscale; 770 | -webkit-user-select: none; 771 | -moz-user-select: none; 772 | -ms-user-select: none; 773 | user-select: none; 774 | } 775 | 776 | .markdown-body .octicon-link:before { 777 | content: '\f05c'; 778 | } 779 | 780 | .markdown-body .task-list-item { 781 | list-style-type: none; 782 | } 783 | 784 | .markdown-body .task-list-item+.task-list-item { 785 | margin-top: 3px; 786 | } 787 | 788 | .markdown-body .task-list-item-checkbox { 789 | margin: 0 4px 0.25em -20px; 790 | vertical-align: middle; 791 | } 792 | -------------------------------------------------------------------------------- /css/light.css: -------------------------------------------------------------------------------- 1 | /* Light */ 2 | * { 3 | font-family: Lato, arial; 4 | } 5 | -------------------------------------------------------------------------------- /data/html: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |
10 | %s 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /data/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgesStavracas/gedit-markdown/2ccf077dee16fd9ff89c4a30fe0d7f1dab4b9b6b/data/screenshot.png -------------------------------------------------------------------------------- /icons/gmarkdown-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgesStavracas/gedit-markdown/2ccf077dee16fd9ff89c4a30fe0d7f1dab4b9b6b/icons/gmarkdown-128.png -------------------------------------------------------------------------------- /icons/gmarkdown-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgesStavracas/gedit-markdown/2ccf077dee16fd9ff89c4a30fe0d7f1dab4b9b6b/icons/gmarkdown-48.png -------------------------------------------------------------------------------- /icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 27 | 32 | 38 | 43 | 48 | 54 | 55 | 56 | 74 | 76 | 77 | 79 | image/svg+xml 80 | 82 | 83 | 84 | 85 | 86 | 91 | 100 | 104 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /markdown.plugin.m4: -------------------------------------------------------------------------------- 1 | [Plugin] 2 | Module=gedit-markdown 3 | IAge=3 4 | Name=Markdown 5 | Version=VERSION 6 | Loader=python3 7 | Description=Preview Markdown files 8 | Authors=Georges Basile Stavracas Neto 9 | Copyright=Copyright © 2014 Georges Basile Stavracas Neto 10 | Icon=gmarkdown 11 | Website=http://www.gedit.org 12 | -------------------------------------------------------------------------------- /parser.py: -------------------------------------------------------------------------------- 1 | from gi.repository import GLib, GObject, Gio, WebKit2, JavaScriptCore 2 | import markdown 3 | 4 | class MdParser(GObject.Object): 5 | 6 | get_y_scroll = "document.pageYOffset"; 7 | 8 | def __init__(self): 9 | GObject.Object.__init__(self) 10 | self.webview = WebKit2.WebView() 11 | 12 | def parse(self, text="", css="", html="%d%s%s", margin=0, base_uri="file://"): 13 | parsed_md = markdown.markdown(text) 14 | 15 | page = html % (margin, css, parsed_md) 16 | 17 | # Async function 18 | #scroll = self.get_scroll() 19 | self.webview.load_html(page, base_uri) 20 | 21 | def get_scroll(self): 22 | self.webview.run_javascript(self.get_y_scroll, None, self.get_scroll_finished, None) 23 | 24 | def get_scroll_finished(self, unused_webview, parent_result, unused_data=None): 25 | result = self.webview.run_javascript_finish(parent_result) 26 | 27 | if result is None: 28 | return 29 | 30 | val = result.get_global_context() 31 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | from gi.repository import Gtk, GLib, GObject, Gio, WebKit2 2 | import os 3 | 4 | # Single instance 5 | settings = None 6 | 7 | # Auxiliary methods 8 | def plugin_dir(): 9 | user_data_dir = os.path.join(GLib.get_user_data_dir(), "gedit") 10 | user_plugin_dir = os.path.join(user_data_dir, "plugins") 11 | return os.path.join(user_plugin_dir, "gedit-markdown") 12 | 13 | def css_dir(): 14 | return os.path.join(plugin_dir(), "css") 15 | 16 | def css_file(var): 17 | return os.path.join(css_dir(), var) 18 | 19 | def data_file(var): 20 | return os.path.join(plugin_dir(), var) 21 | 22 | def config_file(): 23 | path = os.path.join(GLib.get_user_config_dir(), "gedit-markdown") 24 | return os.path.join(path, "settings.conf") 25 | 26 | def html_file(): 27 | path = os.path.join(plugin_dir(), "data") 28 | return os.path.join(path, "html") 29 | 30 | def is_css(f): 31 | return f.endswith(".css") 32 | 33 | def check_config_file(): 34 | path = os.path.join(GLib.get_user_config_dir(), "gedit-markdown") 35 | 36 | if not os.path.exists(path): 37 | os.mkdir(path) 38 | 39 | if not os.path.exists(config_file()): 40 | # Create the config file when it doesn't exists 41 | file = open(config_file(), "w+") 42 | file.close() 43 | 44 | return False 45 | 46 | return True 47 | 48 | def get_stylesheet_title(css): 49 | f = open(css, "r") 50 | first_line = f.readline() 51 | return first_line.replace("/*","").replace("*/","").strip() 52 | 53 | # MdSettings 54 | class MdSettings(GObject.Object): 55 | 56 | __gsignals__ = { 57 | "css-file-selected": (GObject.SIGNAL_RUN_FIRST, None, ()), 58 | "show-preview-window": (GObject.SIGNAL_RUN_FIRST, None, ()), 59 | "margin-changed": (GObject.SIGNAL_RUN_FIRST, None, ()) 60 | } 61 | 62 | def __init__(self): 63 | GObject.Object.__init__(self) 64 | 65 | # Builder 66 | self.ui = Gtk.Builder() 67 | self.ui.add_from_file(data_file("settings.ui")) 68 | 69 | # Dialog 70 | self.dialog = self.ui.get_object('settings_dialog') 71 | self.dialog.set_modal(True) 72 | 73 | self.listbox = self.ui.get_object('listbox') 74 | self.listbox.connect("row-activated", self.on_listbox_row_activated) 75 | 76 | # Preview window 77 | self.preview_window = self.ui.get_object('preview_window') 78 | 79 | self.preview_window_scroll = self.ui.get_object('preview_scroll') 80 | 81 | # CSS files 82 | self.css_files = [f for f in os.listdir(css_dir()) if is_css(os.path.join(css_dir(), f))] 83 | self.update_css_list() 84 | 85 | # Spin 86 | self.margin = self.ui.get_object('margin_spin') 87 | 88 | # Buttons 89 | self.save_button = self.ui.get_object('save_button') 90 | self.cancel_button = self.ui.get_object('cancel_button') 91 | 92 | # Switches 93 | self.auto_switch = self.ui.get_object('auto_switch') 94 | self.preview_switch = self.ui.get_object('attached_switch') 95 | 96 | # Signals 97 | self.connect_signals() 98 | 99 | # Settings file 100 | file_exists = check_config_file() 101 | 102 | self.key_file = GLib.KeyFile() 103 | self.key_file.load_from_file(config_file(), GLib.KeyFileFlags.NONE) 104 | 105 | # Update dialog preferences 106 | if file_exists: 107 | self.auto_switch.set_active(self.key_file.get_boolean("Config", "AutoPreview")) 108 | self.preview_switch.set_active(self.key_file.get_boolean("Config", "ShowPreviewWindow")) 109 | self.margin.set_value(self.key_file.get_integer("Config", "Margin")) 110 | else: 111 | self.key_file.set_boolean("Config", "AutoPreview", False) 112 | self.key_file.set_boolean("Config", "ShowPreviewWindow", False) 113 | self.key_file.set_integer("Config", "Margin", self.margin.get_value_as_int()) 114 | self.key_file.set_string("Config", "CSS", css_file("github.css")) 115 | self.key_file.save_to_file(config_file()) 116 | 117 | def connect_signals(self): 118 | self.save_button.connect("clicked", self.on_dialog_button_clicked) 119 | self.cancel_button.connect("clicked", self.on_dialog_button_clicked) 120 | self.preview_switch.connect("notify::active", self.on_preview_switch_activate) 121 | self.margin.connect("changed", self.on_margin_changed) 122 | 123 | def show_settings_window(self, parent): 124 | self.dialog.run() 125 | 126 | def show_preview_window(self): 127 | self.preview_window.show() 128 | 129 | def on_dialog_button_clicked(self, button): 130 | # Save config contents 131 | if button is self.save_button: 132 | self.key_file.set_boolean("Config", "AutoPreview", self.auto_switch.get_active()) 133 | self.key_file.set_boolean("Config", "ShowPreviewWindow", self.preview_switch.get_active()) 134 | self.key_file.set_integer("Config", "Margin", self.margin.get_value_as_int()) 135 | self.key_file.save_to_file(config_file()) 136 | 137 | self.dialog.hide(); 138 | 139 | def update_css_list(self): 140 | 141 | for css_file in self.css_files: 142 | 143 | css_entry = CssListItem() 144 | css_entry.name.set_text(get_stylesheet_title(os.path.join(css_dir(), css_file))) 145 | css_entry.filename.set_text(css_file) 146 | 147 | css_entry.show() 148 | 149 | self.listbox.add(css_entry) 150 | 151 | def on_listbox_row_activated(self, unused_widget, row): 152 | self.key_file.set_string("Config", "CSS", css_file(row.filename.get_text())) 153 | self.emit("css-file-selected") 154 | 155 | # Get AutoPreview option 156 | def get_autopreview(self): 157 | return self.auto_switch.get_active() 158 | 159 | def get_show_preview_window(self): 160 | return self.preview_switch.get_active() 161 | 162 | def get_window_webview(self): 163 | return self.webview 164 | 165 | # Get base html content 166 | def get_html(self): 167 | f = open(html_file(), "r") 168 | html_content = f.read() 169 | f.close() 170 | 171 | return html_content 172 | 173 | def on_preview_switch_activate(self, unused_switch, unused_data): 174 | self.emit("show-preview-window") 175 | 176 | def on_margin_changed(self, unused_switch): 177 | self.emit("margin-changed") 178 | 179 | # Get selected CSS content 180 | def get_css(self): 181 | css_f = self.key_file.get_string("Config", "CSS") 182 | 183 | if css_f is None: 184 | return "" 185 | 186 | path = css_file(css_f) 187 | 188 | css = open(path, "r") 189 | css_content = css.read() 190 | css.close() 191 | 192 | return css_content 193 | 194 | 195 | # CssList item entry 196 | class CssListItem(Gtk.ListBoxRow): 197 | 198 | def __init__(self): 199 | Gtk.ListBoxRow.__init__(self) 200 | 201 | self.ui = Gtk.Builder() 202 | self.ui.add_from_file(data_file("settings.ui")) 203 | 204 | self.add (self.ui.get_object('listbox_child')) 205 | self.name = self.ui.get_object('name_label') 206 | self.filename = self.ui.get_object('filename_label') 207 | 208 | 209 | -------------------------------------------------------------------------------- /settings.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 100 7 | 1 8 | 10 9 | 10 | 11 | 600 12 | 450 13 | False 14 | center-on-parent 15 | 400 16 | 300 17 | gmarkdown 18 | dialog 19 | 20 | 21 | False 22 | vertical 23 | 2 24 | 25 | 26 | False 27 | end 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | False 37 | True 38 | end 39 | 0 40 | 41 | 42 | 43 | 44 | True 45 | False 46 | 12 47 | 12 48 | 12 49 | 12 50 | True 51 | True 52 | 12 53 | 54 | 55 | True 56 | False 57 | 0 58 | Stylesheet file 59 | 60 | 61 | 0 62 | 3 63 | 2 64 | 65 | 66 | 67 | 68 | True 69 | True 70 | True 71 | True 72 | in 73 | 74 | 75 | True 76 | False 77 | 78 | 79 | True 80 | False 81 | 82 | 83 | 84 | 85 | 86 | 87 | 0 88 | 4 89 | 2 90 | 91 | 92 | 93 | 94 | True 95 | False 96 | True 97 | 0 98 | Automatically preview Markdown files 99 | end 100 | 101 | 102 | 0 103 | 0 104 | 105 | 106 | 107 | 108 | True 109 | True 110 | end 111 | 112 | 113 | 1 114 | 0 115 | 116 | 117 | 118 | 119 | True 120 | False 121 | 0 122 | Preview in another window (highly unstable) 123 | 124 | 125 | 0 126 | 1 127 | 128 | 129 | 130 | 131 | True 132 | False 133 | True 134 | 135 | 136 | 1 137 | 1 138 | 139 | 140 | 141 | 142 | True 143 | False 144 | 0 145 | Margin 146 | 147 | 148 | 0 149 | 2 150 | 151 | 152 | 153 | 154 | True 155 | True 156 | end 157 | adjustment1 158 | 10 159 | 160 | 161 | 1 162 | 2 163 | 164 | 165 | 166 | 167 | True 168 | True 169 | 1 170 | 171 | 172 | 173 | 174 | 175 | 176 | True 177 | False 178 | Markdown Settings 179 | 180 | 181 | Cancel 182 | True 183 | True 184 | True 185 | 186 | 187 | 188 | 189 | Save 190 | True 191 | True 192 | True 193 | 196 | 197 | 198 | end 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | text/css 207 | 208 | 209 | 210 | True 211 | False 212 | 12 213 | 12 214 | 5 215 | 5 216 | 12 217 | 218 | 219 | True 220 | False 221 | True 222 | 0 223 | 224 | 225 | 226 | 227 | 228 | 229 | 0 230 | 1 231 | 232 | 233 | 234 | 235 | True 236 | False 237 | True 238 | 0 239 | 240 | 241 | 0 242 | 0 243 | 244 | 245 | 246 | 247 | False 248 | 400 249 | 300 250 | 251 | 252 | True 253 | False 254 | True 255 | True 256 | 257 | 258 | 259 | 260 | True 261 | False 262 | Markdown Preview 263 | True 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /win.py: -------------------------------------------------------------------------------- 1 | from gi.repository import GLib, GObject, Gio, Gtk, Gedit, WebKit2 2 | from .settings import MdSettings, settings 3 | from .parser import MdParser 4 | import markdown 5 | 6 | class MdWinActivatable(GObject.Object, Gedit.WindowActivatable): 7 | 8 | window = GObject.property(type=Gedit.Window) 9 | 10 | ignored_states = { 11 | 0, 12 | Gedit.WindowState.ERROR, 13 | Gedit.WindowState.PRINTING, 14 | Gedit.WindowState.SAVING 15 | } 16 | 17 | def do_activate(self): 18 | # Window signal 19 | self.window.connect("active-tab-changed", self.on_active_tab_changed) 20 | 21 | # Settings 22 | global settings 23 | 24 | if settings is None: 25 | settings = MdSettings() 26 | 27 | self.settings = settings 28 | self.settings.connect("css-file-selected", self.on_css_selected) 29 | self.settings.connect("margin-changed", self.on_margin_changed) 30 | #self.settings.connect("show-preview-window", self.on_show_preview_window) 31 | 32 | # Parser 33 | self.parser = MdParser() 34 | self.parser.webview.show() 35 | 36 | self.window.get_bottom_panel().add_titled(self.parser.webview, "markdown_preview", _("Markdown")) 37 | 38 | # Actions 39 | self.preview_action = Gio.SimpleAction.new("markdown_preview", None) 40 | self.preview_action.connect("activate", self.do_markdown) 41 | 42 | self.settings_action = Gio.SimpleAction.new("markdown_settings", None) 43 | self.settings_action.connect("activate", self.do_show_settings) 44 | 45 | self.window.add_action(self.preview_action) 46 | self.window.add_action(self.settings_action) 47 | 48 | # Document monitor 49 | self.current_is_md = False 50 | self.current_document = None 51 | 52 | 53 | def do_deactivate(self): 54 | self.window.remove_action("markdown_preview") 55 | self.window.remove_action("markdown_settings") 56 | self.window.get_bottom_panel().remove(self.parser.webview) 57 | 58 | def do_update_state(self): 59 | # We don't want to show it every single time an event is fired 60 | if self.window.get_property("state") not in self.ignored_states: 61 | self.update_status() 62 | 63 | def on_active_tab_changed(self, unused_window, tab): 64 | self.update_status() 65 | 66 | # Parse the active-tab document text 67 | def do_markdown(self, unused_a=None, unused_b=None): 68 | self.parse_markdown() 69 | self.toggle_previewer() 70 | 71 | 72 | # Parse the current markdown file 73 | def parse_markdown(self, unused_a=None, unused_b=None): 74 | doc = self.window.get_active_document() 75 | 76 | if doc is None: 77 | return 78 | 79 | if not self.is_markdown_file(doc.get_short_name_for_display()): 80 | return 81 | 82 | margin = settings.margin.get_value_as_int() 83 | 84 | self.parser.parse(doc.get_property("text"), settings.get_css(), settings.get_html(), margin) 85 | 86 | # Enable/disable menu entries 87 | def update_status(self): 88 | # Update the Gear menu entries 89 | active_doc = self.window.get_active_document() 90 | 91 | # Make sure there really is an active document opened 92 | if active_doc is None: 93 | self.preview_action.set_enabled(False) 94 | self.toggle_previewer(False) 95 | return 96 | 97 | filename = active_doc.get_short_name_for_display() 98 | self.current_is_md = self.is_markdown_file(filename) 99 | self.preview_action.set_enabled(self.current_is_md) 100 | 101 | if settings.get_autopreview(): 102 | self.parse_markdown() 103 | self.toggle_previewer(self.current_is_md) 104 | 105 | if self.current_is_md and not self.settings.get_show_preview_window(): 106 | self.window.get_bottom_panel().set_visible_child(self.parser.webview) 107 | 108 | # Update connection 109 | self.update_document_connection() 110 | 111 | # Connect Gtk.TextBuffer::changed signal 112 | def update_document_connection(self): 113 | active_doc = self.window.get_active_document() 114 | 115 | if self.current_document is not None: 116 | 117 | if self.connection_id >= 0: 118 | self.current_document.disconnect(self.connection_id) 119 | 120 | self.connection_id = -1 121 | 122 | if self.current_is_md: 123 | self.current_document = active_doc 124 | self.connection_id = active_doc.connect ("changed", self.on_text_changed) 125 | 126 | # Toggle the preview window, or set a given visibility 127 | def toggle_previewer(self, show=None): 128 | widget = None 129 | 130 | if self.settings.get_show_preview_window(): 131 | widget = self.settings.preview_window 132 | else: 133 | widget = self.window.get_bottom_panel() 134 | 135 | if show is None: 136 | widget.set_visible(not widget.get_visible()) 137 | else: 138 | widget.set_visible(show) 139 | 140 | 141 | # Check if it's a Markdown file 142 | def is_markdown_file(self, filename): 143 | return filename.endswith(".md") 144 | 145 | def should_preview(self): 146 | active_doc = self.window.get_active_document() 147 | 148 | if active_doc is None: 149 | return False 150 | 151 | filename = active_doc.get_short_name_for_display() 152 | 153 | return self.is_markdown_file(filename) 154 | 155 | 156 | def do_show_settings(self, unused_a, unused_b): 157 | self.settings.show_settings_window(self.window) 158 | 159 | # Update Markdown view when editing an markdown file 160 | def on_text_changed(self, unused_widget): 161 | 162 | if self.current_is_md is False: 163 | self.window.get_bottom_panel().hide() 164 | return 165 | 166 | if self.settings.get_autopreview() is True: 167 | self.parse_markdown() 168 | 169 | # Change CSS automatically when it's selected 170 | def on_css_selected(self, unused_widget): 171 | self.parse_markdown() 172 | 173 | def on_margin_changed(self, unused_data): 174 | self.parse_markdown() 175 | 176 | def on_show_preview_window(self, unused_widget): 177 | # Remove from the current container 178 | self.parser.webview.get_parent().remove(self.parser.webview) 179 | 180 | if self.settings.get_show_preview_window(): 181 | self.window.get_bottom_panel().set_visible(False) 182 | 183 | self.settings.preview_window_scroll.add(self.parser.webview) 184 | self.settings.preview_window.set_visible(self.should_preview()) 185 | self.settings.preview_window.present() 186 | 187 | else: 188 | self.settings.preview_window.set_visible(False) 189 | self.window.get_bottom_panel().set_visible(self.should_preview()) 190 | 191 | self.update_status() 192 | 193 | --------------------------------------------------------------------------------