diff --git a/html/advanced.html b/html/advanced.html index fd5c3180e..bccf1e739 100644 --- a/html/advanced.html +++ b/html/advanced.html @@ -433,6 +433,22 @@ ID Project Pri Description command.

+ % task <id> edit +

+ This command allows you to use your text editor to edit all aspects + of a task. The specified task will be written to a file, and your + text editor will be invoked. If you modify the task in the text + editor, task will update accordingly. +

+

+ Task will first check to see if you have defined a text editor + in the TASK_EDITOR environment variable. If not, task will + check to see if you defined a text editor in the VISUAL + environment variable. If not task will check to see if you + defined a text editor in the EDITOR environment variable. + If all those fail, task launches vi. +

+ % task <id> fg:... bg:...

Not strictly a command, the setting of the fg and bg (foreground diff --git a/src/command.cpp b/src/command.cpp index 7c164bb92..19ec92fc6 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -359,7 +359,7 @@ std::string handleVersion (Config& conf) "color.pri.L color.pri.M color.pri.none color.recurring color.tagged " "confirmation curses data.location dateformat default.command " "default.priority defaultwidth due echo.command locking monthsperline nag " - "next project shadow.command shadow.file shadow.notify weekstart " + "next project shadow.command shadow.file shadow.notify weekstart editor " "import.synonym.id import.synonym.uuid import.synonym.status " "import.synonym.tags import.synonym.entry import.synonym.start " "import.synonym.due import.synonym.recur import.synonym.end " @@ -860,6 +860,118 @@ std::string handleDuplicate (TDB& tdb, T& task, Config& conf) return out.str (); } +//////////////////////////////////////////////////////////////////////////////// +static const char* leftDelim = "<<"; +static const char* rightDelim = ">>"; +static std::string findValue (const std::string& text, const std::string& name) +{ + // Look for /^\s+name:\s+<<(.*)>>/ + // Extract + // Trim + // Join + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +// Introducing the Silver Bullet. This feature is the catch-all fixative for +// various other ills. This is like opening up the hood and going in with a +// wrench. To be used sparingly. +std::string handleEdit (TDB& tdb, T& task, Config& conf) +{ + std::stringstream out; + std::vector all; + tdb.pendingT (all); + + filterSequence (all, task); + foreach (seq, all) + { + // Check for file permissions. + std::string dataLocation = expandPath (conf.get ("data.location")); + if (access (dataLocation.c_str (), X_OK)) + throw std::string ("Your data.location directory is not writable."); + + // Create a temp file name in data.location. + std::stringstream pattern; + pattern << dataLocation << "/task." << seq->getId () << ".XXXXXX"; + char cpattern [PATH_MAX]; + strcpy (cpattern, pattern.str ().c_str ()); + char* file = mktemp (cpattern); + + // Format the contents, T -> text. + std::stringstream before; + before << "# The 'task edit ' command allows you to modify all aspects of a task" << std::endl + << "# using a text editor. What is shown below is a representation of the" << std::endl + << "# task in all it's detail. Modify what you wish, and if you save and" << std::endl + << "# quit your editor, task will read this file and try to make sense of" << std::endl + << "# what changed, and apply those changes. If you quit your editor without" << std::endl + << "# saving or making any modifications, task will do nothing." << std::endl + << "#" << std::endl + << "# Lines that begin with # are comments, and will be ignored by task." << std::endl + + << "# Edit only the items within the marks " + << leftDelim << " " << rightDelim << "." + << "All other edits will be ignored." << std::endl + + << "# Externally Visible" << std::endl + << " ID: " << seq->getId () << std::endl + << " Status: " << leftDelim << seq->getStatus () << rightDelim << std::endl + << " Project: " << leftDelim << seq->getAttribute ("project") << rightDelim << std::endl + << " Priority: " << leftDelim << seq->getAttribute ("priority") << rightDelim << std::endl; + + std::vector tags; + seq->getTags (tags); + std::string allTags; + join (allTags, " ", tags); + before << " Tags: " << leftDelim << allTags << rightDelim << std::endl; + + std::map annotations; + seq->getAnnotations (annotations); + foreach (anno, annotations) + before << " Annotation: " << leftDelim << anno->first << " " << anno->second << rightDelim << std::endl; + + before << " Description: " << leftDelim << seq->getDescription () << rightDelim << std::endl + + << "# Internals" << std::endl + << " Start: " << leftDelim << seq->getAttribute ("start") << rightDelim << std::endl + << " End: " << leftDelim << seq->getAttribute ("end") << rightDelim << std::endl + << " Due: " << leftDelim << seq->getAttribute ("due") << rightDelim << std::endl + << " Recur: " << leftDelim << seq->getAttribute ("recur") << rightDelim << std::endl + << " Mask: " << leftDelim << seq->getAttribute ("mask") << rightDelim << std::endl + << " iMask: " << leftDelim << seq->getAttribute ("imask") << rightDelim << std::endl; + + // Write to file. + spit (file, before.str ()); + + // Determine correct editor: .taskrc:editor > $VISUAL > $EDITOR > vi + std::string editor = conf.get ("editor", ""); + if (editor == "") editor = getenv ("VISUAL"); + if (editor == "") editor = getenv ("EDITOR"); + if (editor == "") editor = "vi"; + + // Launch the editor. + editor += " "; + editor += file; + system (editor.c_str ()); + + // Slurp file. + std::string after; + slurp (file, after, true); + + // Update seq based on what can be parsed back out of the file. + seq->setAttribute ("Project", findValue (after, "Project")); + seq->setAttribute ("Priority", findValue (after, "Priority")); + seq->setDescription ( findValue (after, "Description")); + + // Modify task. + tdb.modifyT (*seq); + + // Cleanup. + unlink (file); + } + + return out.str (); +} + //////////////////////////////////////////////////////////////////////////////// std::string handleColor (Config& conf) { diff --git a/src/parse.cpp b/src/parse.cpp index fed5f515b..ae10e2be5 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -129,6 +129,7 @@ static const char* commands[] = "delete", "done", "duplicate", + "edit", "export", "help", "history", diff --git a/src/task.cpp b/src/task.cpp index 4a9481b97..4d2390154 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -108,6 +108,10 @@ static std::string shortUsage (Config& conf) table.addCell (row, 1, "task ID /from/to/g"); table.addCell (row, 2, "Performs all substitutions on the task description, for fixing mistakes"); + row = table.addRow (); + table.addCell (row, 1, "task edit ID"); + table.addCell (row, 2, "Launches an editor to let you modify all aspects of a task directly, therefore it is to be used carefully"); + row = table.addRow (); table.addCell (row, 1, "task duplicate ID [tags] [attrs] [desc...]"); table.addCell (row, 2, "Duplicates the specified task, and allows modifications"); @@ -889,6 +893,7 @@ std::string runTaskCommand ( else if (command == "undo") { cmdMod = true; out = handleUndo (tdb, task, conf); } else if (command == "import") { cmdMod = true; out = handleImport (tdb, task, conf); } else if (command == "duplicate") { cmdMod = true; out = handleDuplicate (tdb, task, conf); } + else if (command == "edit") { cmdMod = true; out = handleEdit (tdb, task, conf); } // Command that display IDs and therefore need TDB::gc first. else if (command == "completed") { if (gc) gcMod = tdb.gc (); out = handleCompleted (tdb, task, conf); } diff --git a/src/task.h b/src/task.h index a2b60c69d..803aac0e6 100644 --- a/src/task.h +++ b/src/task.h @@ -90,6 +90,7 @@ std::string handleUndo (TDB&, T&, Config&); std::string handleColor (Config&); std::string handleAnnotate (TDB&, T&, Config&); std::string handleDuplicate (TDB&, T&, Config&); +std::string handleEdit (TDB&, T&, Config&); T findT (int, const std::vector &); int deltaAppend (T&, T&); int deltaDescription (T&, T&); @@ -151,6 +152,8 @@ std::string expandPath (const std::string&); #endif bool slurp (const std::string&, std::vector &, bool trimLines = false); +bool slurp (const std::string&, std::string&, bool trimLines = false); +void spit (const std::string&, const std::string&); // rules.cpp void initializeColorRules (Config&); diff --git a/src/util.cpp b/src/util.cpp index 21c4717ee..d5f7ed04e 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -434,3 +434,41 @@ bool slurp ( } //////////////////////////////////////////////////////////////////////////////// +bool slurp ( + const std::string& file, + std::string& contents, + bool trimLines /* = false */) +{ + contents = ""; + + std::ifstream in (file.c_str ()); + if (in.good ()) + { + std::string line; + while (getline (in, line)) + { + if (trimLines) line = trim (line); + contents += line; + } + + in.close (); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +void spit (const std::string& file, const std::string& contents) +{ + std::ofstream out (file.c_str ()); + if (out.good ()) + { + out << contents; + out.close (); + } + else + throw std::string ("Could not write file '") + file + "'"; +} + +////////////////////////////////////////////////////////////////////////////////