Compare commits

...

2 Commits

Author SHA1 Message Date
TEC b437178c64
Old uncommited (why?) posts 2024-01-11 15:02:16 +08:00
TEC b67cc0a39c
Update the publish script and config 2024-01-11 14:58:15 +08:00
16 changed files with 1045 additions and 221 deletions

View File

@ -1,4 +0,0 @@
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* 404.html [L]

BIN
assets/figures/engraved-faces-multitheme.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/figures/engraved-faces-sample.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/figures/org-fold-perf-shifttab-contents.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/figures/org-fold-perf-shifttab-showall.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/figures/org-markup-to-confluence.svg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/figures/org-modern-readme-demo.gif (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -5,7 +5,7 @@
</a>
To the extent possible under law,
<a rel="dct:publisher"
href="https://blog.tecosaur.com/">
href="https://blog.tecosaur.net/">
<span property="dct:title">TEC</span></a>
has waived all copyright and related or neighboring rights to
<span property="dct:title" style="font-style:italic">This Month in Org</span>.

View File

@ -1,6 +1,6 @@
<header>
<h4><a href="https://orgmode.org" class="orgicorn"><img src="org-icon-monochrome.svg" class="invertible"></a>
<a href="https://blog.tecosaur.com/tmio/" class="title">
<a href="https://blog.tecosaur.net/tmio/" class="title">
This Month in Org</a>
<a href="rss.xml" title="RSS Feed" type="application/rss+xml" class="rss">
<img src="rss.svg" alt="RSS icon" />

View File

@ -75,45 +75,87 @@ summary {
display: list-item; }
/*! end normalise.css */
#page {
--accent: #002642;
--accent-dark: #00151c;
--code-foreground: #2c3e50;
--code-background: #efefef;
--code-func: #6c3163;
--code-const: #4e3163;
--code-regex: #655370;
--back-white: #fff;
--back-light: #f0eeed;
--back-medium: #c8c8c8;
--text-light: #7b7b7b;
--text-gray: #444;
--text-medium: #222;
--text-dark: #000;
--switch-icon: "🌚";
--switch-shadow-color: #373d4e;
--switch-text: "dark mode?"; }
@media (prefers-color-scheme: light) {
#page {
--accent: #002642;
--accent-dark: #00151c;
--code-foreground: #2c3e50;
--code-background: #f5f5f5;
--code-func: #6c3163;
--code-const: #4e3163;
--code-regex: #655370;
--back-white: #fff;
--back-light: #f0eeed;
--back-medium: #c8c8c8;
--text-light: #7b7b7b;
--text-gray: #444;
--text-medium: #222;
--text-dark: #000;
--switch-icon: "🌚";
--switch-shadow-color: #373d4e;
--switch-text: "dark mode?"; }
#theme-switch:checked ~ #page {
--accent: #002642;
--accent-dark: #daf1ff;
--code-foreground: #a1a8ae;
--code-background: #222;
--code-func: #bd56ad;
--code-const: #8755ab;
--code-regex: #a184b3;
--back-white: #000;
--back-light: #181818;
--back-medium: #444;
--text-light: #7b7b7b;
--text-gray: #c8c8c8;
--text-medium: #ddd;
--text-dark: #efefef;
--switch-icon: "🌝";
--switch-shadow-color: #fce477;
--switch-text: "light mode?"; }
#theme-switch:checked ~ #page img.invertible, #theme-switch:checked ~ #page object[type="image/svg+xml"].invertible, #theme-switch:checked ~ #page img.org-latex {
filter: invert(90.5%) hue-rotate(180deg) sepia(1%); } }
#theme-switch:checked ~ #page {
--accent: #002642;
--accent-dark: #daf1ff;
--code-foreground: #a1a8ae;
--code-background: #222;
--code-func: #bd56ad;
--code-const: #8755ab;
--code-regex: #a184b3;
--back-white: #000;
--back-light: #181818;
--back-medium: #444;
--text-light: #7b7b7b;
--text-gray: #c8c8c8;
--text-medium: #ddd;
--text-dark: #efefef;
--switch-icon: "🌝";
--switch-shadow-color: #fce477;
--switch-text: "light mode?"; }
#theme-switch:checked ~ #page img.invertible, #theme-switch:checked ~ #page object.invertible[type="image/svg+xml"] {
filter: invert(90%) hue-rotate(180deg) sepia(1%); }
@media (prefers-color-scheme: dark) {
#theme-switch:checked ~ #page {
--accent: #002642;
--accent-dark: #00151c;
--code-foreground: #2c3e50;
--code-background: #f5f5f5;
--code-func: #6c3163;
--code-const: #4e3163;
--code-regex: #655370;
--back-white: #fff;
--back-light: #f0eeed;
--back-medium: #c8c8c8;
--text-light: #7b7b7b;
--text-gray: #444;
--text-medium: #222;
--text-dark: #000;
--switch-icon: "🌚";
--switch-shadow-color: #373d4e;
--switch-text: "dark mode?"; }
#theme-switch:checked ~ #page img.invertible, #theme-switch:checked ~ #page object[type="image/svg+xml"].invertible, #theme-switch:checked ~ #page img.org-latex {
filter: invert(7%) sepia(4%); }
#page {
--accent: #002642;
--accent-dark: #daf1ff;
--code-foreground: #a1a8ae;
--code-background: #222;
--code-func: #bd56ad;
--code-const: #8755ab;
--code-regex: #a184b3;
--back-white: #000;
--back-light: #181818;
--back-medium: #444;
--text-light: #7b7b7b;
--text-gray: #c8c8c8;
--text-medium: #ddd;
--text-dark: #efefef;
--switch-icon: "🌝";
--switch-shadow-color: #fce477;
--switch-text: "light mode?"; }
#page img.invertible, #page object[type="image/svg+xml"].invertible, #page img.org-latex {
filter: invert(90.5%) hue-rotate(180deg) sepia(1%); } }
#theme-switch {
display: none; }
@ -356,23 +398,22 @@ aside {
border-radius: 5px;
font-family: "Open Sans", sans;
font-weight: normal;
font-size: 15px;
font-size: 14px;
line-height: 1.6;
color: var(--text-gray);
margin: 20px 0;
padding: 5px 20px 10px;
line-height: 1.6 !important; }
aside p {
font-family: "Open Sans", sans;
font-weight: normal;
font-size: 15px;
line-height: 1.6;
color: var(--text-gray);
line-height: 1.6 !important; }
aside pre {
font-size: 12px;
border: none;
padding-left: 0; }
aside :first-child {
margin-top: 0.5em; }
aside :last-child {
margin-bottom: 0.5em; }
@media (min-width: 1280px) {
aside {
@ -394,7 +435,9 @@ blockquote {
padding-left: 15px; }
blockquote p {
display: inline;
font-size: 13px; }
font-size: 14px; }
blockquote ol, blockquote ul {
margin-left: 1em; }
blockquote footer {
font-family: "Open Sans", sans;
font-weight: normal;
@ -528,6 +571,37 @@ div.error::before {
line-height: 1.1;
text-align: center; }
div.notes {
background: rgba(113, 90, 177, 0.15);
border-left: 4px solid rgba(113, 90, 177, 0.45);
margin: 1.8rem 0 1.25rem 15px;
padding: 0.8em;
line-height: 1.4;
text-align: center;
position: relative;
clear: both; }
div.notes p {
margin: 0; }
div.notes::before {
content: "✎";
color: var(--back-white);
background: rgba(113, 90, 177, 0.8);
align-items: flex-end;
top: -1rem;
font-weight: 700;
font-size: 1.4rem;
-webkit-clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
width: 32px;
height: 32px;
display: inline-flex;
justify-content: center;
position: absolute;
left: -1.2rem;
line-height: 1.3;
text-align: center; }
.org-ref-bib-h1 {
margin-top: 70px;
padding-top: 30px;
@ -606,8 +680,8 @@ pre.src:empty {
code {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
color: var(--code-foreground);
font-size: 15px;
padding: 0 5px; }
font-size: 14px;
padding: 0 2px; }
kbd {
display: inline-block;
@ -633,7 +707,7 @@ details.code {
position: relative; }
details.code summary {
position: relative;
left: -3px;
left: -2.5px;
padding-left: 10px;
padding-bottom: 4px;
margin-left: -10px;
@ -1187,7 +1261,8 @@ hr {
margin-top: 40px; }
a {
color: inherit; }
color: inherit;
position: relative; }
figcaption a,
p a,
@ -1195,12 +1270,12 @@ p a,
table a,
li a,
dl a {
background-image: linear-gradient(var(--text-dark), var(--text-dark));
background-size: 1px 1px;
background-repeat: repeat-x;
background-position: 0% 95%;
border-radius: 1px;
text-decoration: none; }
text-decoration-line: underline;
text-decoration-style: dotted;
text-decoration-color: var(--text-light);
text-decoration-thickness: .1em;
text-underline-offset: 1.5px;
border-radius: 1px; }
figcaption a::selection,
p a::selection,
.page-tags a::selection,
@ -1252,31 +1327,35 @@ dl a {
dl a:hover {
opacity: 0.9;
filter: drop-shadow(1px 1px 1px var(--back-white));
background-image: linear-gradient(var(--text-dark), var(--text-dark));
background-size: 1px 1px;
background-repeat: repeat-x;
background-position: 0% 95%;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg id='squiggle-link' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:ev='http://www.w3.org/2001/xml-events' viewBox='0 0 20 4'%3E%3Cstyle type='text/css'%3E.squiggle{animation:shift .3s linear infinite;}@keyframes shift {from {transform:translateX(0);}to {transform:translateX(-20px);}}%3C/style%3E%3Cpath fill='none' stroke='%23000000' stroke-width='2' class='squiggle' d='M0,3.5 c 5,0,5,-3,10,-3 s 5,3,10,3 c 5,0,5,-3,10,-3 s 5,3,10,3'/%3E%3C/svg%3E") !important;
background-position: 0% 100%;
background-size: 0.8em auto;
text-decoration: none; }
figcaption a:hover::selection,
p a:hover::selection,
.page-tags a:hover::selection,
table a:hover::selection,
li a:hover::selection,
dl a:hover::selection {
background: var(--back-white); }
figcaption a:hover::before,
p a:hover::before,
.page-tags a:hover::before,
table a:hover::before,
li a:hover::before,
dl a:hover::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
mask-image: url("data:image/svg+xml;charset=utf8,%3Csvg id='squiggle-link' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:ev='http://www.w3.org/2001/xml-events' viewBox='0 0 20 4'%3E%3Cstyle type='text/css'%3E.squiggle{animation:shift .3s linear infinite;}@keyframes shift {from {transform:translateX(0);}to {transform:translateX(-20px);}}%3C/style%3E%3Cpath fill='none' stroke='%23000000' stroke-width='2' class='squiggle' d='M0,3.5 c 5,0,5,-3,10,-3 s 5,3,10,3 c 5,0,5,-3,10,-3 s 5,3,10,3'/%3E%3C/svg%3E") !important;
mask-position: 0% 100%;
mask-size: 0.8em auto;
mask-repeat: repeat-x;
background: var(--text-light); }
figcaption a[href^="#"],
p a[href^="#"],
.page-tags a[href^="#"],
table a[href^="#"],
li a[href^="#"],
dl a[href^="#"] {
background-image: linear-gradient(var(--text-gray), var(--text-gray));
background-size: 1px 1px;
background-repeat: repeat-x;
background-position: 0% 95%; }
text-decoration-line: underline;
text-decoration-style: dotted;
text-decoration-color: var(--text-light);
text-decoration-thickness: .1em;
text-underline-offset: 1.5px; }
figcaption a[href^="#"]::selection,
p a[href^="#"]::selection,
.page-tags a[href^="#"]::selection,
@ -1292,10 +1371,11 @@ dl a {
dl a[href^="#"]:hover {
opacity: 0.9;
filter: drop-shadow(1px 1px 1px var(--back-white));
background-image: linear-gradient(var(--text-medium), var(--text-medium));
background-size: 1px 1px;
background-repeat: repeat-x;
background-position: 0% 95%; }
text-decoration-line: underline;
text-decoration-style: dotted;
text-decoration-color: var(--text-light);
text-decoration-thickness: .1em;
text-underline-offset: 1.5px; }
figcaption a[href^="#"]:hover::selection,
p a[href^="#"]:hover::selection,
.page-tags a[href^="#"]:hover::selection,
@ -1312,7 +1392,26 @@ dl a {
#theme-switch:checked ~ #page #crosslinks a:not(.highlight):hover,
#theme-switch:checked ~ #page li a:hover,
#theme-switch:checked ~ #page dl a:hover {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg id='squiggle-link' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:ev='http://www.w3.org/2001/xml-events' viewBox='0 0 20 4'%3E%3Cstyle type='text/css'%3E.squiggle{animation:shift .3s linear infinite;}@keyframes shift {from {transform:translateX(0);}to {transform:translateX(-20px);}}%3C/style%3E%3Cpath fill='none' stroke='%23ffffff' stroke-width='2' class='squiggle' d='M0,3.5 c 5,0,5,-3,10,-3 s 5,3,10,3 c 5,0,5,-3,10,-3 s 5,3,10,3'/%3E%3C/svg%3E"); }
text-decoration: none; }
#theme-switch:checked ~ #page #breadcrumbs a:hover::before,
#theme-switch:checked ~ #page figcaption a:hover::before,
#theme-switch:checked ~ #page p a:hover::before,
#theme-switch:checked ~ #page .page-tags a:hover::before,
#theme-switch:checked ~ #page table a:hover::before,
#theme-switch:checked ~ #page #crosslinks a:not(.highlight):hover::before,
#theme-switch:checked ~ #page li a:hover::before,
#theme-switch:checked ~ #page dl a:hover::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
mask-image: url("data:image/svg+xml;charset=utf8,%3Csvg id='squiggle-link' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:ev='http://www.w3.org/2001/xml-events' viewBox='0 0 20 4'%3E%3Cstyle type='text/css'%3E.squiggle{animation:shift .3s linear infinite;}@keyframes shift {from {transform:translateX(0);}to {transform:translateX(-20px);}}%3C/style%3E%3Cpath fill='none' stroke='%23000000' stroke-width='2' class='squiggle' d='M0,3.5 c 5,0,5,-3,10,-3 s 5,3,10,3 c 5,0,5,-3,10,-3 s 5,3,10,3'/%3E%3C/svg%3E") !important;
mask-position: 0% 100%;
mask-size: 0.8em auto;
mask-repeat: repeat-x;
background: var(--text-light); }
*:target::before {
content: "🠖";
@ -1330,12 +1429,12 @@ dl a {
right: 0; } }
#content .page-intro p a,
#footnotes a {
background-image: linear-gradient(var(--text-gray), var(--text-gray));
background-size: 1px 1px;
background-repeat: repeat-x;
background-position: 0% 95%;
border-radius: 1px;
text-decoration: none; }
text-decoration-line: underline;
text-decoration-style: dotted;
text-decoration-color: var(--text-light);
text-decoration-thickness: .1em;
text-underline-offset: 1.5px;
border-radius: 1px; }
#content .page-intro p a::selection,
#footnotes a::selection {
background: var(--back-medium); }
@ -1353,12 +1452,12 @@ dl a {
color: var(--text-gray); }
#content .page-intro p a:hover,
#footnotes a:hover {
background-image: linear-gradient(var(--text-gray), var(--text-gray));
background-size: 1px 1px;
background-repeat: repeat-x;
background-position: 0% 95%;
border-radius: 1px;
text-decoration: none; }
text-decoration-line: underline;
text-decoration-style: dotted;
text-decoration-color: var(--text-light);
text-decoration-thickness: .1em;
text-underline-offset: 1.5px;
border-radius: 1px; }
#content .page-intro p a:hover::selection,
#footnotes a:hover::selection {
background: var(--back-medium); }
@ -1444,6 +1543,7 @@ div.link-preview {
max-height: 5em;
padding-left: 0.5em; }
div.link-preview a {
color: inherit;
text-decoration: none; }
div.link-preview img {
border-right: 1px solid var(--back-medium);
@ -1452,7 +1552,7 @@ div.link-preview {
margin-left: -0.5em;
margin-right: 0.5em; }
div.link-preview p {
margin: 0;
margin: 0 0.6em 0 0;
font-size: 10pt; }
div.link-preview p b {
font-size: 11pt; }
@ -1486,14 +1586,16 @@ ul li::before {
ol {
counter-reset: li; }
ol li:not([value]) {
counter-increment: li; }
ol li::before {
content: counter(li);
margin-left: -1.5em;
margin-right: 0.5em;
text-align: right;
direction: rtl; }
ol li {
counter-increment: li; }
ol li[value]::before {
content: attr(value); }
ul ul, ol ol {
padding-left: 20px; }
@ -1786,6 +1888,7 @@ table {
line-height: 1.9; }
table caption {
margin-bottom: 10px;
min-width: 20em;
font-family: "Open Sans", sans;
font-weight: normal;
font-size: 15px;

View File

@ -0,0 +1,293 @@
#+title: February 2022
#+subtitle: An orgmode clone for neovim
#+author: Kristijan Husak
#+date: 2022-02-30
Timothy (TEC) here. This month we have a guest post from a different part of the
Org ecosystem, to highlight one of the most promising efforts to provide a good
experience outside Emacs.
[[Https://github.com/nvim-orgmode/orgmode]]
"But I use Emacs, I don't care" you may say. In that case, I'd like to point out
that wider spread and better Org support enriches the Org ecosystem as a whole.
It makes the format more approachable, and /useful/ for other people. This is good
for everybody.
Without any further ado, here's the guest post kindly written by Kristijan.
Enjoy!
-----
Like every beginner Vim user, at some point I ran into a usual editor war post:
Vim vs Emacs. At that time, I didn't have an idea what "Emacs" was.
A simple Google search yielded something that seemed just like a very simple
editor with strange, but more familiar shortcuts. I didn't bother too much to
figure out what it is, because I was already pulled in fairly deep into Vim and
its philosophy.
* Note taking in (Neo)Vim
At first, I did some note taking only when really necessary, in random
plain text files. Most of the things I managed to keep in my head, since
I was younger and less busy 🙂.
Once I got into the situation where I needed to keep more notes, [[https://github.com/vimwiki/vimwiki][vimwiki]] was the
natural choice.
That worked very well for a period, until the need for writing quick notes
arise. Vimwiki didn't have anything that would allow that. I could of course
have a mapping that opens a specific file where I can add notes, but that just
never felt right in my mind. I would keep a bunch of things in the same place,
and then later I needed to spend some time organizing them.
At that point, I wasn't sure how to achieve what I want. I did a brief look at
[[https://orgmode.org/][Emacs OrgMode]] to see what's all the fuss about, but to me, it seemed just like a
different version of Markdown. You put some unordered lists as your notes, and
that's it. I never spent more time trying to see all the neat features. I even
tried creating some of my custom note taking tools, but I never managed to
finish them because I didn't have a clear idea of how to solve my problems.
* First encounter with Orgmode like tool: vim-dotoo
One weekend, I was browsing through Vim subreddit, as I usually do at least once
a day. There was a post about an "Orgmode like task logging" plugin called
[[https://github.com/dhruvasagar/vim-dotoo][vim-dotoo]]. I opened it up, and I didn't see much at that point. I wasn't too
excited. I went through readme, and noticed that author ([[https://github.com/dhruvasagar][dhruvasagar]]) put a
fairly big emphasis on the "Agenda view". I had no idea what "Agenda view" is.
Thankfully, the author also made a [[https://www.youtube.com/watch?v=nsv33iOnH34][screencast]], which is rather long (1.5h), but
I had some time, so I went through it.
At that point, I was first met with "Capturing" and "Refiling". *My mind was
blown!* What a simple, yet extremely powerful idea! How had that never crossed my
mind? From that point on, this plugin had my full attention.
I'm always emphasizing that [[https://github.com/dhruvasagar][dhruvasagar]] and his [[https://github.com/dhruvasagar/vim-dotoo][vim-dotoo]] plugin are most
deserving for having inspired [[https://github.com/nvim-orgmode/orgmode]], and I
can't thank him enough for that.
* First steps with vim-dotoo and birth of orgmode.nvim
For some time, I was using [[https://github.com/dhruvasagar/vim-dotoo][vim-dotoo]]. I moved all of my Vimwiki notes to it. It
was a breath of fresh air. Alongside that, I started getting more interest in
the original Emacs Orgmode. I started noticing the differences, and some of the
missing features that were now looking quite attractive. I made [[https://github.com/dhruvasagar/vim-dotoo/pulls?q=is%3Apr+sort%3Aupdated-desc+author%3Akristijanhusak+is%3Aclosed][few
contributions]] to vim-dotoo. As time passed, and my notes started to grow, things
began being slow. I did some profiling, and figured out that it's just a usual
Vim problem, Vimscript performance. It was just too slow for certain things that
Orgmode provides, and it would hardly get any better as more things are added.
Separately from Vim and Vimscript, [[https://github.com/neovim/neovim][Neovim]] was on a stable =v0.4= release, and =v0.5=
was still being developed. I was using Neovim from version 0.3, and was
carefully following the progress on it. Lua was introduced as a first class
citizen. A Bunch of new plugins arise from it. All the benchmarks showed that
Lua outperforms Vimscript in almost everything. Besides the performance, Lua is
a "normal" programming language, which means that support for it is much better.
At that point, I became curious: Could Lua be the path to the faster Orgmode? I
spent several days thinking about it. I wanted to give it a try. My biggest
concern was that I had absolutely zero experience writing parsers. I had never
written anything more complicated than an averagely complicated regex for
purposes of parsing. I noticed that vim-dotoo also used regex to do the parsing,
so that eased my mind a bit.
One weekend, I started working on it. It was really interesting and challenging.
I spent a lot of my free time on it. At certain points, it seemed like hacking,
since it was not a proper parsing. I tried to learn how to write a proper
parser, but it was just too time consuming and complicated. I proceeded with the
regex parsing to see how far I can go.
Besides parsing, I had a few more challenges to overcome:
** Understanding the OrgMode syntax and all the functionality
This is still the biggest challenge. I didn't have any idea how big and robust
OrgMode is. If I would know it at that time, I wouldn't even jump on this train.
It's really hard to grasp all of it. Considering I've only used it for around 8
months, I think I made some good progress on learning it.
** Remote editing
By remote editing, I mean automatically updating content in the current
or any other file. Few examples: adding/updating properties, managing
tags, changing TODO states, archiving, refiling, remote editing from
agenda view, etc.
There is no built-in way to update content in another file through the
Neovim API, without actually opening the file in an editor. I solved
this by:
- Saving as much position information as possible in the internal state,
so I can pinpoint the correct location
- Opening a file in a =1 row x 1 col= floating window and doing quick
edits there
** Working with dates
From my experience, dates are challenging in all areas of programming,
so this is not so surprising. There are some Lua plugins for dates, but
those seemed a bit too basic for my use case, and I wanted to keep
external plugins to the minimum. I went with a custom solution that uses
Lua's native dates, which has certain limitations, but works out for
most of the things.
** Highlighting, mostly in Agenda view
Vim's syntax engine is fairly old, but still very much used, especially
in the Vim community. Implementation of tree-sitter slightly improved
this experience in Neovim, because "Highlight matches" are found via
tree-sitter, instead of a bunch of regexes.
This helped me out later for the Org file itself, but agenda view is
still something that's built as a custom view. Old Syntax highlight engine
would be really hard to add, because the content is too dynamic. I went
with the Neovim highlight API that allows Highlighting things by their
exact position in the buffer. Tree-sitter implementation does something
similar in the background for Highlighting.
** Keeping configuration simple and familiar to Emacs OrgMode
Vim-dotoo configuration was mostly Vim style, through some global
variables. I wanted to have a configuration that is familiar to an Emacs
OrgMode user, by having as many options as possible named completely the
same as in Emacs.
For example, Here's a comparison of few options between Emacs and
Neovim:
Emacs:
#+begin_src elisp
(setq org-agenda-files '("~/orgmodes"))
(setq org-agenda-skip-scheduled-if-done t)
(setq org-agenda-span 7)
(setq org-hide-leading-stars t)
(setq org-capture-templates
'(("t" "Todo" entry (file "~/orgmodes/todos.org")
"* TODO %?")
("j" "Journal" entry (file "~/orgmodes/journal.org")
"* %?\nEntered on %U\n %a")))
#+end_src
Neovim:
#+begin_src lua
require('orgmode').setup({
org_agenda_files = { '~/orgmodes' },
org_agenda_skip_scheduled_if_done = true,
org_agenda_span = 7,
org_hide_leading_stars = true
org_capture_templates = {
t = {
description = 'Todo',
target = '~/orgmodes/todos.org',
template = '* TODO %?',
},
j = {
description = 'Journal',
target = '~/orgmodes/journal.org',
template = '* %?\nEntered on %U\n %a',
}
}
})
#+end_src
One of the most noticeable differences is between the usage of hyphens
(=-=) and underscores (=_=). I did that only for the sake of simplicity,
because hyphens is not a valid character in variable names in Lua, so
all of the options would need to be wrapped as a string (for example:
=['org-agenda-files']=).
* First release of orgmode.nvim and introduction of tree-sitter parser
After ~1.5 months I [[https://www.reddit.com/r/neovim/comments/o8zp0k/orgmodenvim_orgmode_clone_written_in_lua_for/][published the initial version]]. The focus was on Agenda and
capturing (GTD), since those are the things I mostly used. It got some traction,
and people started testing it and reporting bugs.
One of the common questions was: /"Any plans to introduce tree-sitter parser?"/.
I knew about [[https://github.com/tree-sitter/tree-sitter][tree-sitter]] and used it in my day-to-day job for a few programming
languages, but I had absolutely no idea how it worked, and especially how to
write a tree-sitter parser. I put it aside, and continued working on what I
had.
One day, Emilia ([[https://github.com/milisims][milisims]]) contacted me via email to ask me if I would be
willing to try the tree-sitter parser she's been working on for some time. I
gladly accepted. She gave me access to the repository, and I started tinkering
with it in a separate branch. No one was aware at that point that tree-sitter
support would happen some time soon.
After some time, I set up a "beta" branch called "tree-sitter" and [[https://www.reddit.com/r/neovim/comments/ph2xqc/orgmodenvim_treesitter_support/][announced it
for testing]]. Once the reported bugs slowed to a trickle, I merged it into the
"master" branch.
I believe that tree-sitter grammar for Org could help out other editors to
implement their version of Orgmode plugin, but I don't think it would ever be
helpful for Emacs. Emacs parser is the one and only that has it all implemented.
Also, as much as tree-sitter is powerful, its main purpose is to parse
programming languages, which mostly has "static" patterns to match. Orgmode is
by its nature dynamic, which causes a variety of issues for a parser that's not
meant for that kind of usage.
* Limitations
(Neo)Vim is a great editor, but it still cannot compare to Emacs in certain
things. Manipulating the "View" part of the editor is tricky or impossible for
certain things.
I even [[https://github.com/nvim-orgmode/orgmode/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aneovim-dependency][made a label]] for reported issues where Neovim support for certain things
is a blocker. I'm hoping that at least some of these will be available in future
Neovim releases.
* Features
I will not go into too many details about the available features, since those
can be viewed in [[https://github.com/nvim-orgmode/orgmode#features-detailed-breakdown][repository readme]], but I want to mention one feature that does
not exist as a built/-in feature in the Emacs Orgmode: [[https://github.com/nvim-orgmode/orgmode/blob/master/DOCS.md#notifications-experimental][Notifications]].
This allows getting a "desktop notification" for tasks that are within
the specified threshold for schedule/deadline time. It requires some
configuration to set up a cron job, but it's been working great for me
for several months now.
* Plans
The current state of the project is very usable for me. I'm not lacking any of
the major features, mostly because I'm not used to using them. Nevertheless,
there are plans to add more things, and I'm getting a lot of help from the
community. I want to specifically mention [[https://github.com/levouh][levouh]] and [[https://github.com/lukas-reineke][lukas-reineke]], since they
added a lot of value to the project, and I want to thank them and everyone else
who contributed. Their help is much appreciated.
There are few high priority tasks that I'm hoping to flush out first:
- Implementing [[https://github.com/milisims/tree-sitter-org/issues/13][v1.0.0]] release of the tree-sitter parser. This should allow for
faster and less error-prone parsing.
- [[https://github.com/nvim-orgmode/orgmode/issues/26][Infrastructure for plugin developers]], to allow other people to build plugins
on top of nvim-orgmode.
And a long term goal for these:
- Tables support (and at least basic formulas)
- [[https://github.com/nvim-orgmode/orgmode/issues/190][Org Babel like code block evaluation]] (and hopefully basic support for literate
programming)
- [[https://github.com/nvim-orgmode/orgmode/issues/195][Diary format dates]]
- [[https://github.com/nvim-orgmode/orgmode/issues/135][Custom agenda commands]]
- More clocking features (reports)
- File specific configuration via directives ([[https://github.com/nvim-orgmode/orgmode/issues/185][todo keywords]], properties, etc.)
* Closing thoughts
When I started working on [[https://github.com/nvim-orgmode/orgmode][nvim-orgmode]], I didn't have a clue what I'm jumping
into. Every day I learn about more and more Orgmode features that I wasn't even
aware existed.
I'm certain that this project will never manage to clone the Orgmode
functionality completely, but I'm hoping it will get close enough so everyone
from Neovim community and Emacsers trying out Neovim will be able to use it for
their needs.
Having experienced Orgmode users [[https://github.com/nvim-orgmode/orgmode/issues/159][testing]] it is a huge help, so if anyone is
willing to give it a try, feel free to open up an issue and write your thoughts
there. Thanks!

View File

@ -0,0 +1,41 @@
#+title: Special Announcement
#+subtitle: Throwing in the towel and jumping on the bandwagon
#+author: TEC
#+date: 2022-04-01
It *h*as become apparent to the Org Mode developers that Org is suffering from a
severe lack of enterprise adoption. To r*e*ctify this, we will be leveraging our
collective decades working on holistic human-markup interaction tools to rapid*l*y
pivot to what we believe to be the /true/ markup format of the future ---
[[https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html][Confluence Wiki Markup]].
With this *p*aradigm shift, you can look forward to a much more intuitive syntax,
empowering you to create next-generation agile documents.
To assist you in this *t*ransition, we'll give you a brief overview of the changes
you can expect. Text formatting is almost unaffected, with a few sensible
changes made to the surrounding c*h*aracters.
#+caption: Translation between Org's syntax and our new Confluence overlords's equivalents
#+attr_html: :class invertible
[[file:figures/org-markup-to-confluence.svg]]
We'd like to thank the FSF for giving the Org project the space and support to
grow to become a world-class proj*e*ct. In our new venture, cop*y*right assignment
to t*h*e FSF is no longer required. Inste*a*d, copyright must be assigned to
Atlassian prior to contributing.
This new partnership enables new and exciting integrations with Atlassian's
proprietary technologies. O*v*er the next f*e*w months we will *b*e transforming Org
to a cloud-n*a*tive serverless profes*s*ional markup offering. Unlike Confluence, we
will allow you to bo*t*h write /and edit/ documents using pla*i*ntext markup. We
believe that by unlocking the synergy between plaintext and Confluence the Org
project will become a thought leader in hyp*e*r-scale markup solutions.
The shift to Confluence wiki markup will come as a surprise to many of you, but
after doing a deep dive into the scalability of Org we realised that there were
no low-hanging fruit available --- only out of the box thinking will allow for the
in*n*ovation so many of our users crave.
We hope this change will not just disrupt your documents, but the entire markup
industry.

View File

@ -0,0 +1,224 @@
# -*- org-plot/gnuplot-term-extra: "background rgb '#fafafa' size 800,500 font 'Alegreya Sans, 16'"; -*-
#+title: May 2022
#+subtitle: Folding more improvement into Org
#+author: TEC
#+date: 2022-05-31
Finding time as of late has been more difficult than I anticipated, and on top
of that, just as I was thinking of writing last month's post, I got distracted
by an exciting patchset that has been in the works for over a year finally
getting sorted out and landing. So, I hope that some of the fun developments in
this post will make up the absense of the last one 🙂.
Since it's been longer than I thought since the last standard post, we've got a
fair few commits to catch up on --- about 200. Most of these are miscellaneous
minor improvements and bugfixes, but a few notable changes have arrived too.
* Folding
The fabulous new folding engine (=org-fold-core=) should noticeably improve Org's
performance with large files. It contains a number of key optimisations to
improve speed, namely:
+ Deferring fontification of folded regions
+ Using text properties (\(\mathcal{O}(n \log n)\)) instead of overlays (\(\mathcal{O}(n^2)\)) for folded regions
+ A collection of aggressive optimisations available under ~org-fold-core--optimise-for-huge-buffers~
+ Convert text properties to overlays for =isearch= (which currently only supports overlays)
How noticeable is the overall performance impact? Well, I poked Ihor and he was
kind enough to whip up some benchmarks.
#+caption: The scaling of ~org-shifttab~ showing file contents, as file size increases, with and without org-fold.
#+attr_html: :class invertible
[[file:figures/org-fold-perf-shifttab-contents.svg]]
#+caption: The scaling of ~org-shifttab~ showing the entire file, as file size increases, with and without org-fold.
#+attr_html: :class invertible
[[file:figures/org-fold-perf-shifttab-showall.svg]]
Well this looks very promising[fn::Note the difference in scale, org-fold makes
the most difference in the graph where the times are an order of magnitude
more.]! Let's see how much of an improvement this is overall.
#+caption: Time to run =org-shifttab= twice, cycling through all three display modes (in seconds).
| File size (Mb) | Headings (thousands) | Bugfix (no org-fold) | Main (with org-fold) | Improvement |
|----------------+----------------------+----------------------+----------------------+-------------|
| 18 | 36 | 115.31 | 0.89 | 99% |
| 8.8 | 24 | 19.03 | 0.48 | 97% |
| 4.4 | 5 | 3.79 | 0.13 | 97% |
| 2.2 | 2 | 1.29 | 0.08 | 94% |
| 1.1 | 1 | 0.50 | 0.045 | 91% |
#+TBLFM: $5=100*(1 - $4/$3) ; %.0f%%
To be clear, even the smallest file in this data --- a 1.1 Mb Org file with around
a thousand headings, is fairly large. So, it's unlikely you'll notice much of a
difference with small--medium files, but if you a few large+ files this should
be a /fantastic/ improvement. Once again, thanks Ihor!
#+begin_warning
The change to text properties instead of overlays breaks a number of third party
packages like =evil-search= and =consult='s ~consult-line~.
If you are involved in any packages affected by this, you'll either want to
consider supporting invisible text, or look at ~isearch-filter-predicate~ and
~isearch-mode-end-hook~, which =org-fold= now uses.
If you're an end-user, perhaps politely make an issue on the repo for a project
/if no issue currently exists/, and either:
+ Stay off Org's bleeding edge till the package ecosystem has adapted to this change
+ Help the packages you use adapt to this change
+ Set ~org-fold-core-style~ to ~overlays~ to restore the old behaviour
#+end_warning
** Benchmark data :noexport:
#+plot: ind:2 deps:(3 4) with:linespoints file:"figures/org-fold-perf-shifttab-contents.svg"
#+plot: set:"title '{/*1.6 Running org-shifttab to CONTENTS}'" set:"xlabel '{/*1.2 File size (Mb)}'" set:"ylabel '{/*1.2 Load time (seconds)}'"
#+plot: set:"logscale x" set:"logscale y" set:"linetype 1 pt 5" set:"linetype 2 pt 7"
| Headings (k) | File Size (Mb) | Bugfix (no org-fold) | Main (org-fold) |
|--------------+----------------+----------------------+-----------------|
| 217 | 150 | 173.28 | 10.73 |
| 36 | 18 | 1.97 | 0.92 |
| 24 | 8.8 | 0.81 | 0.46 |
| 5 | 4.4 | 0.09 | 0.21 |
| 2 | 2.2 | 0.03 | 0.07 |
| 1 | 1.1 | 0.02 | 0.04 |
#+plot: ind:2 deps:(3 4) with:linespoints file:"figures/org-fold-perf-shifttab-showall.svg"
#+plot: set:"title '{/*1.6 Running org-shifttab to SHOW-ALL}'" set:"xlabel '{/*1.2 File size (Mb)}'" set:"ylabel '{/*1.2 Load time (seconds)}'"
#+plot: set:"logscale x" set:"logscale y" set:"linetype 1 pt 5" set:"linetype 2 pt 7"
| Headings (k) | File Size (Mb) | Bugfix (no org-fold) | Main (org-fold) |
|--------------+----------------+----------------------+-----------------|
| 217 | 150 | 8921.02 | 0.09 |
| 36 | 18 | 100.96 | 0.02 |
| 24 | 8.8 | 17.35 | 0.01 |
| 5 | 4.4 | 3.79 | 0.005 |
| 2 | 2.2 | 1.20 | 0.003 |
| 1 | 1.1 | 0.49 | 0.003 |
* /Engraved/ source code blocks in LaTeX
All too often exporting code to LaTeX has been a disappointment, with lovely
syntax highlighting from Emacs major modes replaced with a markedly inferior
attempt by pygments (setting ~org-latex-listings~ to ~minted~) in a colour scheme I
don't really like.
A bit over a year ago, a project called [[https://github.com/tecosaur/engrave-faces][engrave-faces]] started with the aim of
making Emacs' font-lock more exportable, like a generalised =htmlize.el=. This has
recently been used to provide a new option for inline and block source code
exports in LaTeX.
#+caption: A screenshot of an Org code block, exported to a PDF,
#+caption: using =engrave-faces= and the =doom-one-light= theme.
#+attr_html: :class invertible
[[file:figures/engraved-faces-sample.png]]
To use this, simply install the package and set ~org-latex-src-block-backend~ (a
rename of ~org-latex-listings~ to better reflect its usage) to ~engraved~.
While this is sufficient to get started, this new backend also allows for some
new options. The theme used for /engraving/ a source block can be set globally
with the new variable ~org-latex-engraved-theme~, or per-file with the
=#+latex_engraved_theme= keyword. It takes either the name of a theme, or the
symbol =t= as a stand-in for the current theme.
The theme can also be set on a per-block level using the LaTeX attribute
=:engraved-theme=.
#+caption: Seven code blocks exported to LaTeX, each with a different engrave-faces theme.
[[file:figures/engraved-faces-multitheme.png]]
Here's what using these new capabilities looks like in practice.
#+begin_src org
,#+title: Engraving source blocks
,#+latex_engraved_theme: modus-operandi
,#+begin_src emacs-lisp
(message "look ma, some %s" 'code)
,#+end_src
,#+attr_latex: :engraved-theme modus-viviandi
,#+begin_src shell
echo "This is shell code"
,#+end_src
#+end_src
This may well be the best syntax-highlighting solution available for PDFs/LaTeX
currently available, but I am a tad biased 😛.
* TexInfo export improvements
Jonas Bernoulli has been using a custom TexInfo backend for Magit's
documentation for a while now, and over the past few months he's worked the
features he was missing into Org's built-in TexInfo exporter.
Upstreaming like this always takes a fair bit of effort, so thank you Jonas for
going through with this!
* Toggle noweb prefix handling
Previously, whenever a noweb reference appeared on a non-empty line, a
multi-line replacement would duplicate the content before the noweb reference.
Clearly, this is not always desirable, and this behaviour can now be turned of
by setting the new header argument =:noweb-prefix no=.
#+begin_src org
,#+begin_src emacs-lisp :noweb yes :noweb-prefix no
(setq example-data "<<example>>")
,#+end_src
Will now expand to
,#+begin_src emacs-lisp
(setq example-data "some
multi-line
content")
,#+end_src
Instead of
,#+begin_src emacs-lisp
(setq example-data "some
(setq example-data "multiline
(setq example-data "content")
,#+end_src
#+end_src
* Package highlight: org-modern
I think we've all [[https://github.com/integral-dw/org-superstar-mode][seen]] [[https://github.com/sabof/org-bullets][plenty]] of =org-mode= [[https://github.com/Fuco1/org-pretty-table][prettification]] [[https://github.com/harrybournis/org-fancy-priorities][packages]] [[https://gitlab.com/marcowahl/org-pretty-tags][before]], so
what makes Minad's [[https://github.com/minad/org-modern][org-modern]] special? It's actually doing something similar to
Ihor's org-fold improvements, switching out slower overlay-based approaches for
text properties. I can confirm that switching out =org-superstar-mode= for
=org-modern= has made a substantial improvement in my experience, halving the
first-load time of my =config.org= to around 20 seconds. If you're a fan of Org
prettification and haven't taken a look at this package, I highly recommend
giving it a shot.
#+caption: A demonstration of org-modern taken from the project README.
#+attr_html: :class invertible
[[file:figures/org-modern-readme-demo.gif]]
* Other improvements
+ Clean up some magic numbers in =org-attach= _Marco Wahl_
+ Allow /any/ command form in ~org-attach-commands~ (including keyboard macros)
_Marco Wahl_
+ Allow =dest= in ~org-list-send-item~ to be a buffer position _Sacha Chua_
+ Improve CSL-JSON date handling in =oc-basic= _David Lukes_
+ Add =TOML= and =desktop= language aliases _TEC_
+ Speed up cached bibliography retrieval in =oc-basic= _Ihor Radchenko_
+ Allow setting PlantUML jar arguments _Ihor Radchenko_
+ Allow for customisation of property separators with ~org-property-separators~
_Tyler Grinn_
+ New =ox-latex= maintainer, Daniel Fleischer
+ More unit tests _Kyle Keyer, Nick Dokos_
+ Documentation improvements _Kyle Meyer, Juan Manuel Macias, Bastien, Karl
Fogel, Cody Harris_
* Bugfixes
+ An Emacs <28 bug in =org-persist= _Ihor Radchenko_
+ Author extraction in =oc-basic= _Nicolas Goaziou_
+ Fix behaviour of ~org-copy-visible~ with adjacent tex and
~buffer-invisibility-spec~ _Kyle Meyer_
+ Parsing of inline footnotes with parentheses _Nicolas Goaziou_
+ Honor ~default-directory~ in =ob-gnuplot= _Ihor Radchenko_
+ Heading fontification bug _Anders Johansson_
+ Template expansion where one key is a substring of another _Andrew Arensburger_

56
engraved-theme.el Normal file
View File

@ -0,0 +1,56 @@
;;; engraved-theme.el -*- lexical-binding: t; -*-
(require 'engrave-faces)
(add-to-list 'engrave-faces-themes
'(doom-opera-light
(default :short "default" :slug "D" :family "JetBrains Mono" :foreground "#2a2a2a" :background "#fafafa" :slant normal :weight regular)
(bold :short "bold" :slug "b" :weight bold)
(italic :short "italic" :slug "i" :slant italic)
(variable-pitch :short "var-pitch" :slug "vp" :family "Overpass" :height 196)
(shadow :short "shadow" :slug "h" :foreground "#9e9e9e")
(success :short "success" :slug "sc" :foreground "#4f894c")
(warning :short "warning" :slug "w" :foreground "#9a7500")
(error :short "error" :slug "e" :foreground "#99324b")
(link :short "link" :slug "l" :foreground "#3b6ea8" :weight bold)
(link-visited :short "link" :slug "lv" :foreground "#8b008b" :weight bold)
(highlight :short "link" :slug "hi" :foreground "#fafafa" :background "#3b6ea8")
(font-lock-comment-face :short "fl-comment" :slug "c" :foreground "#b1b1b1")
(font-lock-comment-delimiter-face :short "fl-comment-delim" :slug "cd" :foreground "#b1b1b1")
(font-lock-string-face :short "fl-string" :slug "s" :foreground "#4f894c")
(font-lock-doc-face :short "fl-doc" :slug "d" :foreground "#b6b6b6")
(font-lock-doc-markup-face :short "fl-doc-markup" :slug "m" :foreground "#97365b")
(font-lock-keyword-face :short "fl-keyword" :slug "k" :foreground "#3b6ea8")
(font-lock-builtin-face :short "fl-builtin" :slug "bi" :foreground "#29838d")
(font-lock-function-name-face :short "fl-function" :slug "f" :foreground "#29838d")
(font-lock-variable-name-face :short "fl-variable" :slug "v" :foreground "#cb9aad")
(font-lock-type-face :short "fl-type" :slug "t" :foreground "#9a7500")
(font-lock-constant-face :short "fl-constant" :slug "o" :foreground "#97365b")
(font-lock-warning-face :short "fl-warning" :slug "wr" :foreground "#9a7500")
(font-lock-negation-char-face :short "fl-neg-char" :slug "nc" :foreground "#3b6ea8" :weight bold)
(font-lock-preprocessor-face :short "fl-preprocessor" :slug "pp" :foreground "#3b6ea8" :weight bold)
(font-lock-regexp-grouping-construct :short "fl-regexp" :slug "rc" :foreground "#3b6ea8" :weight bold)
(font-lock-regexp-grouping-backslash :short "fl-regexp-backslash" :slug "rb" :foreground "#3b6ea8" :weight bold)
(org-block :short "org-block" :slug "ob" :background "#e0e0e0")
(org-block-begin-line :short "org-block-begin" :slug "obb" :foreground "#b1b1b1" :background "#e0e0e0")
(org-block-end-line :short "org-block-end" :slug "obe" :foreground "#b1b1b1" :background "#e0e0e0")
(outline-1 :short "outline-1" :slug "Oa" :foreground "#3b6ea8" :weight extra-bold :height 1.25)
(outline-2 :short "outline-2" :slug "Ob" :foreground "#97365b" :weight bold :height 1.15)
(outline-3 :short "outline-3" :slug "Oc" :foreground "#842879" :weight bold :height 1.12)
(outline-4 :short "outline-4" :slug "Od" :foreground "#6c92bd" :weight semi-bold :height 1.09)
(outline-5 :short "outline-5" :slug "Oe" :foreground "#b16883" :weight semi-bold :height 1.06)
(outline-6 :short "outline-6" :slug "Of" :foreground "#9db6d3" :weight semi-bold :height 1.03)
(outline-7 :short "outline-7" :slug "Og" :foreground "#cb9aad" :weight bold)
(outline-8 :short "outline-8" :slug "Oh" :foreground "#d7e2ed" :weight semi-bold)
(highlight-numbers-number :short "hl-number" :slug "hn" :foreground "#97365b" :weight bold)
(highlight-quoted-quote :short "hl-qquote" :slug "hq" :foreground "#3b6ea8")
(highlight-quoted-symbol :short "hl-qsymbol" :slug "hs" :foreground "#9a7500")
(rainbow-delimiters-depth-1-face :short "rd-1" :slug "rda" :foreground "#3b6ea8")
(rainbow-delimiters-depth-2-face :short "rd-2" :slug "rdb" :foreground "#97365b")
(rainbow-delimiters-depth-3-face :short "rd-3" :slug "rdc" :foreground "#4f894c")
(rainbow-delimiters-depth-4-face :short "rd-4" :slug "rdd" :foreground "#842879")
(rainbow-delimiters-depth-5-face :short "rd-5" :slug "rde" :foreground "#29838d")
(rainbow-delimiters-depth-6-face :short "rd-6" :slug "rdf" :foreground "#3b6ea8")
(rainbow-delimiters-depth-7-face :short "rd-7" :slug "rdg" :foreground "#97365b")
(rainbow-delimiters-depth-8-face :short "rd-8" :slug "rdh" :foreground "#4f894c")
(rainbow-delimiters-depth-9-face :short "rd-9" :slug "rdi" :foreground "#842879")))

View File

@ -1,77 +0,0 @@
#!/usr/bin/env sh
":"; exec script -eqfc "TERM=xterm-direct emacs --quick -nw --eval '(progn (setq file-to-htmlize \"$1\" output-file \"$2\") (load (expand-file-name \"$0\")))' && rm typescript" 1>/dev/null # -*- mode: emacs-lisp; lexical-binding: t; -*-
(defvar htmlize-theme 'doom-opera-light)
;; Need output file
(when (string= "" output-file)
(kill-emacs 2))
(setq output-file (expand-file-name output-file))
;;; Doom initialisation
(unless (bound-and-true-p doom-init-p)
(setq gc-cons-threshold 16777216
gcmh-high-cons-threshold 16777216)
(setq doom-disabled-packages '(doom-themes))
(load (expand-file-name "core/core.el" user-emacs-directory) nil t)
(require 'core-cli)
(doom-initialize))
(advice-add 'undo-tree-mode :override #'ignore) ; Undo tree is a pain
(load-theme htmlize-theme t)
;;; No recentf please
(recentf-mode -1)
(advice-add 'recentf-mode :override #'ignore)
(advice-add 'recentf-cleanup :override #'ignore)
;;; Writegood is not desired
(advice-add 'writegood-mode :override #'ignore)
;;; Quit without fuss
(setq kill-emacs-hook nil
noninteractive t)
;;; Lighten org-mode
(when (string= "org" (file-name-extension file-to-htmlize))
(setcdr (assoc 'org after-load-alist) nil)
(setq org-load-hook nil)
(require 'org)
(setq org-mode-hook nil))
;; Start htmlizing
(require 'htmlize)
(ignore-errors
(with-temp-buffer
(insert-file-contents file-to-htmlize)
(setq buffer-file-name file-to-htmlize)
(ignore-errors
(normal-mode)
(if (eq major-mode 'org-mode)
(org-show-all))
(font-lock-ensure))
(with-current-buffer (htmlize-buffer-1)
(goto-char (point-min))
(replace-string "</title>\n"
"</title>
<style>
body { background: #f0eeed !important; }
body > pre {
font-size: 1rem;
max-width: min(100rem, 100%);
width: max-content;
white-space: pre-wrap;
margin: auto;
}
</style>\n")
(write-file output-file)
(kill-emacs 0))))
(kill-emacs 1)

View File

@ -1,35 +1,109 @@
#!/usr/bin/env sh
":"; exec emacs --quick --script "$0" -- "$@" # -*- mode: emacs-lisp; lexical-binding: t; -*-
(message "Publising")
(if load-file-name
(message "Publising")
(error "This is designed to be run as a script file, not within Emacs"))
(defvar force nil) ; -f --force
(defvar update-commit nil) ; -u --update
(defvar update-draft nil) ; -d --draft
(pop argv) ; $0
(setq force (string= "-f" (pop argv)))
;;; Doom initialisation
(while argv
(pcase (pop argv)
((or "-f" "--force")
(setq force t))
((or "-u" "--update")
(setq update-commit t))
((or "-d" "--draft")
(setq update-draft t))))
(unless (bound-and-true-p doom-init-p)
(setq gc-cons-threshold 16777216
gcmh-high-cons-threshold 16777216)
(setq doom-disabled-packages '(doom-themes))
(load (expand-file-name "core/core.el" user-emacs-directory) nil t)
(require 'core-cli)
(doom-initialize))
(when (and update-commit update-draft)
(warn! "--update and --draft are mutually exclusive, --draft will take priority"))
(advice-add 'undo-tree-mode :override #'ignore) ; Undo tree is a pain
(setq gc-cons-threshold (* 4 1024 1024)
gcmh-high-cons-threshold (* 4 1024 1024))
;;; Package initialisation
(unless (file-directory-p "~/.config/doom")
(error "This publishing script currently assumes a Doom emacs install exists."))
;; Assumes that the doom install is already fully-functional.
(push "~/.config/emacs/lisp" load-path)
(push "~/.config/emacs/lisp/lib" load-path)
(require 'doom-lib)
(require 'doom)
(doom-require 'doom-lib 'files)
(require 'doom-modules)
(require 'doom-packages)
(doom-initialize-packages)
;;I don't like this, but it works.
(dolist (subdir (directory-files (file-name-concat straight-base-dir "straight" straight-build-dir) t))
(push subdir load-path))
(require 'doom-cli)
(doom-require 'doom-cli 'doctor)
(doom-module-context-with '(:config . use-package)
(doom-load (abbreviate-file-name
(file-name-sans-extension
(doom-module-locate-path :config 'use-package doom-module-init-file)))))
(defun doom-shut-up-a (fn &rest args)
;;`quiet!' is defined in doom-lib.el
(quiet! (apply fn args)))
(push "~/.config/doom/subconf" load-path)
;;; General publishing setup
(section! "Initialising")
(require 'org)
(require 'ox-publish)
(require 'ox-html)
(require 'ox-latex)
(require 'ox-ascii)
(require 'ox-org) ; For the word count
(require 'org-persist)
(remove-hook 'kill-emacs-hook #'org-persist-gc)
(setq site-root "https://blog.tecosaur.com/tmio/")
(require 's) ; Needed for my config
(let ((css-src (expand-file-name "misc/org-css/main.css" doom-private-dir))
(css-dest (expand-file-name "assets/org-style.css" (file-name-directory load-file-name)))
(js-src (expand-file-name "misc/org-css/main.js" doom-private-dir))
(js-dest (expand-file-name "assets/org-style.js" (file-name-directory load-file-name))))
(provide 'config-org-behaviour) ; We *don't* want this
(require 'config-org-exports)
(require 'config-ox-html)
(require 'config-ox-latex)
(require 'config-ox-ascii)
(require 'engrave-faces-html)
(load (expand-file-name "engraved-theme.el" (file-name-directory load-file-name)))
(engrave-faces-use-theme 'doom-opera-light)
;; For faces
(require 'highlight-quoted)
(require 'highlight-numbers)
(require 'rainbow-delimiters)
;; Setup
(setq site-root "https://blog.tecosaur.net/tmio/"
publish-root (file-name-directory load-file-name)
content-dir (file-name-concat publish-root "content")
html-dir (file-name-concat publish-root "html")
assets-dir (file-name-concat publish-root "assets")
git-publish-branch "html")
(setq default-directory publish-root)
(let ((css-src (expand-file-name "misc/org-css/main.css" doom-user-dir))
(css-dest (file-name-concat assets-dir "org-style.css"))
(js-src (expand-file-name "misc/org-css/main.js" doom-user-dir))
(js-dest (file-name-concat assets-dir "org-style.js")))
(when (file-newer-than-file-p css-src css-dest)
(copy-file css-src css-dest t))
(when (file-newer-than-file-p js-src js-dest)
@ -48,12 +122,17 @@
:width "464"
:height "512"
:alt "Org unicorn logo")
org-export-with-broken-links t
org-id-locations-file (expand-file-name ".orgids")
org-babel-default-inline-header-args '((:eval . "no") (:exports . "code")))
org-babel-default-inline-header-args '((:eval . "no") (:exports . "code"))
org-confirm-babel-evaluate nil
org-resource-download-policy t
user-full-name "TEC"
user-mail-address "contact.tmio@tecosaur.net")
;;; For some reason emoji detection doesn't seem to work, so let's just turn it on
(setcar (rassoc 'emoji org-latex-conditional-features) t)
;; (setcar (rassoc 'emoji org-latex-conditional-features) t)
;;; Remove generated .tex/.pdf files from the base directory
@ -136,7 +215,7 @@ Return output file name."
;;; Htmlized file publishing
(defun org-publish-to-htmlized (_plist filename pub-dir)
(defun org-publish-to-engraved (_plist filename pub-dir)
"Publish a file with no change other than maybe optimisation.
FILENAME is the filename of the Org file to be published. PLIST
@ -146,7 +225,7 @@ publishing directory.
Return output file name."
(unless (file-directory-p pub-dir)
(make-directory pub-dir t))
(call-process (expand-file-name "./htmlize-file.el") nil nil nil filename (expand-file-name (concat (file-name-base filename) ".org.html") pub-dir)))
(engrave-faces-html-file filename (expand-file-name (concat (file-name-base filename) ".org.html") pub-dir)))
;;; RSS
@ -176,6 +255,7 @@ PROJECT is the current project."
(date (format-time-string "%Y-%m-%d" (org-publish-find-date entry project)))
(link (concat (file-name-sans-extension entry) ".html")))
(with-temp-buffer
(org-mode)
(insert (format "* [[file:%s][%s]]\n" file title))
(org-set-property "RSS_TITLE" title)
(org-set-property "RSS_PERMALINK" link)
@ -233,17 +313,18 @@ PROJECT is the current project."
"This Month in Org - Assets"
"This Month in Org - RSS"))
("This Month in Org - Pages"
:base-directory "./content"
:base-directory ,content-dir
:base-extension "org"
:publishing-directory "./html"
:publishing-directory ,html-dir
:exclude "rss\\.org"
:recursive t
:publishing-function
(org-html-publish-to-html
org-org-publish-to-org
org-publish-to-htmlized
org-publish-to-engraved
org-ascii-publish-to-utf8
org-latex-publish-to-pdf)
;; org-latex-publish-to-pdf
)
:headline-levels 4
:section-numbers nil
:with-toc nil
@ -252,9 +333,9 @@ PROJECT is the current project."
:html-postamble t
:html-postamble-format (("en" ,html-postamble)))
("This Month in Org - Index"
:base-directory "./assets"
:base-directory ,assets-dir
:base-extension "org"
:publishing-directory "./html"
:publishing-directory ,html-dir
:exclude ".*"
:include ("index.org")
:recursive nil
@ -262,14 +343,14 @@ PROJECT is the current project."
:headline-levels 4
:section-numbers nil
:with-toc nil
:html-head-extra ,(file-contents "assets/index-head-extra.html")
:html-head-extra ,(file-contents (file-name-concat assets-dir "index-head-extra.html"))
:html-preamble nil
:html-postamble t
:html-postamble-format (("en" ,html-postamble)))
("This Month in Org - Archive,404"
:base-directory "./assets"
:base-directory ,assets-dir
:base-extension "org"
:publishing-directory "./html"
:publishing-directory ,html-dir
:exclude ".*"
:include ("archive.org" "404.org")
:recursive nil
@ -282,19 +363,19 @@ PROJECT is the current project."
:html-postamble t
:html-postamble-format (("en" ,html-postamble)))
("This Month in Org - Assets"
:base-directory "./assets"
:base-directory ,assets-dir
:base-extension any
:exclude "\\.html$" ; template files
:publishing-directory "./html"
:publishing-directory ,html-dir
:recursive t
:publishing-function org-publish-attachment-optimised)
("This Month in Org - RSS"
:base-directory "./content"
:base-directory ,content-dir
:base-extension "org"
:recursive nil
:exclude ,(rx (or "rss.org" (regexp "DRAFT.*\\.org")))
:publishing-function org-rss-publish-to-rss-only
:publishing-directory "./html"
:publishing-directory ,html-dir
:rss-extension "xml"
:html-link-home ,site-root
:html-link-use-abs-url t
@ -309,21 +390,110 @@ PROJECT is the current project."
))
(section! "Publishing files")
(when force
(warn! "Force flag set"))
(when force
(delete-directory "./html" t))
(when (and force (file-directory-p html-dir))
(call-process "git" nil nil nil "worktree" "remove" "-f" git-publish-branch)
(call-process "git" nil nil nil "worktree" "prune")
(delete-directory html-dir t)
(call-process "git" nil nil nil "worktree" "add" html-dir git-publish-branch)
(if (file-directory-p html-dir)
(dolist (child (directory-files html-dir))
(unless (member child '("." ".." ".git"))
(if (file-directory-p child)
(delete-directory child t)
(delete-file child))))
(warn! "Failed to create html worktree")))
(unless (file-directory-p html-dir)
(call-process "git" nil nil nil "worktree" "add" html-dir git-publish-branch)
(unless (file-directory-p html-dir)
(warn! "Failed to create html worktree")))
(org-publish "This Month in Org" force)
(section! "Uploading")
(let ((rsync-status
(with-temp-buffer
(cons (call-process "rsync" nil t nil "-avzL" "--delete"
(expand-file-name "html/" (file-name-directory load-file-name))
"imh:/home/thedia18/public_html/tecosaur.com/blog/tmio/")
(message "\033[0;33m%s\033[0m" (buffer-string))))))
(if (= (car rsync-status) 0)
(success! "Content uploaded")
(error! "Content failed to upload, rsync exited with code %d" rsync-code)))
(section! "Pushing")
;; To make somewhat nice git history in the HTML branch, we'll want to collect
;; information on the current state off affairs and commit accordingly.
;;
;; We start by checking to see if we should make a "publish" style commit or a
;; "draft" style commit. This is determied by seeing if there are any
;; =content/...= lines in the git status, the assumption being that at each
;; publish point everything under =content/= has been comitted.
;;
;; Then we check to see if the last commit in the html branch is a "publish"
;; style commit or a "draft" style commit. We make this easy for ourselves by
;; prepending draft commits messages with the keyword "DRAFT". Should the last
;; commit be a draft, we replace it. Otherwise, a new commit is created.
;;
;; Lastly, we actually push the HTML branch.
(defun last-commit-log (fmt &optional branch)
"Get the log line for the last commit in FMT (optionally for BRANCH)."
(with-temp-buffer
(apply #'call-process "git" nil t nil "log"
(delq nil (list (and branch (format "refs/heads/%s" branch)) "-1"
(format "--pretty=format:%s" fmt))))
(buffer-string)))
(defun last-commit-subject (&optional branch)
"Get the commit subject line."
(last-commit-log "%s" branch))
(defun last-commit-hash (&optional branch)
"Get the commit subject line."
(last-commit-log "%h" branch))
(defun get-unstaged-changes ()
(with-temp-buffer
(call-process "git" nil t nil "status" "--porcelain=v1")
(goto-char (point-min))
(let (changes)
(while (re-search-forward "^\\(..\\) +" nil t)
(push (cons (match-string 1)
(and (re-search-forward "[^\n]+" nil t)
(match-string 0)))
changes))
changes)))
(defun git-try-command (&rest args)
(with-temp-buffer
(setq args (delq nil args))
(let ((exit-code (apply #'call-process "git" nil t nil args)))
(or (eq exit-code 0)
(progn
(error! "Failed to %s" (car args))
(message " Git command: git %s" (mapconcat #'shell-quote-argument args " "))
(message " Error: %s" (mapconcat #'identity (split-string (buffer-string) "\n") "\n "))
nil)))))
(let* ((source-draft-p (and (or update-draft (not update-commit))
(member (file-name-base content-dir)
(mapcar
(lambda (change) (car (file-name-split (cdr change))))
(get-unstaged-changes)))))
(html-draft-p (string-prefix-p "DRAFT " (last-commit-subject git-publish-branch)))
(html-changed-files (length (let ((default-directory html-dir)) (get-unstaged-changes))))
(commit-message
(if source-draft-p
(format "DRAFT update (%s files changed)\nLast source commit: %s\nLocal time: %s"
html-changed-files
(last-commit-hash)
(format-time-string "%F %T (UTC%z)"))
(format "Publish update based on %s" (last-commit-hash)))))
(if (= html-changed-files 0)
(warn! "No changes to push")
(let ((default-directory html-dir))
(unless source-draft-p
(dolist (file (mapcar #'cdr (get-unstaged-changes)))
(when (and (file-exists-p file)
(string-prefix-p "DRAFT-" (file-name-base file)))
(delete-file file))))
(and (git-try-command "add" "-A")
(git-try-command "commit" (and html-draft-p "--amend") "--message" commit-message)
(git-try-command "push" (and html-draft-p "--force-with-lease"))))))
(section! "Finished")