Compare commits

...
Author SHA1 Message Date
tomasr 19610c1c51 fix: clang format 2026-08-18 18:37:15 +02:00
tomasr 39a3cb6bba docs: redone demo gif
It was first done on a WebDav mounted drive. That meant that read and
write operations were really slow.
2026-08-17 17:54:39 +02:00
tomasr 0d5da72b13 feat: show warning if git operation too slow 2026-08-17 17:43:00 +02:00
tomasr e78a9ca669 fix: git commit message being truncated 2026-08-17 17:16:06 +02:00
tomasr c83a504648 Merge pull request #67 from tomasriveral/flake-update/2026-08-17 2026-08-17 08:18:04 +02:00
github-actions[bot] 2e301b0b52 chore: weekly flake update 2026-08-17 03:56:10 +00:00
tomasr c7a9d45a65 Merge (#66) from tomasriveral/vhs-demo-gif
docs: add demo and miscellanious improvement in README.md
2026-08-16 15:11:37 +02:00
tomasr 62da4cfb2e docs: add demo and miscellanious improvement in README.md 2026-08-16 15:09:28 +02:00
tomasr d56e7d9e4f Merge (#65) from tomasriveral/fix-double-free
fix: double free and double openEditor
2026-08-16 08:38:07 +02:00
8 changed files with 267 additions and 17 deletions

No files matched your search

+3 -1
View File
@@ -10,7 +10,7 @@ This project aims to remain minimal, terminal-focused, and easy to extend. Contr
Please use the standardized debugging and error-handling functions defined in [/src/utils.c](./src/utils.c):
* `debug(...)`
* `debug("message", ...)`
Used for formatted debugging output (printf-style formatting).
* `altDebug(...)`
@@ -21,6 +21,8 @@ Please use the standardized debugging and error-handling functions defined in [/
* `"user"`: errors caused by user input or configuration
* `"program"`: internal or unexpected program errors
* `warn(condition, "message", ...)`
Used for formatted warning (printf-style formatting).
---
+14 -11
View File
@@ -4,6 +4,8 @@ Note: This project fullfills all my usecases for now, so my main source for feat
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/tomasriveral/notewrapper)
![demo.gif](demo.gif)
## Why?
I started journaling and note-taking with **[Obsidian](https://obsidian.md/)** but wanted to use only free software. I also tried **[Logseq](https://logseq.com/)** and **[Joplin](https://github.com/laurent22/joplin)**, but I preferred a terminal-based workflow.
@@ -229,17 +231,18 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
* `git.enable`: enable git version control for backups
* `git.name` and `git.email` are for the commits signature
Note:
* Directories must end with `/`.
* All directories used must exist.
* Elements of `directory` and `backup.directory` must match exactly.
* Directories set in `directory` but not in `backup.directory` won't be backed up.
* Directories set in `backup.directory` but not in `directory` will be ignored.
* `journalRegex` must match if the file name ends with `.md` and if it doesn't.
* It is recommended to keep `-q` or `--quiet` flag in `rsyncArgs` to avoid interference with `ncurses`.
* If `rsync` fails, you will see it inside `ncurses`.
* If you want the git backup but without rsync, you can leave `backup.directory` empty or map each directory to itself. When enabled, git will apply to all your vaults.
* Each vault gets its own git repository that will be created the first time you select the vault with the option enabled.
> [!NOTE]
> * The git backup implementation relies a lot on the IO speed of your drive. A low speed one (or a web mounted one) could create blanks between closing a document and the TUI opening.
> * Directories must end with `/`.
> * All directories used must exist.
> * Elements of `directory` and `backup.directory` must match exactly.
> * Directories set in `directory` but not in `backup.directory` won't be backed up.
> * Directories set in `backup.directory` but not in `directory` will be ignored.
> * `journalRegex` must match if the file name ends with `.md` and if it doesn't.
> * It is recommended to keep `-q` or `--quiet` flag in `rsyncArgs` to avoid interference with `ncurses`.
> * If `rsync` fails, you will see it inside `ncurses`.
> * If you want the git backup but without rsync, you can leave `backup.directory` empty or map each directory to itself. When enabled, git will apply to all your vaults.
> * Each vault gets its own git repository that will be created the first time you select the vault with the option enabled.
---
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

+190
View File
@@ -0,0 +1,190 @@
Require echo
Set Shell "zsh"
Set TypingSpeed 35ms
Set FontSize 18
Set Width 1800
Set Height 900
Output demo.gif
# ─────────────────────────────────────────────────────────────────────
# Introduction
# ─────────────────────────────────────────────────────────────────────
Type "echo 'Welcome to NoteWrapper!'"
Sleep 800ms
Enter
Sleep 1500ms
Type "notewrapper --help"
Sleep 500ms
Enter
Sleep 4000ms
# ─────────────────────────────────────────────────────────────────────
# Create / open a unified journal
# ─────────────────────────────────────────────────────────────────────
Type "notewrapper"
Sleep 700ms
Enter
Sleep 1500ms
Down
Sleep 500ms
Down
Sleep 500ms
Down
Sleep 700ms
Enter
Sleep 1200ms
Down
Sleep 600ms
Enter
Sleep 1500ms
# Enter the editor
Type "i"
Sleep 800ms
Type "NoteWrapper uses standard Markdown files, so you can open your notes in any other application that supports Markdown."
Sleep 700ms
Enter
Sleep 800ms
Type "This is a unified journal: all your daily entries live in the same file. You can also configure NoteWrapper to use monthly or other time-based entries."
Sleep 700ms
Enter
Sleep 800ms
Type "And, although you can't see it in this demo, this Markdown file is rendered live in my browser thanks to Vivify!"
Sleep 1500ms
Sleep 3000ms
# Leave the editor
Escape
Sleep 500ms
Type ":q!"
Sleep 800ms
Enter
Sleep 1500ms
# ─────────────────────────────────────────────────────────────────────
# Create / open a divided journal
# ─────────────────────────────────────────────────────────────────────
Down
Sleep 500ms
Down
Sleep 500ms
Down
Sleep 700ms
Enter
Sleep 2000ms
Enter
Sleep 1000ms
Type "i"
Sleep 800ms
Type "And this is a divided journal."
Sleep 500ms
Type " It uses one file per entry."
Sleep 700ms
Enter
Sleep 1000ms
Type "Find the dates and journal divisions a little too cumbersome?"
Sleep 700ms
Enter
Sleep 1500ms
Type "Don't worry: NoteWrapper also works with basic notes."
Sleep 800ms
Type " It distinguishes them using a regular expression, which defaults to `.*journal.*`."
Sleep 800ms
Enter
Sleep 700ms
Type " The unified or divided journal mode is chosen when you first create the journal."
Sleep 1000ms
Enter
Sleep 1500ms
# ─────────────────────────────────────────────────────────────────────
# Search
# ─────────────────────────────────────────────────────────────────────
Type "Let me show you one last cool feature."
Sleep 1000ms
Type " Have you ever forgotten when or where you wrote something?"
Sleep 1000ms
Enter
Sleep 800ms
Type "Well ... NoteWrapper was made for you!"
Sleep 1500ms
Escape
Sleep 500ms
Type ":q!"
Sleep 800ms
Enter
Sleep 1500ms
# Navigate to search
Down
Sleep 500ms
Down
Sleep 500ms
Down
Sleep 800ms
Enter
Sleep 1500ms
Down
Sleep 500ms
Down
Sleep 800ms
Enter
Sleep 2000ms
# Search all notes with fzf
Type "c"
Sleep 300ms
Type "o"
Sleep 300ms
Type "o"
Sleep 300ms
Type "l"
Sleep 1500ms
# Show the search result / preview
Up
Sleep 2000ms
Enter
Sleep 1000ms
# ─────────────────────────────────────────────────────────────────────
# Final note
# ─────────────────────────────────────────────────────────────────────
Type "i"
Sleep 700ms
Type "Never lose a note."
Sleep 1500ms
Enter
Sleep 1000ms
Type "P. S. Don't mind the broken fzf preview. Normally, you'll see the context around your search result, but sometimes `sed` breaks when fzf is opened for the first time."
Sleep 5000ms
Generated
+3 -3
View File
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1786106723,
"narHash": "sha256-zDSUbpoeo/9ZmD2+wXnzxoo1+uhL8vxc0b8yuYMKYq0=",
"lastModified": 1786862985,
"narHash": "sha256-FBJRXmbGXiSUDvYEbfLYRkckayyZ6SK1UEqhCrIZ2Cs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f13ff45afd1bb73e640eaa08a7066dbed07e3238",
"rev": "e5bdc4a41d4c072fe1e3787eaa0320a384741d44",
"type": "github"
},
"original": {
+1
View File
@@ -53,6 +53,7 @@
clang-tools
gnumake
libgit2
vhs
];
};
+53 -2
View File
@@ -71,6 +71,26 @@ void _error(const int shouldDebug, const int condition, const char *type, const
exit(1);
}
}
void _warn(const int condition, const char *file, const int line, const char *function, const char *message, ...) { // use for warnings that should always be shown
if (condition) {
fflush(stdout);
fflush(stderr);
va_list args;
va_start(args, message);
int h, m, s;
getCurrentTime(&h, &m, &s);
fprintf(stdout, "\e[0;33m[WARNING -- %d:%d:%d] From file %s line %d function %s:\e[0m\n", h, m, s, file, line, function);
vfprintf(stdout, message, args);
fprintf(stdout, "\e[0m\n");
va_end(args);
}
}
static void copyDir(const char *source, const char *destination, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug) {
debug("Backuping... source: %s and destination: %s", source, destination);
@@ -113,6 +133,20 @@ static void ensureDir(const char *path, const int shouldDebug) {
}
}
static void slowGitWarning(struct timespec start, int *wasShown) {
if (!*wasShown) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
double elapsed = (double)(now.tv_sec - start.tv_sec) + (double)(now.tv_nsec - start.tv_nsec) / 1e9;
warn(elapsed > 0.3, "Git operations are taking too much time. You drive might be slow. Please do not stop the program.");
if (elapsed > 0.3) {
*wasShown = 1;
}
}
}
void ensureGitDirectory(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const int shouldDebug) {
git_repository *repo = NULL;
char *fullPath = malloc(PATH_MAX);
@@ -151,6 +185,10 @@ void ensureGitDirectory(const char *path, const char *vault, const char *signatu
}
void gitBackupUpdate(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const char *commitMsg, const int shouldDebug) {
// on slow drive or WebDav mounted drive this git operations are pretty slow. We'll just show a warning to avoid the user closing the program.
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start);
git_repository *repo = NULL;
git_index *index = NULL;
git_tree *tree = NULL;
@@ -162,6 +200,8 @@ void gitBackupUpdate(const char *path, const char *vault, const char *signatureN
git_oid tree_id;
git_oid commit_id;
int slowGitWarningAlreadyShown = 0;
char *fullPath = malloc(PATH_MAX);
error(fullPath == NULL, "program", "Failed to allocate path");
@@ -172,17 +212,21 @@ void gitBackupUpdate(const char *path, const char *vault, const char *signatureN
int return_code = git_repository_open(&repo, fullPath);
error(return_code, "program", "Failed to open Git repository\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
/* Get the index */
return_code = git_repository_index(&index, repo);
error(return_code, "program", "Failed to get Git index\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
/* git add . */
return_code = git_index_add_all(index, NULL, 0, NULL, NULL);
error(return_code, "program", "Failed to add files\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
return_code = git_index_write(index);
error(return_code, "program", "Failed to write index\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
/*
* Check whether there are changes to commit.
@@ -199,16 +243,19 @@ void gitBackupUpdate(const char *path, const char *vault, const char *signatureN
return_code = git_reference_name_to_id(&head_oid, repo, "HEAD");
error(return_code, "program", "Failed to get HEAD\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
return_code = git_commit_lookup(&head_commit, repo, &head_oid);
error(return_code, "program", "Failed to lookup HEAD commit\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
return_code = git_commit_tree(&head_tree, head_commit);
error(return_code, "program", "Failed to get HEAD tree\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
return_code = git_diff_tree_to_index(&diff, repo, head_tree, index, NULL);
error(return_code, "program", "Failed to create diff\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
has_changes = git_diff_num_deltas(diff) > 0;
}
@@ -222,13 +269,16 @@ void gitBackupUpdate(const char *path, const char *vault, const char *signatureN
/* index → tree */
return_code = git_index_write_tree(&tree_id, index);
error(return_code, "program", "Failed to write tree\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
return_code = git_tree_lookup(&tree, repo, &tree_id);
error(return_code, "program", "Failed to lookup tree\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
/* Author/committer */
return_code = git_signature_now(&signature, signatureName, signatureEmail);
error(return_code, "program", "Failed to create signature\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
/* Create commit */
if (git_repository_head_unborn(repo)) {
@@ -237,6 +287,7 @@ void gitBackupUpdate(const char *path, const char *vault, const char *signatureN
return_code = git_commit_create_v(&commit_id, repo, "HEAD", signature, signature, NULL, commitMsg, tree, 1, head_commit);
}
error(return_code, "program", "Failed to create commit\n%s", git_error_last()->message);
slowGitWarning(start, &slowGitWarningAlreadyShown);
debug("Created commit %s in %s", commitMsg, fullPath);
@@ -315,7 +366,7 @@ char *getDateAndTime() {
struct tm tm_now;
localtime_r(&now, &tm_now);
strftime(return_value, sizeof(return_value), "%Y-%m-%%d %H:%M:%%S", &tm_now);
strftime(return_value, 20, "%Y-%m-%d %H:%M:%S", &tm_now);
return return_value;
}
+3
View File
@@ -31,6 +31,7 @@
#define debug(message, ...) _debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
#define altDebug(message, ...) _altDebug(shouldDebug, message, ##__VA_ARGS__)
#define error(condition, type, message, ...) _error(shouldDebug, condition, type, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
#define warn(condition, message, ...) _warn(condition, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
// you must edit this two values if you want to add suport for an editor
extern const char *supportedEditor[]; // array of supported editors
extern const int numEditors; // number of supported editors
@@ -53,6 +54,8 @@ void _debug(const int d, const char *file, const int line, const char *function,
void _altDebug(const int d, const char *message, ...);
// formated error.
void _error(const int shouldDebug, const int condition, const char *type, const char *file, const int line, const char *function, const char *message, ...);
// formated warn
void _warn(const int condition, const char *file, const int line, const char *function, const char *message, ...);
// Returns 1 if the string is in the array.
// Returns 0 if the string is not in the array.
// If you want to only check the first n elements of the array, pass n as len.