aboutsummaryrefslogtreecommitdiff
path: root/gdb/python
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/python')
-rw-r--r--gdb/python/py-cmd.c1
-rw-r--r--gdb/python/py-param.c2
-rw-r--r--gdb/python/py-utils.c194
-rw-r--r--gdb/python/python-internal.h21
4 files changed, 218 insertions, 0 deletions
diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c
index bc48c2e..f803183 100644
--- a/gdb/python/py-cmd.c
+++ b/gdb/python/py-cmd.c
@@ -493,6 +493,7 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw)
docstring = python_string_to_host_string (ds_obj.get ());
if (docstring == nullptr)
return -1;
+ docstring = gdbpy_fix_doc_string_indentation (std::move (docstring));
}
}
if (docstring == nullptr)
diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c
index cac9bd2..5d509ba 100644
--- a/gdb/python/py-param.c
+++ b/gdb/python/py-param.c
@@ -385,6 +385,8 @@ get_doc_string (PyObject *object, enum doc_string_type doc_type,
result = python_string_to_host_string (ds_obj.get ());
if (result == NULL)
gdbpy_print_stack ();
+ else if (doc_type == doc_string_description)
+ result = gdbpy_fix_doc_string_indentation (std::move (result));
}
}
diff --git a/gdb/python/py-utils.c b/gdb/python/py-utils.c
index 63eb4e8..1bd7b47 100644
--- a/gdb/python/py-utils.c
+++ b/gdb/python/py-utils.c
@@ -400,3 +400,197 @@ gdbpy_handle_exception ()
else
error ("%s", msg.get ());
}
+
+/* See python-internal.h. */
+
+gdb::unique_xmalloc_ptr<char>
+gdbpy_fix_doc_string_indentation (gdb::unique_xmalloc_ptr<char> doc)
+{
+ /* A structure used to track the white-space information on each line of
+ DOC. */
+ struct line_whitespace
+ {
+ /* Constructor. OFFSET is the offset from the start of DOC, WS_COUNT
+ is the number of whitespace characters starting at OFFSET. */
+ line_whitespace (size_t offset, int ws_count)
+ : m_offset (offset),
+ m_ws_count (ws_count)
+ { /* Nothing. */ }
+
+ /* The offset from the start of DOC. */
+ size_t offset () const
+ { return m_offset; }
+
+ /* The number of white-space characters at the start of this line. */
+ int ws () const
+ { return m_ws_count; }
+
+ private:
+ /* The offset from the start of DOC to the first character of this
+ line. */
+ size_t m_offset;
+
+ /* White space count on this line, the first character of this
+ whitespace is at OFFSET. */
+ int m_ws_count;
+ };
+
+ /* Count the number of white-space character starting at TXT. We
+ currently only count true single space characters, things like tabs,
+ newlines, etc are not counted. */
+ auto count_whitespace = [] (const char *txt) -> int
+ {
+ int count = 0;
+
+ while (*txt == ' ')
+ {
+ ++txt;
+ ++count;
+ }
+
+ return count;
+ };
+
+ /* In MIN_WHITESPACE we track the smallest number of whitespace
+ characters seen at the start of a line (that has actual content), this
+ is the number of characters that we can delete off all lines without
+ altering the relative indentation of all lines in DOC.
+
+ The first line often has no indentation, but instead starts immediates
+ after the 3-quotes marker within the Python doc string, so, if the
+ first line has zero white-space then we just ignore it, and don't set
+ MIN_WHITESPACE to zero.
+
+ Lines without any content should (ideally) have no white-space at
+ all, but if they do then they might have an artificially low number
+ (user left a single stray space at the start of an otherwise blank
+ line), we don't consider lines without content when updating the
+ MIN_WHITESPACE value. */
+ gdb::optional<int> min_whitespace;
+
+ /* The index into WS_INFO at which the processing of DOC can be
+ considered "all done", that is, after this point there are no further
+ lines with useful content and we should just stop. */
+ gdb::optional<size_t> all_done_idx;
+
+ /* White-space information for each line in DOC. */
+ std::vector<line_whitespace> ws_info;
+
+ /* Now look through DOC and collect the required information. */
+ const char *tmp = doc.get ();
+ while (*tmp != '\0')
+ {
+ /* Add an entry for the offset to the start of this line, and how
+ much white-space there is at the start of this line. */
+ size_t offset = tmp - doc.get ();
+ int ws_count = count_whitespace (tmp);
+ ws_info.emplace_back (offset, ws_count);
+
+ /* Skip over the white-space. */
+ tmp += ws_count;
+
+ /* Remember where the content of this line starts, and skip forward
+ to either the end of this line (newline) or the end of the DOC
+ string (null character), whichever comes first. */
+ const char *content_start = tmp;
+ while (*tmp != '\0' && *tmp != '\n')
+ ++tmp;
+
+ /* If this is not the first line, and if this line has some content,
+ then update MIN_WHITESPACE, this reflects the smallest number of
+ whitespace characters we can delete from all lines without
+ impacting the relative indentation of all the lines of DOC. */
+ if (offset > 0 && tmp > content_start)
+ {
+ if (!min_whitespace.has_value ())
+ min_whitespace = ws_count;
+ else
+ min_whitespace = std::min (*min_whitespace, ws_count);
+ }
+
+ /* Each time we encounter a line that has some content we update
+ ALL_DONE_IDX to be the index of the next line. If the last lines
+ of DOC don't contain any content then ALL_DONE_IDX will be left
+ pointing at an earlier line. When we rewrite DOC, when we reach
+ ALL_DONE_IDX then we can stop, the allows us to trim any blank
+ lines from the end of DOC. */
+ if (tmp > content_start)
+ all_done_idx = ws_info.size ();
+
+ /* If we reached a newline then skip forward to the start of the next
+ line. The other possibility at this point is that we're at the
+ very end of the DOC string (null terminator). */
+ if (*tmp == '\n')
+ ++tmp;
+ }
+
+ /* We found no lines with content, fail safe by just returning the
+ original documentation string. */
+ if (!all_done_idx.has_value () || !min_whitespace.has_value ())
+ return doc;
+
+ /* Setup DST and SRC, both pointing into the DOC string. We're going to
+ rewrite DOC in-place, as we only ever make DOC shorter (by removing
+ white-space), thus we know this will not overflow. */
+ char *dst = doc.get ();
+ char *src = doc.get ();
+
+ /* Array indices used with DST, SRC, and WS_INFO respectively. */
+ size_t dst_offset = 0;
+ size_t src_offset = 0;
+ size_t ws_info_offset = 0;
+
+ /* Now, walk over the source string, this is the original DOC. */
+ while (src[src_offset] != '\0')
+ {
+ /* If we are at the start of the next line (in WS_INFO), then we may
+ need to skip some white-space characters. */
+ if (src_offset == ws_info[ws_info_offset].offset ())
+ {
+ /* If a line has leading white-space then we need to skip over
+ some number of characters now. */
+ if (ws_info[ws_info_offset].ws () > 0)
+ {
+ /* If the line is entirely white-space then we skip all of
+ the white-space, the next character to copy will be the
+ newline or null character. Otherwise, we skip the just
+ some portion of the leading white-space. */
+ if (src[src_offset + ws_info[ws_info_offset].ws ()] == '\n'
+ || src[src_offset + ws_info[ws_info_offset].ws ()] == '\0')
+ src_offset += ws_info[ws_info_offset].ws ();
+ else
+ src_offset += std::min (*min_whitespace,
+ ws_info[ws_info_offset].ws ());
+
+ /* If we skipped white-space, and are now at the end of the
+ input, then we're done. */
+ if (src[src_offset] == '\0')
+ break;
+ }
+ if (ws_info_offset < (ws_info.size () - 1))
+ ++ws_info_offset;
+ if (ws_info_offset > *all_done_idx)
+ break;
+ }
+
+ /* Don't copy a newline to the start of the DST string, this would
+ result in a leading blank line. But in all other cases, copy the
+ next character into the destination string. */
+ if ((dst_offset > 0 || src[src_offset] != '\n'))
+ {
+ dst[dst_offset] = src[src_offset];
+ ++dst_offset;
+ }
+
+ /* Move to the next source character. */
+ ++src_offset;
+ }
+
+ /* Remove the trailing newline character(s), and ensure we have a null
+ terminator in place. */
+ while (dst_offset > 1 && dst[dst_offset - 1] == '\n')
+ --dst_offset;
+ dst[dst_offset] = '\0';
+
+ return doc;
+}
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index d947b96..217bc15 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -822,4 +822,25 @@ extern bool gdbpy_is_architecture (PyObject *obj);
extern bool gdbpy_is_progspace (PyObject *obj);
+/* Take DOC, the documentation string for a GDB command defined in Python,
+ and return an (possibly) modified version of that same string.
+
+ When a command is defined in Python, the documentation string will
+ usually be indented based on the indentation of the surrounding Python
+ code. However, the documentation string is a literal string, all the
+ white-space added for indentation is included within the documentation
+ string.
+
+ This indentation is then included in the help text that GDB displays,
+ which looks odd out of the context of the original Python source code.
+
+ This function analyses DOC and tries to figure out what white-space
+ within DOC was added as part of the indentation, and then removes that
+ white-space from the copy that is returned.
+
+ If the analysis of DOC fails then DOC will be returned unmodified. */
+
+extern gdb::unique_xmalloc_ptr<char> gdbpy_fix_doc_string_indentation
+ (gdb::unique_xmalloc_ptr<char> doc);
+
#endif /* PYTHON_PYTHON_INTERNAL_H */