From 7dd3e081c7dfd68014e713a6a37e81b38cd98a02 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Wed, 17 Aug 2011 22:39:28 -0400 Subject: [PATCH] Bug #804 - URL link and break line - Addressed bug #804 by allowing rc.hyphenate to control whether hyphens are inserted when long lines are broken. This may help prevent xterm from mis-parsing URLs in task annotations, when wrapped (thanks to Yann Davin). - Added unit tests. --- AUTHORS | 1 + ChangeLog | 3 ++ NEWS | 2 + doc/man/taskrc.5.in | 4 ++ src/Config.cpp | 1 + src/columns/ColDepends.cpp | 4 +- src/columns/ColDepends.h | 1 + src/columns/ColDescription.cpp | 10 +++-- src/columns/ColDescription.h | 1 + src/columns/ColProject.cpp | 4 +- src/columns/ColProject.h | 1 + src/columns/ColString.cpp | 6 ++- src/columns/ColString.h | 1 + src/columns/ColTags.cpp | 4 +- src/columns/ColTags.h | 1 + src/commands/CmdShow.cpp | 1 + src/text.cpp | 23 +++++++--- src/text.h | 4 +- test/bug.804.t | 79 ++++++++++++++++++++++++++++++++++ test/text.t.cpp | 24 +++++------ 20 files changed, 147 insertions(+), 28 deletions(-) create mode 100755 test/bug.804.t diff --git a/AUTHORS b/AUTHORS index 71755ee88..60be6ab89 100644 --- a/AUTHORS +++ b/AUTHORS @@ -105,4 +105,5 @@ suggestions: Wim Schuermann Tom Duffy Miguel de Val Borro + Yann Davin diff --git a/ChangeLog b/ChangeLog index 802a2e3e6..d48ee57f5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -162,6 +162,9 @@ (thanks to Bryce Harrington). + Applied patch for #803, allowing rc.confirmation to bypass confirmation of the deletion of a recurring task (thanks to Matt Kraai). + + Addressed bug #804 by allowing rc.hyphenate to control whether hyphens are + inserted when long lines are broken. This may help prevent xterm from + mis-parsing URLs in task annotations, when wrapped (thanks to Yann Davin). + Fixed bug #807, which caused a lack of Lua to prevent tests from building (thanks to Owen Clarke). + Fixed bug #808, which generated compiler warnings on Solarix (thanks to diff --git a/NEWS b/NEWS index 9901aadfd..b297d0e63 100644 --- a/NEWS +++ b/NEWS @@ -61,6 +61,8 @@ New configuration options in taskwarrior 2.0.0 - New 'color.completed' and 'color.deleted' color rules. - New 'abbreviation.minimum' setting controls how short an abbreviated command or value may be. + - New 'hyphenate' setting controls whether long lines are hyphenated when + broken during text-wrapping. Newly deprecated features in taskwarrior 2.0.0 diff --git a/doc/man/taskrc.5.in b/doc/man/taskrc.5.in index e26eabbf1..3a4f27b27 100644 --- a/doc/man/taskrc.5.in +++ b/doc/man/taskrc.5.in @@ -177,6 +177,10 @@ Causes the width of the terminal minus one to be used as the full width. This avoids placing color codes in the last column which can cause problems for Cygwin users. Default value is 'no'. +.TP +.B hyphenate=on +Hyphenates lines when wrapping breaks occur mid-word. Default value is 'on'. + .TP .B editor=vi Specifies which text editor you wish to use for when the diff --git a/src/Config.cpp b/src/Config.cpp index 7a9007a38..9f53f0cda 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -72,6 +72,7 @@ std::string Config::defaults = "detection=on # Detects terminal width\n" "defaultwidth=80 # Without detection, assumed width\n" "avoidlastcolumn=no # Fixes Cygwin width problem\n" + "hyphenate=on # Hyphenates lines wrapped on non-word-breaks\n" "#editor=vi # Preferred text editor\n" "edit.verbose=yes # Include comments in files created during task edit\n" "\n" diff --git a/src/columns/ColDepends.cpp b/src/columns/ColDepends.cpp index e195258ad..55be13928 100644 --- a/src/columns/ColDepends.cpp +++ b/src/columns/ColDepends.cpp @@ -51,6 +51,8 @@ ColumnDepends::ColumnDepends () _examples.push_back ("1 2 10"); _examples.push_back ("[3]"); _examples.push_back (context.config.get ("dependency.indicator")); + + _hyphenate = context.config.getBoolean ("hyphenate"); } //////////////////////////////////////////////////////////////////////////////// @@ -150,7 +152,7 @@ void ColumnDepends::render ( join (combined, " ", blocking_ids); std::vector all; - wrapText (all, combined, width); + wrapText (all, combined, width, _hyphenate); std::vector ::iterator i; for (i = all.begin (); i != all.end (); ++i) diff --git a/src/columns/ColDepends.h b/src/columns/ColDepends.h index 925ae218b..614b7e3ec 100644 --- a/src/columns/ColDepends.h +++ b/src/columns/ColDepends.h @@ -46,6 +46,7 @@ public: void render (std::vector &, Task&, int, Color&); private: + bool _hyphenate; }; #endif diff --git a/src/columns/ColDescription.cpp b/src/columns/ColDescription.cpp index 3220b4ca0..30250bfed 100644 --- a/src/columns/ColDescription.cpp +++ b/src/columns/ColDescription.cpp @@ -71,6 +71,8 @@ ColumnDescription::ColumnDescription () + " " + t + " " + a4); _examples.push_back (d.substr (0, 20) + "..."); _examples.push_back (d + " [4]"); + + _hyphenate = context.config.getBoolean ("hyphenate"); } //////////////////////////////////////////////////////////////////////////////// @@ -199,7 +201,7 @@ void ColumnDescription::render ( } std::vector raw; - wrapText (raw, description, width); + wrapText (raw, description, width, _hyphenate); std::vector ::iterator i; for (i = raw.begin (); i != raw.end (); ++i) @@ -210,7 +212,7 @@ void ColumnDescription::render ( else if (_style == "desc") { std::vector raw; - wrapText (raw, description, width); + wrapText (raw, description, width, _hyphenate); std::vector ::iterator i; for (i = raw.begin (); i != raw.end (); ++i) @@ -237,7 +239,7 @@ void ColumnDescription::render ( } std::vector raw; - wrapText (raw, description, width); + wrapText (raw, description, width, _hyphenate); std::vector ::iterator i; for (i = raw.begin (); i != raw.end (); ++i) @@ -264,7 +266,7 @@ void ColumnDescription::render ( description += " [" + format ((int) annos.size ()) + "]"; std::vector raw; - wrapText (raw, description, width); + wrapText (raw, description, width, _hyphenate); std::vector ::iterator i; for (i = raw.begin (); i != raw.end (); ++i) diff --git a/src/columns/ColDescription.h b/src/columns/ColDescription.h index bd68c7a5f..8652feea4 100644 --- a/src/columns/ColDescription.h +++ b/src/columns/ColDescription.h @@ -45,6 +45,7 @@ public: void render (std::vector &, Task&, int, Color&); private: + bool _hyphenate; }; #endif diff --git a/src/columns/ColProject.cpp b/src/columns/ColProject.cpp index 3e73a93a3..9b8787ef5 100644 --- a/src/columns/ColProject.cpp +++ b/src/columns/ColProject.cpp @@ -47,6 +47,8 @@ ColumnProject::ColumnProject () _examples.push_back (STRING_COLUMN_EXAMPLES_PROJ); _examples.push_back (STRING_COLUMN_EXAMPLES_PAR); + + _hyphenate = context.config.getBoolean ("hyphenate"); } //////////////////////////////////////////////////////////////////////////////// @@ -96,7 +98,7 @@ void ColumnProject::render ( } std::vector raw; - wrapText (raw, project, width); + wrapText (raw, project, width, _hyphenate); std::vector ::iterator i; for (i = raw.begin (); i != raw.end (); ++i) diff --git a/src/columns/ColProject.h b/src/columns/ColProject.h index 461b12a5c..5330f041b 100644 --- a/src/columns/ColProject.h +++ b/src/columns/ColProject.h @@ -45,6 +45,7 @@ public: void render (std::vector &, Task&, int, Color&); private: + bool _hyphenate; }; #endif diff --git a/src/columns/ColString.cpp b/src/columns/ColString.cpp index 68beee445..6174cb176 100644 --- a/src/columns/ColString.cpp +++ b/src/columns/ColString.cpp @@ -51,6 +51,8 @@ ColumnString::ColumnString () _styles.push_back (" Hello (wrapped)"); _styles.push_back ("Hello (no-wrap) "); _styles.push_back (" Hello (no-wrap)"); + + _hyphenate = context.config.getBoolean ("hyphenate"); } //////////////////////////////////////////////////////////////////////////////// @@ -96,7 +98,7 @@ void ColumnString::render ( if (_style == "default" || _style == "left") { std::vector raw; - wrapText (raw, value, width); + wrapText (raw, value, width, _hyphenate); std::vector ::iterator i; for (i = raw.begin (); i != raw.end (); ++i) @@ -105,7 +107,7 @@ void ColumnString::render ( else if (_style == "right") { std::vector raw; - wrapText (raw, value, width); + wrapText (raw, value, width, _hyphenate); std::vector ::iterator i; for (i = raw.begin (); i != raw.end (); ++i) diff --git a/src/columns/ColString.h b/src/columns/ColString.h index 4fb17bbe1..3469d2ae4 100644 --- a/src/columns/ColString.h +++ b/src/columns/ColString.h @@ -45,6 +45,7 @@ public: void render (std::vector &, const std::string&, int, Color&); private: + bool _hyphenate; }; #endif diff --git a/src/columns/ColTags.cpp b/src/columns/ColTags.cpp index acb98f866..ebd50bfe5 100644 --- a/src/columns/ColTags.cpp +++ b/src/columns/ColTags.cpp @@ -50,6 +50,8 @@ ColumnTags::ColumnTags () _examples.push_back (STRING_COLUMN_EXAMPLES_TAGS); _examples.push_back (context.config.get ("tag.indicator")); _examples.push_back ("[2]"); + + _hyphenate = context.config.getBoolean ("hyphenate"); } //////////////////////////////////////////////////////////////////////////////// @@ -136,7 +138,7 @@ void ColumnTags::render ( { std::replace (tags.begin (), tags.end (), ',', ' '); std::vector all; - wrapText (all, tags, width); + wrapText (all, tags, width, _hyphenate); std::vector ::iterator i; for (i = all.begin (); i != all.end (); ++i) diff --git a/src/columns/ColTags.h b/src/columns/ColTags.h index 7c4180b0e..144099fa8 100644 --- a/src/columns/ColTags.h +++ b/src/columns/ColTags.h @@ -46,6 +46,7 @@ public: void render (std::vector &, Task&, int, Color&); private: + bool _hyphenate; }; #endif diff --git a/src/commands/CmdShow.cpp b/src/commands/CmdShow.cpp index 275ab65d6..301182c2e 100644 --- a/src/commands/CmdShow.cpp +++ b/src/commands/CmdShow.cpp @@ -151,6 +151,7 @@ int CmdShow::execute (std::string& output) " extensions" " fontunderline" " gc" + " hyphenate" " import.synonym.bg" " import.synonym.description" " import.synonym.due" diff --git a/src/text.cpp b/src/text.cpp index 417645248..aab6edc6a 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -52,7 +52,8 @@ static void replace_positional (std::string&, const std::string&, const std::str void wrapText ( std::vector & lines, const std::string& text, - const int width) + const int width, + bool hyphenate) { std::string copy = text; std::string line; @@ -61,7 +62,7 @@ void wrapText ( while (copy.length ()) // Used as Boolean, therefore UTF8 safe. { - extractLine (copy, line, modified_width); + extractLine (copy, line, modified_width, hyphenate); lines.push_back (line); } } @@ -316,7 +317,11 @@ int longestLine (const std::string& input) } //////////////////////////////////////////////////////////////////////////////// -void extractLine (std::string& text, std::string& line, int length) +void extractLine ( + std::string& text, + std::string& line, + int length, + bool hyphenate) { size_t eol = text.find ("\n"); @@ -356,8 +361,16 @@ void extractLine (std::string& text, std::string& line, int length) { if (length > 1) { - line = text.substr (0, length - 1) + "-"; - text = text.substr (length - 1); + if (hyphenate) + { + line = text.substr (0, length - 1) + "-"; + text = text.substr (length - 1); + } + else + { + line = text.substr (0, length); + text = text.substr (length); + } } else { diff --git a/src/text.h b/src/text.h index 346877f04..bda520f93 100644 --- a/src/text.h +++ b/src/text.h @@ -33,14 +33,14 @@ #include // text.cpp, Non-UTF-8 aware. -void wrapText (std::vector &, const std::string&, const int); +void wrapText (std::vector &, const std::string&, const int, bool); std::string trimLeft (const std::string& in, const std::string& t = " "); std::string trimRight (const std::string& in, const std::string& t = " "); std::string trim (const std::string& in, const std::string& t = " "); std::string unquoteText (const std::string&); int longestWord (const std::string&); int longestLine (const std::string&); -void extractLine (std::string&, std::string&, int); +void extractLine (std::string&, std::string&, int, bool); void splitq (std::vector&, const std::string&, const char); void split (std::vector&, const std::string&, const char); void split (std::vector&, const std::string&, const std::string&); diff --git a/test/bug.804.t b/test/bug.804.t new file mode 100755 index 000000000..c13b1056a --- /dev/null +++ b/test/bug.804.t @@ -0,0 +1,79 @@ +#! /usr/bin/perl +################################################################################ +## taskwarrior - a command line task list manager. +## +## Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez. +## All rights reserved. +## +## This program is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free Software +## Foundation; either version 2 of the License, or (at your option) any later +## version. +## +## This program is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## this program; if not, write to the +## +## Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, +## Boston, MA +## 02110-1301 +## USA +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 11; + +# Create the rc file. +if (open my $fh, '>', 'bug.rc') +{ + print $fh "data.location=.\n"; + print $fh "bulk=100\n"; + print $fh "confirmation=no\n"; + close $fh; + ok (-r 'bug.rc', 'Created bug.rc'); +} + +# Bug 804: URL link and break line + +# Setup: Add a tasks, annotate with long word. +qx{../src/task rc:bug.rc add One}; +qx{../src/task rc:bug.rc 1 annotate abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz}; + +# List with rc.hyphenate=on. +my $output = qx{../src/task rc:bug.rc rc.defaultwidth:40 rc.hyphenate:on ls}; +like ($output, qr/vwx-$/ms, 'hyphenated 1'); +like ($output, qr/tuv-$/ms, 'hyphenated 2'); + +# List with rc.hyphenate=off. +$output = qx{../src/task rc:bug.rc rc.defaultwidth:40 rc.hyphenate:off ls}; +like ($output, qr/vwxy$/ms, 'not hyphenated 1'); +like ($output, qr/uvwx$/ms, 'not hyphenated 2'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'completed.data'; +ok (!-r 'completed.data', 'Removed completed.data'); + +unlink 'undo.data'; +ok (!-r 'undo.data', 'Removed undo.data'); + +unlink 'backlog.data'; +ok (!-r 'backlog.data', 'Removed backlog.data'); + +unlink 'synch.key'; +ok (!-r 'synch.key', 'Removed synch.key'); + +unlink 'bug.rc'; +ok (!-r 'bug.rc', 'Removed bug.rc'); + +exit 0; + diff --git a/test/text.t.cpp b/test/text.t.cpp index 244785fcf..abeafd09d 100644 --- a/test/text.t.cpp +++ b/test/text.t.cpp @@ -37,10 +37,10 @@ int main (int argc, char** argv) { UnitTest t (262); - // void wrapText (std::vector & lines, const std::string& text, const int width) + // void wrapText (std::vector & lines, const std::string& text, const int width, bool hyphenate) std::string text = "This is a test of the line wrapping code."; std::vector lines; - wrapText (lines, text, 10); + wrapText (lines, text, 10, true); t.is (lines.size (), (size_t) 5, "wrapText 'This is a test of the line wrapping code.' -> total 5 lines"); t.is (lines[0], "This is a", "wrapText line 0 -> 'This is a'"); t.is (lines[1], "test of", "wrapText line 1 -> 'test of'"); @@ -51,7 +51,7 @@ int main (int argc, char** argv) // void wrapText (std::vector & lines, const std::string& text, const int width) text = "This ☺ is a test of utf8 line extraction."; lines.clear (); - wrapText (lines, text, 7); + wrapText (lines, text, 7, true); t.is (lines.size (), (size_t) 7, "wrapText 'This ☺ is a test of utf8 line extraction.' -> total 7 lines"); t.is (lines[0], "This ☺", "wrapText line 0 -> 'This ☺'"); t.is (lines[1], "is a", "wrapText line 1 -> 'is a'"); @@ -61,33 +61,33 @@ int main (int argc, char** argv) t.is (lines[5], "extrac-", "wrapText line 5 -> 'extrac-'"); t.is (lines[6], "tion.", "wrapText line 6 -> 'tion.'"); - // void extractLine (std::string& text, std::string& line, int length) + // void extractLine (std::string& text, std::string& line, int length, bool hyphenate) text = "This ☺ is a test of utf8 line extraction."; std::string line; - extractLine (text, line, 7); + extractLine (text, line, 7, true); t.is (line, "line 1", "extractLine 7 'This ☺ is a test of utf8 line extraction.' -> 'This ☺'"); // void extractLine (std::string& text, std::string& line, int length) text = "line 1\nlengthy second line that exceeds width"; - extractLine (text, line, 10); + extractLine (text, line, 10, true); t.is (line, "line 1", "extractLine 10 'line 1\\nlengthy second line that exceeds width' -> 'line 1'"); - extractLine (text, line, 10); + extractLine (text, line, 10, true); t.is (line, "lengthy", "extractLine 10 'lengthy second line that exceeds width' -> 'lengthy'"); - extractLine (text, line, 10); + extractLine (text, line, 10, true); t.is (line, "second", "extractLine 10 'second line that exceeds width' -> 'second'"); - extractLine (text, line, 10); + extractLine (text, line, 10, true); t.is (line, "line that", "extractLine 10 'line that exceeds width' -> 'line that'"); - extractLine (text, line, 10); + extractLine (text, line, 10, true); t.is (line, "exceeds", "extractLine 10 'exceeds width' -> 'exceeds'"); - extractLine (text, line, 10); + extractLine (text, line, 10, true); t.is (line, "width", "extractLine 10 'width' -> 'width'"); - extractLine (text, line, 10); + extractLine (text, line, 10, true); t.is (line, "", "extractLine 10 '' -> ''"); // void split (std::vector& results, const std::string& input, const char delimiter)