# HG changeset patch
# User J. H. Palmieri <palmieri@math.washington.edu>
# Date 1242769953 25200
# Node ID f18bb3d2781a5c3b651f0e1129c4a28c3c283afd
# Parent  7488340543618251888907e60499593a1c911890
pretty docstrings

diff -r 748834054361 -r f18bb3d2781a doc/en/introspect/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/en/introspect/__init__.py	Tue May 19 14:52:33 2009 -0700
@@ -0,0 +1,1 @@
+ 
diff -r 748834054361 -r f18bb3d2781a doc/en/introspect/conf.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/en/introspect/conf.py	Tue May 19 14:52:33 2009 -0700
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# Sage introspection build configuration file.
+# See sage.server.notebook.cell.set_introspect_html() for details.
+
+import sys, os
+sys.path.append(os.environ['SAGE_DOC'])
+from common.conf import *
+
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.jsmath']
+
+templates_path = ['templates']
+html_static_path = ['static']
+
+html_use_modindex = False
+html_use_index = False
+html_split_index = False
+html_copy_source = False
diff -r 748834054361 -r f18bb3d2781a doc/en/introspect/static/empty
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/en/introspect/static/empty	Tue May 19 14:52:33 2009 -0700
@@ -0,0 +1,1 @@
+
diff -r 748834054361 -r f18bb3d2781a doc/en/introspect/templates/layout.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/en/introspect/templates/layout.html	Tue May 19 14:52:33 2009 -0700
@@ -0,0 +1,3 @@
+<div class="docstring">
+    {% block body %} {% endblock %}
+</div>
diff -r 748834054361 -r f18bb3d2781a sage/misc/sagedoc.py
--- a/sage/misc/sagedoc.py	Fri May 15 18:39:25 2009 -0700
+++ b/sage/misc/sagedoc.py	Tue May 19 14:52:33 2009 -0700
@@ -20,29 +20,38 @@
 #*****************************************************************************
 
 import os
-#('\\item', '*'), \
-substitutes = [('\\_','_'),\
-               ('\\item', '* '), \
-               ('\\to', '-->'), \
-               ('<BLANKLINE>',''), \
-               ('\\leq', '<='), \
-               ('\\geq', '>='), \
-               ('\\le', '<='), \
-               ('\\ge', '>='), \
-               ('\\bf', ''),\
-               ('\\sage', 'SAGE'), \
-               ('\\SAGE', 'SAGE'), \
-               ('\\rm', ''), \
-               ('cdots', '...'), \
-               ('\\cdot', ' *'), \
-               ('$',''), ('\\',''), ('backslash','\\'), \
-               ('begin{enumerate}',''), ('end{enumerate}',''), \
-               ('begin{description}',''), ('end{description}',''), \
-               ('begin{itemize}',''), ('end{itemize}',''), \
-               ('begin{verbatim}',''), ('end{verbatim}',''), \
-               ('mapsto', ' |--> '), \
-               ('ldots', '...'), ('note{','NOTE: ')]
-
+# two kinds of substitutions: math, which should only be done on the
+# command line -- in the notebook, these should instead by taken care
+# of by jsMath -- and nonmath, which should be done always.
+math_substitutes = [('\\to', '-->'),
+                    ('\\leq', '<='),
+                    ('\\geq', '>='),
+                    ('\\le', '<='),
+                    ('\\ge', '>='),
+                    ('cdots', '...'),
+                    ('\\cdot', ' *'),
+                    (' \\times', ' x'),
+                    ('\\times', ' x'),
+                    ('backslash','\\'),
+                    ('mapsto', ' |--> '),
+                    ('ldots', '...')]
+nonmath_substitutes = [('\\_','_'),
+                       ('\\item', '* '),
+                       ('<BLANKLINE>',''),
+                       ('\\bf', ''),
+                       ('\\sage', 'SAGE'),
+                       ('\\SAGE', 'SAGE'),
+                       ('\\rm', ''),
+                       ('backslash','\\'),
+                       ('begin{enumerate}',''),
+                       ('end{enumerate}',''),
+                       ('begin{description}',''),
+                       ('end{description}',''),
+                       ('begin{itemize}',''),
+                       ('end{itemize}',''),
+                       ('begin{verbatim}',''),
+                       ('end{verbatim}',''),
+                       ('note{','NOTE: ')]
 
 def _rmcmd(s, cmd, left='', right=''):
     """
@@ -112,13 +121,20 @@
 itempattern = re.compile(r"\\item\[?([^]]*)\]? *(.*)")
 itemreplace = r"* \1 \2"
 
-def detex(s):
+def detex(s, embedded=False):
     """nodetex
     This strips LaTeX commands from a string; it is used by the
     ``format`` function to process docstrings for display from the
     command line interface.
 
-    INPUT: ``s``, a string.
+    INPUT:
+
+    - ``s`` - string
+    - ``embedded`` - boolean (optional, default False)
+
+    If ``embedded`` is False, then do the replacements in both
+    ``math_substitutes`` and ``nonmath_substitutes``.  If True, then
+    only do ``nonmath_substitutes``.
 
     OUTPUT: string
 
@@ -129,8 +145,10 @@
         'Some math: `n >= k`.  A website: sagemath.org.'
         sage: detex(r'More math: `x \mapsto y`.  {\bf Bold face}.')
         'More math: `x  |-->  y`.  { Bold face}.'
-        sage: detex(r'$a, b, c, \ldots, z$')
-        'a, b, c, ..., z'
+        sage: detex(r'`a, b, c, \ldots, z`')
+        '`a, b, c, ..., z`'
+        sage: detex(r'`a, b, c, \ldots, z`', embedded=True)
+        '`a, b, c, \\\\ldots, z`'
     """
     s = _rmcmd(s, 'url')
     s = _rmcmd(s, 'code')
@@ -142,14 +160,22 @@
     s = _rmcmd(s, 'subsubsection')
     s = _rmcmd(s, 'note', 'NOTE: ', '')
     s = _rmcmd(s, 'emph', '*', '*')
+    s = _rmcmd(s, 'textbf', '*', '*')
 
     s = re.sub(itempattern, itemreplace, s)
-    
-    for a,b in substitutes:
+
+    if not embedded: # not in the notebook
+        for a,b in math_substitutes:  # do math substitutions
+            s = s.replace(a,b)
+        s = s.replace('\\','')        # nuke backslashes
+        s = s.replace('.. math::\n', '')  # get rid of .. math:: directives
+    else:
+        s = s.replace('\\','\\\\')    # double up backslashes for jsMath
+    for a,b in nonmath_substitutes:
         s = s.replace(a,b)
     return s
 
-def format(s):
+def format(s, embedded=False):
     """
     Format Sage documentation ``s`` for viewing with IPython.
 
@@ -157,10 +183,16 @@
     text, and if ``s`` contains a string of the form "<<<obj>>>",
     then it replaces it with the docstring for "obj".
 
-    INPUT: ``s`` - string
+    INPUT:
+
+    - ``s`` - string
+    - ``embedded`` - boolean (optional, default False)
 
     OUTPUT: string
 
+    Set ``embedded`` equal to True if formatting for use in the
+    notebook; this just gets passed as an argument to ``detex``.
+
     EXAMPLES::
 
         sage: from sage.misc.sagedoc import format
@@ -216,9 +248,9 @@
             t = 'Definition: ' + t0 + '\n\n' + t1
             docs.add(obj)
         s = s[:i] + '\n' + t + s[i+6+j:]
-            
+
     if 'nodetex' not in directives:
-        s = detex(s)
+        s = detex(s, embedded=embedded)
     return s
 
 def format_src(s):
diff -r 748834054361 -r f18bb3d2781a sage/misc/sageinspect.py
--- a/sage/misc/sageinspect.py	Fri May 15 18:39:25 2009 -0700
+++ b/sage/misc/sageinspect.py	Tue May 19 14:52:33 2009 -0700
@@ -114,6 +114,7 @@
 
 import inspect
 import os
+EMBEDDED_MODE = False
 
 def isclassinstance(obj):
     r"""
@@ -400,6 +401,33 @@
         if s[:4] == 'self':
             s = s[4:]
         s = s.lstrip(',').strip()
+        # for use with typesetting the definition with the notebook:
+        # sometimes s contains "*args" or "**keywds", and the
+        # asterisks confuse ReST/sphinx/docutils, so escape them:
+        # change * to \*, and change ** to \**.
+        if EMBEDDED_MODE:
+            s = s.replace('**', '\\**')  # replace ** with \**
+            t = ''
+            while True:  # replace * with \*
+                i = s.find('*')
+                if i == -1:
+                    break
+                elif i > 0 and s[i-1] == '\\':
+                    if s[i+1] == "*":
+                        t += s[:i+2]
+                        s = s[i+2:]
+                    else:
+                        t += s[:i+1]
+                        s = s[i+1:]
+                    continue 
+                elif i > 0 and s[i-1] == '*':
+                    t += s[:i+1]
+                    s = s[i+1:]
+                    continue
+                else:
+                    t += s[:i] + '\\*'
+                    s = s[i+1:]
+            s = t + s
         return obj_name + '(' + s + ')'
     except (AttributeError, TypeError, ValueError):
         return '%s( [noargspec] )'%obj_name
@@ -439,7 +467,8 @@
 
     if r is None:
         return ''
-    s = sage.misc.sagedoc.format(str(r))
+
+    s = sage.misc.sagedoc.format(str(r), embedded=EMBEDDED_MODE)
 
     # If there is a Cython embedded position, it needs to be stripped
     pos = _extract_embedded_position(s)
diff -r 748834054361 -r f18bb3d2781a sage/server/notebook/cell.py
--- a/sage/server/notebook/cell.py	Fri May 15 18:39:25 2009 -0700
+++ b/sage/server/notebook/cell.py	Tue May 19 14:52:33 2009 -0700
@@ -34,7 +34,7 @@
 
 import os, shutil
 
-from   sage.misc.misc import word_wrap
+from   sage.misc.misc import word_wrap, SAGE_DOC
 from   sage.misc.html import math_parse
 from   sage.misc.preparser import strip_string_literals
 from   sage.misc.package   import is_package_installed
@@ -46,6 +46,11 @@
 else:
     JEDITABLE_TINYMCE = False
 
+# Introspection.  The cache directory is a module-scope variable set
+# in the first call to Cell.set_introspect_html().
+import errno, hashlib, time
+from sphinx.application import Sphinx
+_SAGE_INTROSPECT = None
 
 class Cell_generic:
     def is_interactive_cell(self):
@@ -1430,12 +1435,168 @@
     #################
     # Introspection #
     #################
-    def set_introspect_html(self, html, completing=False):
-        if completing:
+    def set_introspect_html(self, html, completing=False, verbose=False):
+        """
+        If ``verbose`` is True, print verbose output about notebook
+        introspection to the command-line.  However, the argument
+        ``verbose`` is not easily accessible now -- if you need to
+        debug, you have to edit this file, changing its value to True,
+        and run 'sage -b'.
+        """
+        if html == "" or completing:
             self.__introspect_html = html
+        elif html.find("`") == -1 and html.find("::") == -1:
+            # html doesn't seem to be in ReST format so use docutils
+            # to process the preamble ("**File:**" etc.)  and put
+            # everything else in a <pre> block.
+            i = html.find("**Docstring:**")
+            if i != -1:
+                preamble = html[:i+14]
+                from docutils.core import publish_parts
+                preamble = publish_parts(html[:i+14], writer_name='html')['body']
+                html = html[i+14:]
+            else:
+                preamble = ""
+            self.__introspect_html = '<div class="docstring">' + preamble + '<pre>' + html + '</pre></div>'
         else:
-            html = escape(html).strip()
-            self.__introspect_html = '<pre class="introspection">'+html+'</pre>'
+            # html is in ReST format, so use Sphinx to process it
+            
+            # Set the location of the introspection cache, "permanent"
+            # or temporary.  The former, DOT_SAGE/sage_notebook/doc,
+            # can pool queries from multiple worksheet processes.  The
+            # latter is exclusive to a worksheet's process.  The Sage
+            # cleaner should delete the temporary directory (or
+            # directories) after the notebook server exits.
+            global _SAGE_INTROSPECT
+
+            if _SAGE_INTROSPECT is None:
+                from sage.misc.misc import DOT_SAGE, tmp_dir
+                # It's important to use os.path.join, instead of +,
+                # because Sphinx counts on normalized paths.  It's also
+                # more portable.
+                std_doc_dir = os.path.join(DOT_SAGE, 'sage_notebook/doc')
+                try:
+                    os.makedirs(std_doc_dir)
+                    _SAGE_INTROSPECT = std_doc_dir
+                except OSError, error:
+                    if error.errno == errno.EEXIST:
+                        if os.access(std_doc_dir, os.R_OK | os.W_OK | os.X_OK):
+                            _SAGE_INTROSPECT = std_doc_dir
+                        else:
+                            _SAGE_INTROSPECT = tmp_dir()
+                    else:
+                        _SAGE_INTROSPECT = tmp_dir()
+                if verbose:
+                    print 'Introspection cache: ', _SAGE_INTROSPECT
+                        
+            # We get a quick checksum of the input.  MD5 admits
+            # collisions, but we're not concerned about such security
+            # issues here.  Of course, if performance permits, we can
+            # choose a more robust hash function.
+            hash = hashlib.md5(html).hexdigest()
+            base_name = os.path.join(_SAGE_INTROSPECT, hash)
+            html_name = base_name + '.html'
+
+            # Multiple processes might try to read/write the target
+            # HTML file simultaneously.  We use a file-based lock.
+            # Since we care only about the target's contents, and
+            # we've configured Sphinx accordingly, we allow multiple
+            # simultaneous instances of Sphinx, as long as their
+            # targets are different.  Systems which don't properly
+            # implement os.O_EXCL may require coarser locking.
+
+            # The Pythonic cross-platform file lock below is adapted
+            # from
+            # http://www.evanfosmark.com/2009/01/cross-platform-file-locking-support-in-python/
+            lock_name = base_name + '.lock'
+
+            # Try to acquire the lock, periodically.  If we time out,
+            # we fall back to plainly formatted documentation.
+            timeout = 0.5
+            delay = 0.05
+            start_time = time.time()
+
+            while True:
+                try:
+                    # This operation is atomic on platforms which
+                    # properly implement os.O_EXCL:
+                    fd_lock = os.open(lock_name, os.O_CREAT | os.O_EXCL | os.O_RDWR)
+                    break;
+                except OSError, err:
+                    if (err.errno != errno.EEXIST) or (time.time() - start_time >= timeout):
+                        plain_html = escape(html).strip()
+                        self.__introspect_html = '<pre class="introspection">' + plain_html + '</pre>'
+                        return
+                    time.sleep(delay)
+
+            # We've acquired the lock.  Use cached HTML or run Sphinx.
+            try:
+                open(html_name, 'r')
+                if verbose:
+                    print 'Found: %s' % html_name
+            except IOError:
+                html = html.replace('\\\\', '\\')
+                rst_name = base_name + '.rst'
+                fd_rst = open(rst_name, 'w')
+                fd_rst.write(html)
+                fd_rst.close()
+
+                # Sphinx setup.  The constructor is Sphinx(srcdir,
+                # confdir, outdir, doctreedir, buildername,
+                # confoverrides, status, warning, freshenv).
+                srcdir = os.path.normpath(_SAGE_INTROSPECT)
+
+                # Note: It's crucial that confdir* contains a
+                # customized conf.py and layout.html.  In particular,
+                # we've disabled index generation and told Sphinx to
+                # output almost exactly the HTML we display.  Sphinx
+                # also pickles its environment in doctreedir, but we
+                # force Sphinx never to load this pickle with
+                # freshenv=True.
+                confdir = os.path.join(SAGE_DOC, 'en/introspect')
+                doctreedir = os.path.normpath(base_name)
+                confoverrides = {'html_context' : {}, 'master_doc' : hash}
+
+                # To suppress output, use this:
+
+                if verbose:
+                    import sys
+                    sphinx_app = Sphinx(srcdir, confdir, srcdir, doctreedir, 'html', confoverrides, sys.stdout, sys.stderr, True)
+                else:
+                    sphinx_app = Sphinx(srcdir, confdir, srcdir, doctreedir, 'html', confoverrides, None, None, True)
+
+                # Run Sphinx. The first argument corresponds to
+                # sphinx-build's "write all files" -a flag, which we
+                # set to None.
+                sphinx_app.build(None, [rst_name])
+
+                # We delete .rst files, so future Sphinx runs don't
+                # keep track of them.  We also delete doctrees.
+                try:
+                    os.unlink(rst_name)
+                except OSError:
+                    pass
+                try:
+                    shutil.rmtree(doctreedir)
+                    os.unlink(doctreedir)
+                except OSError:
+                    pass
+
+                if verbose:
+                    print 'Built: %s' % html_name
+            finally:
+                # Contents should be flushed on close().
+                fd_html = open(html_name, 'r')
+                new_html = fd_html.read()
+                fd_html.close()
+
+                # We release the lock and delete the lock file.
+                os.close(fd_lock)
+                os.unlink(lock_name)
+
+                new_html = new_html.replace('<pre>', '<pre class="literal-block">')
+                self.__introspect_html = new_html
+                return
 
     def introspect_html(self):
         if not self.introspect():
diff -r 748834054361 -r f18bb3d2781a sage/server/notebook/css.py
--- a/sage/server/notebook/css.py	Fri May 15 18:39:25 2009 -0700
+++ b/sage/server/notebook/css.py	Tue May 19 14:52:33 2009 -0700
@@ -293,10 +293,25 @@
 div.introspection {
 }
 
+div.docstring {
+  font-family:arial;
+  background-color: #f1f1f1;
+  color: blue;
+  border: solid 1px black;
+  padding:8px;
+  margin:8px;
+}
+
+pre.literal-block {
+  background-color: #efc;
+  color: blue;    
+}
+
 pre.introspection {
   font-family: monospace;
   font-size:15px;
-  background-color: #efefef;
+  background-color: #f1f1f1;
+  color: blue;
   border: solid 1px black;
   padding:8px;
   margin:8px;
@@ -1635,6 +1650,74 @@
    background-color: #990000;   
 }
 
+
+
+/* These have been scraped directly from pygment. */
+
+.hll { background-color: #ffffcc }
+.c { color: #408090; font-style: italic } /* Comment */
+.err { border: 1px solid #FF0000 } /* Error */
+.k { color: #007020; font-weight: bold } /* Keyword */
+.o { color: #666666 } /* Operator */
+.cm { color: #408090; font-style: italic } /* Comment.Multiline */
+.cp { color: #007020 } /* Comment.Preproc */
+.c1 { color: #408090; font-style: italic } /* Comment.Single */
+.cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
+.gd { color: #A00000 } /* Generic.Deleted */
+.ge { font-style: italic } /* Generic.Emph */
+.gr { color: #FF0000 } /* Generic.Error */
+.gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.gi { color: #00A000 } /* Generic.Inserted */
+.go { color: #303030 } /* Generic.Output */
+.gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
+.gs { font-weight: bold } /* Generic.Strong */
+.gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.gt { color: #0040D0 } /* Generic.Traceback */
+.kc { color: #007020; font-weight: bold } /* Keyword.Constant */
+.kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
+.kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
+.kp { color: #007020 } /* Keyword.Pseudo */
+.kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
+.kt { color: #902000 } /* Keyword.Type */
+.m { color: #208050 } /* Literal.Number */
+.s { color: #4070a0 } /* Literal.String */
+.na { color: #4070a0 } /* Name.Attribute */
+.nb { color: #007020 } /* Name.Builtin */
+.nc { color: #0e84b5; font-weight: bold } /* Name.Class */
+.no { color: #60add5 } /* Name.Constant */
+.nd { color: #555555; font-weight: bold } /* Name.Decorator */
+.ni { color: #d55537; font-weight: bold } /* Name.Entity */
+.ne { color: #007020 } /* Name.Exception */
+.nf { color: #06287e } /* Name.Function */
+.nl { color: #002070; font-weight: bold } /* Name.Label */
+.nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
+.nt { color: #062873; font-weight: bold } /* Name.Tag */
+.nv { color: #bb60d5 } /* Name.Variable */
+.ow { color: #007020; font-weight: bold } /* Operator.Word */
+.w { color: #bbbbbb } /* Text.Whitespace */
+.mf { color: #208050 } /* Literal.Number.Float */
+.mh { color: #208050 } /* Literal.Number.Hex */
+.mi { color: #208050 } /* Literal.Number.Integer */
+.mo { color: #208050 } /* Literal.Number.Oct */
+.sb { color: #4070a0 } /* Literal.String.Backtick */
+.sc { color: #4070a0 } /* Literal.String.Char */
+.sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
+.s2 { color: #4070a0 } /* Literal.String.Double */
+.se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
+.sh { color: #4070a0 } /* Literal.String.Heredoc */
+.si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
+.sx { color: #c65d09 } /* Literal.String.Other */
+.sr { color: #235388 } /* Literal.String.Regex */
+.s1 { color: #4070a0 } /* Literal.String.Single */
+.ss { color: #517918 } /* Literal.String.Symbol */
+.bp { color: #007020 } /* Name.Builtin.Pseudo */
+.vc { color: #bb60d5 } /* Name.Variable.Class */
+.vg { color: #bb60d5 } /* Name.Variable.Global */
+.vi { color: #bb60d5 } /* Name.Variable.Instance */
+.il { color: #208050 } /* Literal.Number.Integer.Long */
+
+/* end stuff scraped from pygment */
+
 """
     if color == 'gmail':
         color1 = '#c3d9ff'
diff -r 748834054361 -r f18bb3d2781a sage/server/notebook/js.py
--- a/sage/server/notebook/js.py	Fri May 15 18:39:25 2009 -0700
+++ b/sage/server/notebook/js.py	Tue May 19 14:52:33 2009 -0700
@@ -1017,7 +1017,17 @@
             halt_introspection();
             return;
         }
+
         d.innerHTML = introspection_text;
+
+        if (contains_jsmath(introspection_text)) {
+            try {
+                jsMath.ProcessBeforeShowing(d);
+            } catch(e) {
+                text_cell.innerHTML = jsmath_font_msg + d.innerHTML;
+            }
+        }
+
         if(replacing)
             select_replacement_element();
     } else {
@@ -3016,6 +3026,14 @@
         cell_output.innerHTML = '';
         cell_output_nowrap.innerHTML = '';
         cell_output_html.innerHTML = introspect_html;
+        if (contains_jsmath(introspect_html)) {
+            try {
+                jsMath.ProcessBeforeShowing(cell_output_html);
+            } catch(e) {
+                cell_output.innerHTML = jsmath_font_msg + cell_output_html.innerHTML;
+            }
+        }
+
     }
 }
 
diff -r 748834054361 -r f18bb3d2781a sage/server/support.py
--- a/sage/server/support.py	Fri May 15 18:39:25 2009 -0700
+++ b/sage/server/support.py	Tue May 19 14:52:33 2009 -0700
@@ -53,6 +53,7 @@
         sage.structure.sage_object.base=object_directory
     sage.misc.latex.EMBEDDED_MODE = True
     sage.misc.pager.EMBEDDED_MODE = True
+    sage.misc.sageinspect.EMBEDDED_MODE = True
 
     setup_systems(globs)
     sage.misc.session.init(globs)
@@ -188,19 +189,25 @@
     except (AttributeError, NameError, SyntaxError):
         return "No object '%s' currently defined."%obj_name
     s  = ''
+    newline = "\n\n"  # blank line to start new paragraph
     try:
         filename = sageinspect.sage_getfile(obj)
         #i = filename.find('site-packages/sage/')
         #if i == -1:
-        s += 'File:        %s\n'%filename
+        s += '**File:** %s'%filename
+        s += newline
         #else:
         #    file = filename[i+len('site-packages/sage/'):]
         #    s += 'File:        <html><a href="src_browser?%s">%s</a></html>\n'%(file,file)
     except TypeError:
         pass
-    s += 'Type:        %s\n'%type(obj)
-    s += 'Definition:  %s\n'%sageinspect.sage_getdef(obj, obj_name)
-    s += 'Docstring: \n%s\n'%sageinspect.sage_getdoc(obj, obj_name)
+    s += '**Type:** %s'%type(obj)
+    s += newline
+    s += '**Definition:** %s'%sageinspect.sage_getdef(obj, obj_name)
+    s += newline
+    s += '**Docstring:**'
+    s += newline
+    s += sageinspect.sage_getdoc(obj, obj_name)
     return s.rstrip()
 
 def source_code(s, globs, system='sage'):
@@ -226,13 +233,20 @@
             return obj._sage_src_()
         except:
             pass
+        newline = "\n\n"  # blank line to start new paragraph
+        indent = "    "   # indent source code to mark it as a code block
+
         filename = sageinspect.sage_getfile(obj)
         lines, lineno = sageinspect.sage_getsourcelines(obj, is_binary=False)
-        src = ''.join(lines)
-        src = sagedoc.format_src(src)
+        src = indent.join(lines)
+        src = indent + sagedoc.format_src(src)
         if not lineno is None:
-            src = "File: %s\nSource Code (starting at line %s):\n%s"%(filename, lineno, src)
-        return src
+            output = "**File:** %s"%filename
+            output += newline
+            output += "**Source Code** (starting at line %s)::"%lineno
+            output += newline
+            output += src
+        return output
     
     except (TypeError, IndexError), msg:
         print msg
