Merge pull request #2698 from esdrubal/iosxmlarray
[mono.git] / mcs / class / Mono.Options / Mono.Options / Options.cs
index 5624c76e55e43802451427365e9e2420a27dc256..23ee99324e85eb9dbcd3c37be7d7c63f4c6857dd 100644 (file)
@@ -3,8 +3,12 @@
 //
 // Authors:
 //  Jonathan Pryor <jpryor@novell.com>
+//  Federico Di Gregorio <fog@initd.org>
+//  Rolf Bjarne Kvinge <rolf@xamarin.com>
 //
 // Copyright (C) 2008 Novell (http://www.novell.com)
+// Copyright (C) 2009 Federico Di Gregorio.
+// Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -27,8 +31,8 @@
 //
 
 // Compile With:
-//   gmcs -debug+ -r:System.Core Options.cs -o:Mono.Options.dll
-//   gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:Mono.Options.dll
+//   gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll
+//   gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll
 //
 // The LINQ version just changes the implementation of
 // OptionSet.Parse(IEnumerable<string>), and confers no semantic changes.
@@ -36,7 +40,7 @@
 //
 // A Getopt::Long-inspired option parsing library for C#.
 //
-// Mono.Options.OptionSet is built upon a key/value table, where the
+// NDesk.Options.OptionSet is built upon a key/value table, where the
 // key is a option format string and the value is a delegate that is 
 // invoked when the format string is matched.
 //
@@ -131,7 +135,11 @@ using System.ComponentModel;
 using System.Globalization;
 using System.IO;
 using System.Runtime.Serialization;
+#if PCL
+using System.Reflection;
+#else
 using System.Security.Permissions;
+#endif
 using System.Text;
 using System.Text.RegularExpressions;
 
@@ -140,10 +148,102 @@ using System.Linq;
 #endif
 
 #if TEST
-using Mono.Options;
+using NDesk.Options;
 #endif
 
-namespace Mono.Options {
+#if PCL
+using MessageLocalizerConverter = System.Func<string, string>;
+#else
+using MessageLocalizerConverter = System.Converter<string, string>;
+#endif
+
+#if NDESK_OPTIONS
+namespace NDesk.Options
+#else
+namespace Mono.Options
+#endif
+{
+       static class StringCoda {
+
+               public static IEnumerable<string> WrappedLines (string self, params int[] widths)
+               {
+                       IEnumerable<int> w = widths;
+                       return WrappedLines (self, w);
+               }
+
+               public static IEnumerable<string> WrappedLines (string self, IEnumerable<int> widths)
+               {
+                       if (widths == null)
+                               throw new ArgumentNullException ("widths");
+                       return CreateWrappedLinesIterator (self, widths);
+               }
+
+               private static IEnumerable<string> CreateWrappedLinesIterator (string self, IEnumerable<int> widths)
+               {
+                       if (string.IsNullOrEmpty (self)) {
+                               yield return string.Empty;
+                               yield break;
+                       }
+                       using (IEnumerator<int> ewidths = widths.GetEnumerator ()) {
+                               bool? hw = null;
+                               int width = GetNextWidth (ewidths, int.MaxValue, ref hw);
+                               int start = 0, end;
+                               do {
+                                       end = GetLineEnd (start, width, self);
+                                       char c = self [end-1];
+                                       if (char.IsWhiteSpace (c))
+                                               --end;
+                                       bool needContinuation = end != self.Length && !IsEolChar (c);
+                                       string continuation = "";
+                                       if (needContinuation) {
+                                               --end;
+                                               continuation = "-";
+                                       }
+                                       string line = self.Substring (start, end - start) + continuation;
+                                       yield return line;
+                                       start = end;
+                                       if (char.IsWhiteSpace (c))
+                                               ++start;
+                                       width = GetNextWidth (ewidths, width, ref hw);
+                               } while (start < self.Length);
+                       }
+               }
+
+               private static int GetNextWidth (IEnumerator<int> ewidths, int curWidth, ref bool? eValid)
+               {
+                       if (!eValid.HasValue || (eValid.HasValue && eValid.Value)) {
+                               curWidth = (eValid = ewidths.MoveNext ()).Value ? ewidths.Current : curWidth;
+                               // '.' is any character, - is for a continuation
+                               const string minWidth = ".-";
+                               if (curWidth < minWidth.Length)
+                                       throw new ArgumentOutOfRangeException ("widths",
+                                                       string.Format ("Element must be >= {0}, was {1}.", minWidth.Length, curWidth));
+                               return curWidth;
+                       }
+                       // no more elements, use the last element.
+                       return curWidth;
+               }
+
+               private static bool IsEolChar (char c)
+               {
+                       return !char.IsLetterOrDigit (c);
+               }
+
+               private static int GetLineEnd (int start, int length, string description)
+               {
+                       int end = System.Math.Min (start + length, description.Length);
+                       int sep = -1;
+                       for (int i = start; i < end; ++i) {
+                               if (description [i] == '\n')
+                                       return i+1;
+                               if (IsEolChar (description [i]))
+                                       sep = i+1;
+                       }
+                       if (sep == -1 || end == description.Length)
+                               return end;
+                       return sep;
+               }
+       }
 
        public class OptionValueCollection : IList, IList<string> {
 
@@ -284,13 +384,19 @@ namespace Mono.Options {
                OptionValueType type;
                int count;
                string[] separators;
+               bool hidden;
 
                protected Option (string prototype, string description)
-                       : this (prototype, description, 1)
+                       : this (prototype, description, 1, false)
                {
                }
 
                protected Option (string prototype, string description, int maxValueCount)
+                       : this (prototype, description, maxValueCount, false)
+               {
+               }
+
+               protected Option (string prototype, string description, int maxValueCount, bool hidden)
                {
                        if (prototype == null)
                                throw new ArgumentNullException ("prototype");
@@ -300,10 +406,19 @@ namespace Mono.Options {
                                throw new ArgumentOutOfRangeException ("maxValueCount");
 
                        this.prototype   = prototype;
-                       this.names       = prototype.Split ('|');
                        this.description = description;
                        this.count       = maxValueCount;
+                       this.names       = (this is OptionSet.Category)
+                               // append GetHashCode() so that "duplicate" categories have distinct
+                               // names, e.g. adding multiple "" categories should be valid.
+                               ? new[]{prototype + this.GetHashCode ()}
+                               : prototype.Split ('|');
+
+                       if (this is OptionSet.Category)
+                               return;
+
                        this.type        = ParsePrototype ();
+                       this.hidden      = hidden;
 
                        if (this.count == 0 && type != OptionValueType.None)
                                throw new ArgumentException (
@@ -326,6 +441,7 @@ namespace Mono.Options {
                public string           Description     {get {return description;}}
                public OptionValueType  OptionValueType {get {return type;}}
                public int              MaxValueCount   {get {return count;}}
+               public bool             Hidden          {get {return hidden;}}
 
                public string[] GetNames ()
                {
@@ -341,17 +457,38 @@ namespace Mono.Options {
 
                protected static T Parse<T> (string value, OptionContext c)
                {
-                       TypeConverter conv = TypeDescriptor.GetConverter (typeof (T));
+                       Type tt = typeof (T);
+#if PCL
+                       TypeInfo ti = tt.GetTypeInfo ();
+#else
+                       Type ti = tt;
+#endif
+                       bool nullable = 
+                               ti.IsValueType && 
+                               ti.IsGenericType && 
+                               !ti.IsGenericTypeDefinition && 
+                               ti.GetGenericTypeDefinition () == typeof (Nullable<>);
+#if PCL
+                       Type targetType = nullable ? tt.GenericTypeArguments [0] : tt;
+#else
+                       Type targetType = nullable ? tt.GetGenericArguments () [0] : tt;
+#endif
                        T t = default (T);
                        try {
-                               if (value != null)
+                               if (value != null) {
+#if PCL
+                                       t = (T) Convert.ChangeType (value, targetType);
+#else
+                                       TypeConverter conv = TypeDescriptor.GetConverter (targetType);
                                        t = (T) conv.ConvertFromString (value);
+#endif
+                               }
                        }
                        catch (Exception e) {
                                throw new OptionException (
                                                string.Format (
                                                        c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
-                                                       value, typeof (T).Name, c.OptionName),
+                                                       value, targetType.Name, c.OptionName),
                                                c.OptionName, e);
                        }
                        return t;
@@ -451,7 +588,99 @@ namespace Mono.Options {
                }
        }
 
+       public abstract class ArgumentSource {
+
+               protected ArgumentSource ()
+               {
+               }
+
+               public abstract string[] GetNames ();
+               public abstract string Description { get; }
+               public abstract bool GetArguments (string value, out IEnumerable<string> replacement);
+
+#if !PCL
+               public static IEnumerable<string> GetArgumentsFromFile (string file)
+               {
+                       return GetArguments (File.OpenText (file), true);
+               }
+#endif
+
+               public static IEnumerable<string> GetArguments (TextReader reader)
+               {
+                       return GetArguments (reader, false);
+               }
+
+               // Cribbed from mcs/driver.cs:LoadArgs(string)
+               static IEnumerable<string> GetArguments (TextReader reader, bool close)
+               {
+                       try {
+                               StringBuilder arg = new StringBuilder ();
+
+                               string line;
+                               while ((line = reader.ReadLine ()) != null) {
+                                       int t = line.Length;
+
+                                       for (int i = 0; i < t; i++) {
+                                               char c = line [i];
+                                               
+                                               if (c == '"' || c == '\'') {
+                                                       char end = c;
+                                                       
+                                                       for (i++; i < t; i++){
+                                                               c = line [i];
+
+                                                               if (c == end)
+                                                                       break;
+                                                               arg.Append (c);
+                                                       }
+                                               } else if (c == ' ') {
+                                                       if (arg.Length > 0) {
+                                                               yield return arg.ToString ();
+                                                               arg.Length = 0;
+                                                       }
+                                               } else
+                                                       arg.Append (c);
+                                       }
+                                       if (arg.Length > 0) {
+                                               yield return arg.ToString ();
+                                               arg.Length = 0;
+                                       }
+                               }
+                       }
+                       finally {
+                               if (close)
+                                       reader.Dispose ();
+                       }
+               }
+       }
+
+#if !PCL
+       public class ResponseFileSource : ArgumentSource {
+
+               public override string[] GetNames ()
+               {
+                       return new string[]{"@file"};
+               }
+
+               public override string Description {
+                       get {return "Read response file for more options.";}
+               }
+
+               public override bool GetArguments (string value, out IEnumerable<string> replacement)
+               {
+                       if (string.IsNullOrEmpty (value) || !value.StartsWith ("@")) {
+                               replacement = null;
+                               return false;
+                       }
+                       replacement = ArgumentSource.GetArgumentsFromFile (value.Substring (1));
+                       return true;
+               }
+       }
+#endif
+
+#if !PCL
        [Serializable]
+#endif
        public class OptionException : Exception {
                private string option;
 
@@ -471,22 +700,28 @@ namespace Mono.Options {
                        this.option = optionName;
                }
 
+#if !PCL
                protected OptionException (SerializationInfo info, StreamingContext context)
                        : base (info, context)
                {
                        this.option = info.GetString ("OptionName");
                }
+#endif
 
                public string OptionName {
                        get {return this.option;}
                }
 
+#if !PCL
+#pragma warning disable 618 // SecurityPermissionAttribute is obsolete
                [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
+#pragma warning restore 618
                public override void GetObjectData (SerializationInfo info, StreamingContext context)
                {
                        base.GetObjectData (info, context);
                        info.AddValue ("OptionName", option);
                }
+#endif
        }
 
        public delegate void OptionAction<TKey, TValue> (TKey key, TValue value);
@@ -498,17 +733,26 @@ namespace Mono.Options {
                {
                }
 
-               public OptionSet (Converter<string, string> localizer)
+               public OptionSet (MessageLocalizerConverter localizer)
                {
                        this.localizer = localizer;
+                       this.roSources = new ReadOnlyCollection<ArgumentSource>(sources);
                }
 
-               Converter<string, string> localizer;
+               MessageLocalizerConverter localizer;
 
-               public Converter<string, string> MessageLocalizer {
+               public MessageLocalizerConverter MessageLocalizer {
                        get {return localizer;}
                }
 
+               List<ArgumentSource> sources = new List<ArgumentSource> ();
+               ReadOnlyCollection<ArgumentSource> roSources;
+
+               public ReadOnlyCollection<ArgumentSource> ArgumentSources {
+                       get {return roSources;}
+               }
+
+
                protected override string GetKeyForItem (Option item)
                {
                        if (item == null)
@@ -541,8 +785,8 @@ namespace Mono.Options {
 
                protected override void RemoveItem (int index)
                {
-                       base.RemoveItem (index);
                        Option p = Items [index];
+                       base.RemoveItem (index);
                        // KeyedCollection.RemoveItem() handles the 0th item
                        for (int i = 1; i < p.Names.Length; ++i) {
                                Dictionary.Remove (p.Names [i]);
@@ -552,7 +796,6 @@ namespace Mono.Options {
                protected override void SetItem (int index, Option item)
                {
                        base.SetItem (index, item);
-                       RemoveItem (index);
                        AddImpl (item);
                }
 
@@ -575,6 +818,31 @@ namespace Mono.Options {
                        }
                }
 
+               public OptionSet Add (string header)
+               {
+                       if (header == null)
+                               throw new ArgumentNullException ("header");
+                       Add (new Category (header));
+                       return this;
+               }
+
+               internal sealed class Category : Option {
+
+                       // Prototype starts with '=' because this is an invalid prototype
+                       // (see Option.ParsePrototype(), and thus it'll prevent Category
+                       // instances from being accidentally used as normal options.
+                       public Category (string description)
+                               : base ("=:Category:= " + description, description)
+                       {
+                       }
+
+                       protected override void OnParseComplete (OptionContext c)
+                       {
+                               throw new NotSupportedException ("Category.OnParseComplete should not be invoked.");
+                       }
+               }
+
+
                public new OptionSet Add (Option option)
                {
                        base.Add (option);
@@ -585,7 +853,12 @@ namespace Mono.Options {
                        Action<OptionValueCollection> action;
 
                        public ActionOption (string prototype, string description, int count, Action<OptionValueCollection> action)
-                               : base (prototype, description, count)
+                               : this (prototype, description, count, action, false)
+                       {
+                       }
+
+                       public ActionOption (string prototype, string description, int count, Action<OptionValueCollection> action, bool hidden)
+                               : base (prototype, description, count, hidden)
                        {
                                if (action == null)
                                        throw new ArgumentNullException ("action");
@@ -604,11 +877,16 @@ namespace Mono.Options {
                }
 
                public OptionSet Add (string prototype, string description, Action<string> action)
+               {
+                       return Add (prototype, description, action, false);
+               }
+
+               public OptionSet Add (string prototype, string description, Action<string> action, bool hidden)
                {
                        if (action == null)
                                throw new ArgumentNullException ("action");
                        Option p = new ActionOption (prototype, description, 1, 
-                                       delegate (OptionValueCollection v) { action (v [0]); });
+                                       delegate (OptionValueCollection v) { action (v [0]); }, hidden);
                        base.Add (p);
                        return this;
                }
@@ -620,10 +898,14 @@ namespace Mono.Options {
 
                public OptionSet Add (string prototype, string description, OptionAction<string, string> action)
                {
+                       return Add (prototype, description, action, false);
+               }
+
+               public OptionSet Add (string prototype, string description, OptionAction<string, string> action, bool hidden)   {
                        if (action == null)
                                throw new ArgumentNullException ("action");
                        Option p = new ActionOption (prototype, description, 2, 
-                                       delegate (OptionValueCollection v) {action (v [0], v [1]);});
+                                       delegate (OptionValueCollection v) {action (v [0], v [1]);}, hidden);
                        base.Add (p);
                        return this;
                }
@@ -684,48 +966,30 @@ namespace Mono.Options {
                        return Add (new ActionOption<TKey, TValue> (prototype, description, action));
                }
 
-               protected virtual OptionContext CreateOptionContext ()
+               public OptionSet Add (ArgumentSource source)
                {
-                       return new OptionContext (this);
+                       if (source == null)
+                               throw new ArgumentNullException ("source");
+                       sources.Add (source);
+                       return this;
                }
 
-#if LINQ
-               public List<string> Parse (IEnumerable<string> arguments)
+               protected virtual OptionContext CreateOptionContext ()
                {
-                       bool process = true;
-                       OptionContext c = CreateOptionContext ();
-                       c.OptionIndex = -1;
-                       var def = GetOptionForName ("<>");
-                       var unprocessed = 
-                               from argument in arguments
-                               where ++c.OptionIndex >= 0 && (process || def != null)
-                                       ? process
-                                               ? argument == "--" 
-                                                       ? (process = false)
-                                                       : !Parse (argument, c)
-                                                               ? def != null 
-                                                                       ? Unprocessed (null, def, c, argument) 
-                                                                       : true
-                                                               : false
-                                               : def != null 
-                                                       ? Unprocessed (null, def, c, argument)
-                                                       : true
-                                       : true
-                               select argument;
-                       List<string> r = unprocessed.ToList ();
-                       if (c.Option != null)
-                               c.Option.Invoke (c);
-                       return r;
+                       return new OptionContext (this);
                }
-#else
+
                public List<string> Parse (IEnumerable<string> arguments)
                {
+                       if (arguments == null)
+                               throw new ArgumentNullException ("arguments");
                        OptionContext c = CreateOptionContext ();
                        c.OptionIndex = -1;
                        bool process = true;
                        List<string> unprocessed = new List<string> ();
                        Option def = Contains ("<>") ? this ["<>"] : null;
-                       foreach (string argument in arguments) {
+                       ArgumentEnumerator ae = new ArgumentEnumerator (arguments);
+                       foreach (string argument in ae) {
                                ++c.OptionIndex;
                                if (argument == "--") {
                                        process = false;
@@ -735,6 +999,8 @@ namespace Mono.Options {
                                        Unprocessed (unprocessed, def, c, argument);
                                        continue;
                                }
+                               if (AddSource (ae, argument))
+                                       continue;
                                if (!Parse (argument, c))
                                        Unprocessed (unprocessed, def, c, argument);
                        }
@@ -742,7 +1008,50 @@ namespace Mono.Options {
                                c.Option.Invoke (c);
                        return unprocessed;
                }
-#endif
+
+               class ArgumentEnumerator : IEnumerable<string> {
+                       List<IEnumerator<string>> sources = new List<IEnumerator<string>> ();
+
+                       public ArgumentEnumerator (IEnumerable<string> arguments)
+                       {
+                               sources.Add (arguments.GetEnumerator ());
+                       }
+
+                       public void Add (IEnumerable<string> arguments)
+                       {
+                               sources.Add (arguments.GetEnumerator ());
+                       }
+
+                       public IEnumerator<string> GetEnumerator ()
+                       {
+                               do {
+                                       IEnumerator<string> c = sources [sources.Count-1];
+                                       if (c.MoveNext ())
+                                               yield return c.Current;
+                                       else {
+                                               c.Dispose ();
+                                               sources.RemoveAt (sources.Count-1);
+                                       }
+                               } while (sources.Count > 0);
+                       }
+
+                       IEnumerator IEnumerable.GetEnumerator ()
+                       {
+                               return GetEnumerator ();
+                       }
+               }
+
+               bool AddSource (ArgumentEnumerator ae, string argument)
+               {
+                       foreach (ArgumentSource source in sources) {
+                               IEnumerable<string> replacement;
+                               if (!source.GetArguments (argument, out replacement))
+                                       continue;
+                               ae.Add (replacement);
+                               return true;
+                       }
+                       return false;
+               }
 
                private static bool Unprocessed (ICollection<string> extra, Option def, OptionContext c, string argument)
                {
@@ -820,7 +1129,7 @@ namespace Mono.Options {
                {
                        if (option != null)
                                foreach (string o in c.Option.ValueSeparators != null 
-                                               ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None)
+                                               ? option.Split (c.Option.ValueSeparators, c.Option.MaxValueCount - c.OptionValues.Count, StringSplitOptions.None)
                                                : new string[]{option}) {
                                        c.OptionValues.Add (o);
                                }
@@ -864,7 +1173,7 @@ namespace Mono.Options {
                                        if (i == 0)
                                                return false;
                                        throw new OptionException (string.Format (localizer (
-                                                                       "Cannot bundle unregistered option '{0}'."), opt), opt);
+                                                                       "Cannot use unregistered option '{0}' in bundle '{1}'."), rn, f + n), null);
                                }
                                p = this [rn];
                                switch (p.OptionValueType) {
@@ -895,11 +1204,23 @@ namespace Mono.Options {
                }
 
                private const int OptionWidth = 29;
+               private const int Description_FirstWidth  = 80 - OptionWidth;
+               private const int Description_RemWidth    = 80 - OptionWidth - 2;
 
                public void WriteOptionDescriptions (TextWriter o)
                {
                        foreach (Option p in this) {
                                int written = 0;
+
+                               if (p.Hidden)
+                                       continue;
+
+                               Category c = p as Category;
+                               if (c != null) {
+                                       WriteDescription (o, p.Description, "", 80, 80);
+                                       continue;
+                               }
+
                                if (!WriteOptionPrototype (o, p, ref written))
                                        continue;
 
@@ -910,13 +1231,44 @@ namespace Mono.Options {
                                        o.Write (new string (' ', OptionWidth));
                                }
 
-                               List<string> lines = GetLines (localizer (GetDescription (p.Description)));
-                               o.WriteLine (lines [0]);
-                               string prefix = new string (' ', OptionWidth+2);
-                               for (int i = 1; i < lines.Count; ++i) {
-                                       o.Write (prefix);
-                                       o.WriteLine (lines [i]);
+                               WriteDescription (o, p.Description, new string (' ', OptionWidth+2),
+                                               Description_FirstWidth, Description_RemWidth);
+                       }
+
+                       foreach (ArgumentSource s in sources) {
+                               string[] names = s.GetNames ();
+                               if (names == null || names.Length == 0)
+                                       continue;
+
+                               int written = 0;
+
+                               Write (o, ref written, "  ");
+                               Write (o, ref written, names [0]);
+                               for (int i = 1; i < names.Length; ++i) {
+                                       Write (o, ref written, ", ");
+                                       Write (o, ref written, names [i]);
+                               }
+
+                               if (written < OptionWidth)
+                                       o.Write (new string (' ', OptionWidth - written));
+                               else {
+                                       o.WriteLine ();
+                                       o.Write (new string (' ', OptionWidth));
                                }
+
+                               WriteDescription (o, s.Description, new string (' ', OptionWidth+2),
+                                               Description_FirstWidth, Description_RemWidth);
+                       }
+               }
+
+               void WriteDescription (TextWriter o, string value, string prefix, int firstWidth, int remWidth)
+               {
+                       bool indent = false;
+                       foreach (string line in GetLines (localizer (GetDescription (value)), firstWidth, remWidth)) {
+                               if (indent)
+                                       o.Write (prefix);
+                               o.WriteLine (line);
+                               indent = true;
                        }
                }
 
@@ -1043,60 +1395,9 @@ namespace Mono.Options {
                        return sb.ToString ();
                }
 
-               private static List<string> GetLines (string description)
+               private static IEnumerable<string> GetLines (string description, int firstWidth, int remWidth)
                {
-                       List<string> lines = new List<string> ();
-                       if (string.IsNullOrEmpty (description)) {
-                               lines.Add (string.Empty);
-                               return lines;
-                       }
-                       int length = 80 - OptionWidth - 2;
-                       int start = 0, end;
-                       do {
-                               end = GetLineEnd (start, length, description);
-                               bool cont = false;
-                               if (end < description.Length) {
-                                       char c = description [end];
-                                       if (c == '-' || (char.IsWhiteSpace (c) && c != '\n'))
-                                               ++end;
-                                       else if (c != '\n') {
-                                               cont = true;
-                                               --end;
-                                       }
-                               }
-                               lines.Add (description.Substring (start, end - start));
-                               if (cont) {
-                                       lines [lines.Count-1] += "-";
-                               }
-                               start = end;
-                               if (start < description.Length && description [start] == '\n')
-                                       ++start;
-                       } while (end < description.Length);
-                       return lines;
-               }
-
-               private static int GetLineEnd (int start, int length, string description)
-               {
-                       int end = System.Math.Min (start + length, description.Length);
-                       int sep = -1;
-                       for (int i = start; i < end; ++i) {
-                               switch (description [i]) {
-                                       case ' ':
-                                       case '\t':
-                                       case '\v':
-                                       case '-':
-                                       case ',':
-                                       case '.':
-                                       case ';':
-                                               sep = i;
-                                               break;
-                                       case '\n':
-                                               return i;
-                               }
-                       }
-                       if (sep == -1 || end == description.Length)
-                               return end;
-                       return sep;
+                       return StringCoda.WrappedLines (description, firstWidth, remWidth);
                }
        }
 }