diff --git a/include/wx/cmdline.h b/include/wx/cmdline.h index 5e6309b61e..602ea216d1 100644 --- a/include/wx/cmdline.h +++ b/include/wx/cmdline.h @@ -75,6 +75,10 @@ enum wxCmdLineSwitchState wxCMD_SWITCH_ON // Found in normal state. }; +// Constants determining how (and if) to wrap the usage message +constexpr int wxCMD_LINE_WRAP_AUTO = -1; +constexpr int wxCMD_LINE_WRAP_NONE = 0; + // ---------------------------------------------------------------------------- // wxCmdLineEntryDesc is a description of one command line // switch/option/parameter @@ -295,14 +299,16 @@ public: // syntax error occurred // // if showUsage is true, Usage() is called in case of syntax error or if - // help was requested - int Parse(bool showUsage = true); + // help was requested and if wrapColumn is not 0, the usage message is + // wrapped at the specified column, which will be the terminal width for + // its default value + int Parse(bool showUsage = true, int wrapColumn = wxCMD_LINE_WRAP_AUTO); // give the usage message describing all program options - void Usage() const; + void Usage(int wrapColumn = wxCMD_LINE_WRAP_AUTO) const; // return the usage string, call Usage() to directly show it to the user - wxString GetUsageString() const; + wxString GetUsageString(int wrapColumn = wxCMD_LINE_WRAP_AUTO) const; // get the command line arguments // ------------------------------ diff --git a/include/wx/private/terminal.h b/include/wx/private/terminal.h new file mode 100644 index 0000000000..4c17dc4058 --- /dev/null +++ b/include/wx/private/terminal.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/private/terminal.h +// Purpose: Helpers for working with terminal output +// Author: Vadim Zeitlin +// Created: 2024-11-22 +// Copyright: (c) 2024 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_PRIVATE_TERMINAL_H_ +#define _WX_PRIVATE_TERMINAL_H_ + +#ifdef __WINDOWS__ + #include "wx/utils.h" + + #include "wx/msw/wrapwin.h" +#endif + +#ifdef __UNIX__ + #include + #include + #include +#endif + +namespace wxTerminal +{ + +// Return the current terminal width or 0 if we couldn't find it. +inline int GetWidth() +{ +#ifdef __WINDOWS__ + CONSOLE_SCREEN_BUFFER_INFO csbi; + if ( ::GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) ) + return csbi.srWindow.Right - csbi.srWindow.Left + 1; +#elif defined TIOCGWINSZ + winsize w; + int fd = fileno(stdout); + if ( fd != -1 && ioctl(fd, TIOCGWINSZ, &w) == 0 ) + return w.ws_col; +#endif + + return 0; +} + +} // namespace wxTerminal + +#endif // _WX_PRIVATE_TERMINAL_H_ diff --git a/interface/wx/cmdline.h b/interface/wx/cmdline.h index c495e423c1..515b6ce705 100644 --- a/interface/wx/cmdline.h +++ b/interface/wx/cmdline.h @@ -98,6 +98,29 @@ enum wxCmdLineSwitchState wxCMD_SWITCH_ON }; +/** + Value indicating that wxCmdLineParser::Parse() should determine the wrap + column automatically. + + This is the default value for the @c wrapColumn parameter of + wxCmdLineParser::Parse() and means that the text will be wrapped at the + window boundary if it is possible to find it (which may not be the case if + the output is redirected and doesn't appear on a terminal at all, for + example). + + @since 3.3.0 + */ +constexpr int wxCMD_LINE_WRAP_AUTO = -1; + +/** + Value indicating that wxCmdLineParser::Parse() should not wrap the output. + + Passing this value as @c wrapColumn parameter of wxCmdLineParser::Parse() + means that the text should not be wrapped at all. + + @since 3.3.0 + */ +constexpr int wxCMD_LINE_WRAP_NONE = 0; /** Flags determining wxCmdLineParser::ConvertStringToArgs() behaviour. @@ -682,8 +705,16 @@ public: requested. If @false, only error messages about possible syntax errors are given, use Usage to show the usage message from the caller if needed. + @param wrapColumn + When @a giveUsage is @true, this parameter specifies the column at + which the help text should be wrapped if given. When left as its + default value of wxCMD_LINE_WRAP_AUTO, the text will be wrapped at + the window boundary, if it was possible to detect it, or not at all + otherwise. If the value of this parameter is wxCMD_LINE_WRAP_NONE, + the text is never wrapped. Any other value specifies the column at + which to wrap, e.g. 80. This parameter is new since wxWidgets 3.3.0. */ - int Parse(bool giveUsage = true); + int Parse(bool giveUsage = true, int wrapColumn = wxCMD_LINE_WRAP_AUTO); ///@{ /** @@ -760,15 +791,23 @@ public: resulting message will not be helpful to the user unless the descriptions were indeed specified. + @param wrapColumn + See Parse() for the description of this parameter. This parameter + is new since wxWidgets 3.3.0. + @see SetLogo(), SetUsageSynopsis() */ - void Usage() const; + void Usage(int wrapColumn = wxCMD_LINE_WRAP_AUTO) const; /** Return the string containing the program usage description. Call Usage() to directly show this string to the user. + + @param wrapColumn + See Parse() for the description of this parameter. This parameter + is new since wxWidgets 3.3.0. */ - wxString GetUsageString() const; + wxString GetUsageString(int wrapColumn = wxCMD_LINE_WRAP_AUTO) const; }; diff --git a/src/common/cmdline.cpp b/src/common/cmdline.cpp index a9a7b60a7c..d8e58d7530 100644 --- a/src/common/cmdline.cpp +++ b/src/common/cmdline.cpp @@ -40,6 +40,9 @@ #include "wx/apptrait.h" #include "wx/scopeguard.h" +#include "wx/private/terminal.h" +#include "wx/private/wordwrap.h" + // ---------------------------------------------------------------------------- // private functions // ---------------------------------------------------------------------------- @@ -784,7 +787,7 @@ void wxCmdLineParser::Reset() // the real work is done here // ---------------------------------------------------------------------------- -int wxCmdLineParser::Parse(bool showUsage) +int wxCmdLineParser::Parse(bool showUsage, int wrapColumn) { bool maybeOption = true; // can the following arg be an option? bool ok = true; // true until an error is detected @@ -1237,7 +1240,7 @@ int wxCmdLineParser::Parse(bool showUsage) if ( !ok && (!errorMsg.empty() || (helpRequested && showUsage)) ) { if ( showUsage ) - Usage(); + Usage(wrapColumn); wxSafeMessageOutput(errorMsg); } @@ -1249,12 +1252,12 @@ int wxCmdLineParser::Parse(bool showUsage) // give the usage message // ---------------------------------------------------------------------------- -void wxCmdLineParser::Usage() const +void wxCmdLineParser::Usage(int wrapColumn) const { wxSafeMessageOutput(GetUsageString(wrapColumn)); } -wxString wxCmdLineParser::GetUsageString() const +wxString wxCmdLineParser::GetUsageString(int wrapColumn) const { wxString appname; if ( m_data->m_arguments.empty() ) @@ -1416,22 +1419,61 @@ wxString wxCmdLineParser::GetUsageString() const lenMax = len; } + // Leave some space between the options and their descriptions. + constexpr int MARGIN = 3; + const int widthName = lenMax + MARGIN; + + // Determine the column to wrap at, if necessary. + if ( wrapColumn == wxCMD_LINE_WRAP_AUTO ) + { + // Note that GetWidth() returns 0 if the terminal size couldn't be + // determined, which happens to be exactly wxCMD_LINE_WRAP_NONE. + wrapColumn = wxTerminal::GetWidth(); + } + + // Check that we end up with reasonable layout, where descriptions column + // is at least as wide as the names one. + if ( wrapColumn != wxCMD_LINE_WRAP_NONE && wrapColumn < 2*widthName ) + { + // It doesn't make sense to wrap anything if there is so little space. + wrapColumn = wxCMD_LINE_WRAP_NONE; + } + + // This is only used when wrapColumn is not wxCMD_LINE_WRAP_NONE. + const int widthDesc = wrapColumn - widthName; + for ( size_t n = 0; n < namesOptions.size(); n++ ) { if ( n == count ) usage << wxT('\n') << stdDesc; + const auto& desc = descOptions[n]; + // desc contains text if name is empty if ( namesOptions[n].empty() ) { - usage << descOptions[n] << wxT('\n'); + usage << desc << wxT('\n'); } else { - usage << wxString::Format("%-*s\t%s\n", - static_cast(lenMax), - namesOptions[n], - descOptions[n]); + // Check if the description fits on the same line if we wrap it. + if ( wrapColumn == wxCMD_LINE_WRAP_NONE || + static_cast(desc.length()) <= widthDesc ) + { + // Note that we use TAB as separator here for compatibility. + usage << wxString::Format("%-*s%*s%s\n", + static_cast(lenMax), + namesOptions[n], + MARGIN, "", + desc); + } + else // We need to wrap it. + { + usage << wxString::Format("%s\n", namesOptions[n]); + + for ( const auto& s : wxWordWrap(desc, widthDesc) ) + usage << wxString::Format("%*s%s\n", widthName, "", s); + } } }