source: sage/server/notebook/notebook.py @ 2675:855df0e90495

Revision 2675:855df0e90495, 72.2 KB checked in by Dorian Raymer <deldotdr@…>, 6 years ago (diff)

Added doc-browser to notebook.

Line 
1r"""
2SAGE Notebook Interface
3
4AUTHORS:
5    -- William Stein (2006-05-06): initial version
6    -- Alex Clemesha
7    -- Tom Boothby: support for a wide range of web browsers; refactoring of javascript code; systematic keyboard controls
8
9The SAGE graphical user interface is unusual in that it operates via
10your web browser.  It provides you with SAGE worksheets that you can
11edit and evaluate, which contain scalable typeset mathematics and
12beautiful antialised images.  To try it out immediately, do this:
13
14    sage.: notebook(open_viewer=True)
15    the sage notebook starts...
16
17\subsection{Supported Browsers}
18
19The SAGE notebook should fully work with Firefox (and Mozilla),
20Safari, and Opera. The notebook works somewhat in Internet Explorer.
21
22\subsection{Tutorial}
23Here are some things to try in the the notebook to get a feeling
24for it.
25 
26Type "2+2" in the blank box and press "shift-enter".
27The line below"2+2" will turn a different color for a moment while a SAGE kernel
28fires up and computes the answer.
29 
30Your cursor should now be in the next box down.   Type \code{a = 2\^1000}
31and press return, then "a" alone on the second line, then shift-return.
32You'll see a big number.   Also, "a" will appear in the variable
33browser in the left of the screen.    Next, click just to the
34left of the big number in the blue-ish area.  The number will shrink
35a little and change to occupy only one line.  You can see the whole
36number using your browser's horizontal scroll bar.  Click again and
37the number vanishes, to be replaced by a horizontal bar.  Click on the
38bar and the number is back.  If you click "Hide Output" in the upper
39right, all output disappears.
40 
41Next try graphics!  Type "show(plot(sin,0,10))" into an empty
42box and hit shift-enter.   You'll get a graph of sin.   Try another
43function, e.g.,
44\begin{verbatim}
45   show(plot(lambda x: sin(x)^2 - cos(2*x)^3, -5,5))
46\end{verbatim}
47Click on the left side of the figure (twice) to make it disappear.
48 
49One important feature of the SAGE notebook, is that you can
50"queue up" a bunch of calculations in a row, *while* still editing the
51notebook!  As an example, consider computing factorials, which takes a
52while (but not forever).  First, enter the following in a blank box and
53press"shift-return":
54\begin{verbatim}
55def f(n):
56    return len(str(factorial(10^n)))
57\end{verbatim}         
58This defines a function that takes a while to compute.   For example,
59time the execution of "f(5)", by typing (in a new box), "time f(5)".
60It should take a few seconds.   Next try
61"f(6)", which takes quite a while (about 21 seconds on sage.math).
62While f(6) is being computed, note that the output line for f(6) is a
63different color, indicating that it is being computed
64While f(6) is computing (if it finishes first, restart it by
65just hitting shift-enter in the box where "f(6)" is), try typing
66"f(4)" in the next box.  You're allowed to give input, but the
67result doesn't get computed immediately.  You can enter several more
68lines as well, etc.; when the f(6) finally finishes, SAGE goes on
69to compute "f(4)".   You can queue up dozens of calculations.  For
70example, if you hit the "Evaluate" link in the upper right, the
71whole worksheet is queued up for computation.  Try it.   When the
72computation gets stuck on "f(6)", hit the interrupt button (or press escape)
73and the queued up calculations are cancelled.
74 
75Click "Hide Output" in the upper right.   You'll see just your
76input and some little boxes; clicking on the boxes reveals output.
77 
78You can also embed nicely typeset math.  Try this:
79\begin{verbatim}
80f = maxima('sin(x^2)')
81g = f.integrate('x')
82view(g)
83\end{verbatim}
84 
85If this silently fails, type "view(g, debug=True)" instead.
86You need latex and the "convert" and "gs" commands, (use
87an "apt-get install imagemagick gs").  Anyways, you get
88a nicely typeset formula.  Try a matrix next:
89\begin{verbatim}
90A = MatrixSpace(QQ, 5).random_element()
91view(A)
92\end{verbatim}
93Try typing this into a new box:
94\begin{verbatim}
95%latex
96Consider the matrix $$A = \sage{A},$$
97which has square $$A^2 = \sage{A^2}.$$
98\end{verbatim}
99If you would like to typeset a slide (suitable for presentation),
100use \%slide instead.
101Here is another example:
102\begin{verbatim}
103%latex
104The first ten squares are
105$$
106\sage{', '.join([str(sq(i)) for i in range(1,11)])}
107$$
108
109The primes up to 100 are
110$$
111\sage{', '.join(str(p) for p in prime_range(100))}
112$$
113\end{verbatim}
114
115\subsubsection{Using Gap, Magma, GP/PARI}
116Make the first line of the input block \code{\%gap}
117\code{\%magma}, or \code{\%gp}, etc.  The rest of the block
118is fed directly to the corresponding interpreter.
119In this way you can make a single session that has input blocks
120that work with a range of different systems.
121
122(Note -- there is currently no support for
123pulling in objects and evaluating code in SAGE by typing
124"sage(...)" inside the input block.  This is planned.)
125
126\subsubsection{Typesetting Mathematics}
127SAGE \emph{includes} jsMath, which is an implementation of the TeX
128math layout engine in javascript.  If you use the show or view
129commands, they display a given SAGE object typeset using jsmath.
130Moreover, if you put \code{\%jsmath} at the beginning of an input
131cell, the whole cell will be typeset using jsmath.  Also, you can type
132\code{jsmath(obj)} to typeset a given object obj using jsmath.
133
134 
135\subsubsection{Adding and Removing Cells}
136To add a new cell, click on a little black line that appears when you
137hover between any two cells, or above the top one.  To delete a cell
138delete all its contents, then hit backspace one more time.  The cell
139vanishes forever.
140 
141You can also move back and forth between cells using the up and down
142arrow.  In particular, when you are at the top of a cell and press
143the up arrow the cursor jumps to the previous cell.
144Press control-enter in a cell to create a new cell after the
145current cell.
146
147There is no direct support for moving and reorganizing cells, though
148you can copy and paste any individual cell into another one.  However,
149the "Text" buttons provide the full text of the worksheet in a very
150convenient format for copy and paste.
151
152
153\subsubsection{History}
154Click the history button near the top to pop up a history of the last
1551000 (or so) input cells.  After a huge amount of design discussion about
156how to design a history system, a simple popup with the text of
157previous commands seems like the best choice.  It's incredibly simple,
158yet provides an incredible amount of functionality, especially because
159that popup window can be easily searched (at least in Firefox), pasted
160from, etc., and refreshed (use F5 or Ctrl-R).
161
162 
163\subsubsection{Introspection}
164To find all completions for an identifier you are typing press
165the tab key.  This should work exactly like IPython, and even
166respects the \code{trait_names()} method.
167
168To find help for any object in a line, put ? after it
169and press the tab key.  The cursor must be somewhere in the identifier
170with the question mark after it.   For source code, put ?? after
171the identifier and press tab.  You can also put an identifier by
172itself on a line with ? (or ??) after it and press shift-enter.
173
174To get extensive help on an object, type "help(object)" and press
175return.  This works, since I set the PAGER to "cat", and I strip out
176control codes that appear in the output.  And this isn't annoying,
177since web browsers are very good for scrolling through long output.
178 
179 
180\subsubsection{Saving and Loading Individual Objects}
181When you start a notebook you give a name argument
182to it, and it creates a directory.  Inside that directory there
183will be many worksheets (which you can use all at once and easily
184flip through -- not implemented yet), and an object store.
185You can save and load individual objects (using save and load), and they'll
186be listed in the box on the bottom let, e.g., try
187
188a = 5
189save a
190
191and you'll see the "a" appear there.   You can load and save objects
192from any worksheet in any other one.  (Currently the only way to delete
193objects from the list of saved objects is to remove the object from
194the objects subdirectory.)
195 
196\subsubsection{Pasting in Examples}
197Code is evaluated by exec'ing (after preparsing). Only the output
198of the last line of the cell is implicitly printed. If any line
199starts with "sage:" or ">>>" the {\em entire block} is assumed to
200contain text and examples, and only lines that begin with a
201prompt are executed. Thus you can paste in *complete examples*
202from the docs without any editing, and you can write input
203cells that contains non-evaluated plain text mixed with
204examples by starting the block with ">>>" or including an example.
205(NOTE: Lines beginning with ">>>" are still preparsed.)
206 
207\subsubsection{Saving and Loading Notebooks and Worksheets}
208 
209The SAGE notebook is very persistent.  Every time you submit
210a cell for computation, the state of the notebook is saved (a
211few kb's file).  If you quit the notebook and reload, it will
212have everything you typed from the previous session, along
213with all output.
214Firefox has an excellent undo function for text input cells.
215Just hit control-z to have ``infinite undo'' for the input
216you've entered in that particular cell.
217
218You can save all variables in a current session by typing
219\code{save_session [optional_name]}.  You can then load
220those session variables into another worksheet using
221\code{load_session}, or load into the same worksheet next
222time you use it.
223 
224\subsubsection{Architecture}
225 
226The SAGE Notebook is an ``AJAX application'' that can run either
227entirely locally on your desktop machine, or partly on
228a server and via a web browser that could be located somewhere
229else.
230If you run the server and allow remote access (by setting
231address when starting the notebook), you should also set
232the username and password, so not just anybody can access
233the notebook.
234
235Anywhere, here are the components of the SAGE Notebook:
236
237\begin{enumerate}
238\item Web Server: A Python process that uses the
239      Python standard library's
240     BaseHTTPServer.HTTPServer to create a web server.  This
241     process also handles all requests from the web browser,
242     e.g., organizing computation of cells, etc.  It
243     only imports a small
244     subset of the SAGE library.  In particular, if you do
245     "sage -notebook" at the command line, only some of
246     SAGE is imported. 
247 
248 \item SAGE Server:
249     A Python process with all the SAGE libraries loaded; this
250     is started by (1) when a web browser first requests that
251     a cell be evaluated.  There's (up to) one of these
252     for each worksheet.
253 
254 \item WEB Browser: The web browser runs a 1000-line javascript (plus
255     800 lines of css) program that Alex, Tom and I wrote from
256     scratch, which implements much of the browser-side part of the
257     SAGE notebook functionality.
258     
259\end{enumerate}
260 
261When you use the SAGE Notebook, you are mainly interacting with a
262javascript program.  When you do something serious, e.g., request
263computation of some input, create a new cell, etc., a request is made
264from your web browser to the web server telling it what is going on.
265If it's a calculation, the web server tells the SAGE server to get
266started on the calculation, and tells the web browser to check several
267times a second whether there is anything new with the calculation.
268When something new appears it fills that in.  This continues until all
269calculations are done. During this time, you can edit cells, create
270new cells, submit more computations, etc.  Note that output is
271updated as the computation proceeds, so you can verbosely watch
272a computation progress.  For example, try the following from the SAGE
273Notebook:
274
275\begin{verbatim}
276import time
277for i in range(10):
278    print i
279    time.sleep(0.5)
280\end{verbatim}
281 
282You get to watch as the integers from 1 to 10 are "computed".
283Actually, getting this output to be reported as the computation
284proceeds is, I think, \emph{crucial} to making a really usable SAGE
285GUI--users (i.e., me) want to run huge computations and watch the
286output progress.
287 
288The architecture is also good from the point of view of being able to
289interrupt running computations.  What happens when you request an
290interrupt is that the web browser sends a message to the web server,
291which in turn tells the SAGE server to stop computing by sending it
292many interrupt signals (for several seconds) until it either stops, or
293if it's really frozen (due to a bug, or calling into a C function that
294isn't properly wrapped in signal handling, or maybe you run an
295interactive program, e.g., via "os.system('...')"), it'll just kill that SAGE server
296and start a new one.  The result is that the
297user doesn't get a frozen web browser or browser interface at any point,
298and even if the whole SAGE process went down and froze, at least all
299your input and output from your session is still there in your
300browser.  The only thing you've lost is the definition of all your
301variables.  Hit "shift-enter" a few times or "evaluate all" and you're
302back in shape.  This is much better than having to restart the command
303prompt (e.g., with a terminal interface), then paste back in all your
304setup code, etc., Also, you can save variables as you go easily (via
305the "save" command), and get back to where you were quickly.
306
307"""
308
309## This is commented out, since it's not recommended.  I really
310## don't like crap that is both potentially insecure and will
311## break on some setups.
312## \subsubsection{Typesetting with Latex}
313## If you have latex, gv, and the imagemagick programs (e.g., convert)
314## installed on your system, you can do nice latex typesetting from
315## within SAGE.
316## \begin{enumerate}
317## \item As usual the command \code{latex(obj)} outputs latex code
318## to typeset obj.
319## \item The command \code{view(obj)} creates an image representing
320## the object, which you can copy and paste into other documents.
321## \item If you preface a block with \code{\%latex} the rest of the
322## block is typeset and the corresponding image appears.
323## The input is also (mostly) hidden.  Use {\%latex_debug} to debug
324## latex problems.
325## \item If you preface a block with \code{\%slide} the rest of the
326## block is typeset as a slide (bigger san serif font)
327## and the corresponding image appears.  The input is again hidden.
328## Use {\%slide_debug} for debugging.
329## \end{enumerate}
330
331
332
333###########################################################################
334#       Copyright (C) 2006 William Stein <wstein@gmail.com>
335#
336#  Distributed under the terms of the GNU General Public License (GPL)
337#                  http://www.gnu.org/licenses/
338###########################################################################
339
340import os
341import shutil
342import socket
343import re           # regular expressions
344
345# SAGE libraries
346from   sage.structure.sage_object import SageObject, load
347from   sage.misc.viewer     import browser
348from   sage.misc.misc       import alarm, cancel_alarm
349
350# SAGE Notebook
351import css          # style
352import js           # javascript
353import server       # web server
354import worksheet    # individual worksheets (which make up a notebook)
355import config       # internal configuration stuff (currently, just keycodes)
356import keyboards    # keyboard layouts
357
358MAX_WORKSHEETS = 4096  # do not change this willy nilly; that would break existing notebooks (and there is no reason to).
359MAX_HISTORY_LENGTH = 500
360WRAP_NCOLS = 80
361
362# Temporarily disabled while we try fix the firefox windows hang bug.
363JSMATH=False 
364
365class Notebook(SageObject):
366    def __init__(self, dir='sage_notebook', username=None, 
367                 password=None, color='default', system=None, 
368                 show_debug = False, log_server=False,
369                 kill_idle=False, splashpage=False):
370        self.__dir = dir
371        self.set_system(system)
372        self.__color = color
373        if not (username is None):
374            self.set_auth(username,password)
375        self.__worksheets = {}
376        self.__load_defaults()
377        self.__filename     = '%s/nb.sobj'%dir
378        self.__worksheet_dir = '%s/worksheets'%dir
379        self.__object_dir   = '%s/objects'%dir
380        self.__makedirs()
381        self.__next_worksheet_id = 0
382        self.__history = []
383        self.__history_count = 0
384        self.__log_server = log_server #log all POST's and GET's
385        self.__server_log = [] #server log list
386        W = self.create_new_worksheet('_scratch_')
387        self.__default_worksheet = W
388        self.__show_debug = show_debug
389        self.__kill_idle = kill_idle
390        self.__splashpage = splashpage if splashpage is not None else False
391        self.save()
392
393    def kill_idle(self):
394        """
395        Returns the idle timeout.  0 means don't kill
396        idle processes.
397        """
398        try:
399            return self.__kill_idle
400        except AttributeError:
401            self.__kill_idle = 0
402            return 0
403
404    def kill_idle_every_so_often(self):
405        raise NotImplementedError
406       
407
408    def system(self):
409        try:
410            return self.__system
411        except AttributeError:
412            self.__system = None
413            return None
414
415    def set_system(self, system):
416        if system == 'sage':
417            self.__system = None
418        elif system:  # don't change if it is None
419            self.__system = system
420
421    def splashpage(self):
422        try:
423            return self.__splashpage
424        except AttributeError:
425            self.__splashpage = True
426            return self.__splashpage
427
428    def set_splashpage(self, splashpage):
429        self.__splashpage = splashpage
430
431    def color(self):
432        try:
433            return self.__color
434        except AttributeError:
435            self.__color = 'default'
436            return self.__color
437
438    def set_color(self,color):
439        self.__color = color
440
441    def set_directory(self, dir):
442        if dir == self.__dir:
443            return
444        self.__dir = dir
445        self.__filename = '%s/nb.sobj'%dir
446        self.__worksheet_dir = '%s/worksheets'%dir
447        self.__object_dir = '%s/objects'%dir
448        for W in self.__worksheets.itervalues():
449            W.set_notebook(self)
450
451    def add_to_history(self, input_text):
452        H = self.history()
453        H.append(input_text)
454        while len(H) > self.max_history_length():
455            del H[0]
456   
457    def history_count_inc(self):
458        self.__history_count += 1
459   
460    def history_count(self):
461        return self.__history_count
462
463    def server_log(self):
464        return self.__server_log
465
466    def log_server(self):
467        return self.__log_server
468   
469    def set_log_server(self, log_server):
470        self.__log_server = log_server
471
472    def history(self):
473        try:
474            s = self.__history
475        except AttributeError:
476            self.__history = []
477            s = self.__history
478        return s
479
480    def history_text(self):
481        return '\n\n'.join([H.strip() for H in self.history()])
482
483    def history_with_start(self, start):
484        n = len(start)
485        return [x for x in self.history() if x[:n] == start]
486
487    def export_worksheet(self, worksheet_filename, filename):
488        W = self.get_worksheet_with_filename(worksheet_filename)
489        W.save()
490        cmd = 'cd %s && tar -jcf %s.sws "%s" && mv %s.sws ..'%(
491            self.__worksheet_dir,
492            filename, W.filename(), filename)
493        print cmd
494        os.system(cmd)
495
496    def tmpdir(self):
497        d = '%s/tmp'%self.__dir
498        if os.path.exists(d):
499            os.system('rm -rf "%s"'%d)
500        if not os.path.exists(d):
501            os.makedirs(d)
502        return d
503
504    def import_worksheet(self, filename):
505        if not os.path.exists(filename):
506            raise ValueError, "no file %s"%filename
507        if filename[-4:] != '.sws':
508            raise ValueError, "file %s must have extension sws."%filename
509        tmp = self.tmpdir()
510        cmd = 'cd %s; tar -jxf %s'%(tmp, os.path.abspath(filename))
511        print cmd
512        os.system(cmd)
513        D = os.listdir(tmp)[0]
514        worksheet = load('%s/%s/%s.sobj'%(tmp,D,D), compress=False)
515        names = self.worksheet_names()
516        if D in names:
517            m = re.match('.*?([0-9]+)$',D)
518            if m is None:
519                n = 0
520            else:
521                n = int(m.groups()[0])
522            while "%s%d"%(D,n) in names:
523                n += 1
524            cmd = 'mv %s/%s/%s.sobj %s/%s/%s%d.sobj'%(tmp,D,D,tmp,D,D,n)
525            print cmd
526            os.system(cmd)
527            cmd = 'mv %s/%s %s/%s%d'%(tmp,D,tmp,D,n)
528            print cmd
529            os.system(cmd)
530            D = "%s%d"%(D,n)
531            worksheet.set_name(D)
532        print D
533        S = self.__worksheet_dir
534        cmd = 'rm -rf "%s/%s"'%(S,D)
535        print cmd       
536        os.system(cmd)
537        cmd = 'mv %s/%s %s/'%(tmp, D, S)
538        print cmd
539        os.system(cmd)
540        new_id = None
541        id = worksheet.id()
542        for W in self.__worksheets.itervalues():
543            if W.id() == id:
544                new_id = self.__next_worksheet_id
545                self.__next_worksheet_id += 1
546                break
547        worksheet.set_notebook(self, new_id)
548        name = worksheet.name()
549        self.__worksheets[name] = worksheet
550        return worksheet
551
552    # unpickled, no worksheets will think they are
553    # being computed, since they clearly aren't (since
554    # the server just started).
555    def set_not_computing(self):
556        for W in self.__worksheets.values():
557            W.set_not_computing()
558
559    def set_debug(self,show_debug):
560        self.__show_debug = show_debug
561
562    def default_worksheet(self):
563        return self.__default_worksheet
564
565    def directory(self):
566        return self.__dir
567
568    def DIR(self):
569        """
570        Return the absolute path to the directory that contains
571        the SAGE Notebook directory.
572        """
573        return os.path.abspath('%s/..'%self.__dir)
574
575    def max_history_length(self):
576        try:
577            return self.__defaults['max_history_length']
578        except KeyError:
579            return MAX_HISTORY_LENGTH
580       
581    def __load_defaults(self):
582        # in future this will allow override by a file, and
583        # can be set by user via web interface
584        self.__defaults = {'cell_input_color':'#0000000',
585                           'cell_output_color':'#0000EE',
586                           'word_wrap_cols':int(WRAP_NCOLS),
587                           'max_history_length':MAX_HISTORY_LENGTH}
588
589    def worksheet_directory(self):
590        return self.__worksheet_dir
591
592    def object_directory(self):
593        O = self.__object_dir
594        if not os.path.exists(O):
595            os.makedirs(O)
596        return O
597
598    def objects(self):
599        L = [x[:-5] for x in os.listdir(self.object_directory())]
600        L.sort()
601        return L
602
603    def object_list_html(self):
604        m = max([len(x) for x in self.objects()] + [30])
605        s = []
606        a = '<a href="/%s.sobj" class="object_name">\n'
607        for name in self.objects():
608            s.append(a%name + name + '&nbsp;'*(m-len(name)) + '</a>\n')
609        return '<br>\n'.join(s)
610
611    def defaults(self):
612        return self.__defaults
613
614    def authorize(self, auth):
615        """
616        Returns True if auth is the correct authorization.
617        """
618        a = self.auth_string()
619        if a == ':':
620            return True
621        return a == auth
622
623    def auth_string(self):
624        try:
625            return self.__auth
626        except AttributeError:
627            self.__auth = ":"
628        return self.__auth
629
630    def set_auth(self, username, password):
631        self.__auth = '%s:%s'%(username, password)
632       
633    def __makedirs(self):
634        os.makedirs(self.__dir)
635        os.makedirs(self.__worksheet_dir)
636        os.makedirs(self.__object_dir)
637
638    def worksheet_ids(self):
639        return set([W.id() for W in self.__worksheets.itervalues()])
640
641    def create_new_worksheet(self, name='untitled', passcode=''):
642        if name in self.__worksheets.keys():
643            raise KeyError, 'name (=%s) already taken.'%name
644        name = str(name)
645        passcode = str(passcode)
646        wids = self.worksheet_ids()
647        id = 0
648        while id in wids:
649            id += 1
650
651        if id >= MAX_WORKSHEETS:
652            raise ValueError, 'there can be at most %s worksheets'%MAX_WORKSHEETS
653        self.__next_worksheet_id += 1
654        W = worksheet.Worksheet(name, self, id, system=self.system(), passcode=passcode)
655        self.__worksheets[name] = W
656        return W
657
658    def delete_worksheet(self, name):
659        """
660        Delete the given worksheet and remove its
661        name from the worksheet list.
662        """
663        if not (name in self.__worksheets.keys()):
664            raise KeyError, "Attempt to delete missing worksheet"
665        W = self.__worksheets[name]
666        cmd = 'rm -rf "%s"'%(W.directory())
667        print cmd
668        os.system(cmd)
669           
670        del self.__worksheets[name]
671        if len(self.__worksheets) == 0:
672            return self.create_new_worksheet('_scratch_')
673        else:
674            return self.__worksheets[self.__worksheets.keys()[0]]
675
676    def worksheet_names(self):
677        W = self.__worksheets.keys()
678        W.sort()
679        return W
680
681    def get_worksheet_with_name(self, name):
682        return self.__worksheets[name]
683
684    def get_worksheet_with_id(self, id):
685        """
686        Get the worksheet with given id, which is either a name or the id number.
687        If there is no such worksheet, a KeyError is raised.
688       
689        INPUT:
690            id -- something that identifies a worksheet.
691                 string -- use worksheet with that name or filename.
692                 None -- use the default worksheet.
693                 string int -- something that coerces to an integer; worksheet with that number
694
695        OUTPUT:
696            a worksheet.
697        """
698        if id is None:
699            return self.default_worksheet()
700        try:
701            id = int(id)
702            for W in self.__worksheets.itervalues():
703                if W.id() == id:
704                    return W
705        except ValueError:
706            id = str(id).lower()
707            for W in self.__worksheets.itervalues():
708                if W.name().lower() == id or W.filename().lower() == id:
709                    return W
710        raise KeyError, 'no worksheet %s'%id
711
712    def get_worksheet_with_filename(self, filename):
713        if id != None:
714            for W in self.__worksheets.itervalues():
715                if W.filename() == filename:
716                    return W
717        raise KeyError, "no such worksheet %s"%filename
718
719    def get_worksheet_that_has_cell_with_id(self, id):
720        worksheet_id = id // MAX_WORKSHEETS
721        return self.get_worksheet_with_id(worksheet_id)
722
723    def save(self, filename=None):
724        print "-"*70
725       
726        if filename is None:
727            F = os.path.abspath(self.__filename)
728            try:
729                shutil.copy(F, F[:-5] + '-backup.sobj')
730            except IOError:
731                pass
732            F = os.path.abspath(self.__filename)
733        else:
734            F = os.path.abspath(filename)
735           
736        print "Saving notebook to '%s'..."%F
737        SageObject.save(self, F, compress=False)
738        print "Press control-C twice to stop notebook server."
739        print "-"*70
740       
741    def start(self, port=8000, address='localhost',
742                    max_tries=128, open_viewer=False,
743                    jsmath=False):
744        global JSMATH
745        JSMATH = jsmath
746        tries = 0
747        port = int(port)
748        max_tries = int(max_tries)
749        while True:
750            try:
751                notebook_server = server.NotebookServer(self,
752                         port, address)
753            except socket.error, msg:
754                print msg
755                port += 1
756                tries += 1
757                if tries > max_tries:
758                    print "Not trying any more ports.  Probably your network is down."
759                    break
760                print "Trying next port (=%s)"%port
761            else:
762                break
763
764        s = "Open your web browser to http://%s:%s"%(address, port)
765        t = len(s)
766        if t%2:
767            t += 1
768            s += ' '
769        n = max(t+4, 50)
770        k = n - t  - 1
771        j = k/2 
772        print '*'*n
773        print '*'+ ' '*(n-2) + '*'
774        print '*' + ' '*j + s + ' '*j + '*'
775        print '*'+ ' '*(n-2) + '*'
776        print '*'*n
777        print "WARNING: The SAGE Notebook works best with Firefox/Mozilla, Safari, and Opera."
778
779        if open_viewer:
780            cmd = '%s http://%s:%s 1>&2 >/dev/null &'%(browser(), address, port)
781            os.system(cmd)
782        while True:
783            try:
784                notebook_server.serve()
785            except KeyboardInterrupt, msg:
786                break
787            except Exception, msg:
788                print msg
789                print "Automatically restarting server."
790            else:
791                break
792        self.save()
793        self.quit()
794        self.save()
795
796    def quit(self):
797        for W in self.__worksheets.itervalues():
798            W.quit()
799
800    def worksheet_list_html(self, current_worksheet=None):
801        s = []
802        names = self.worksheet_names()
803        m = max([len(x) for x in names] + [30])
804        for n in names:
805            W = self.__worksheets[n]
806            if W == current_worksheet:
807                cls = 'worksheet_current'
808            else:
809                cls = 'worksheet_other'
810            if W.computing():
811                cls += '_computing' # actively computing
812            name = W.name()
813            name += ' (%s)'%len(W)
814            name += ' '*(m-len(name))
815            name = name.replace(' ','&nbsp;')
816            txt = '<a class="%s" onClick="switch_to_worksheet(\'%s\')" onMouseOver="show_worksheet_menu(%s)" href="/%s">%s</a>'%(
817                #cls,W.id(),W.id(),W.id(),name)
818                cls,W.id(),W.id(), W.filename(),name) 
819            s.append(txt)
820        return '<br>'.join(s)
821
822    def _doc_html_head(self, worksheet_id, css_href):
823        if worksheet_id is not None:
824            worksheet = self.get_worksheet_with_id(worksheet_id)
825            head = '\n<title>%s (%s)</title>'%(worksheet.name(), self.directory())
826        else:
827            head = '\n<title>SAGE Notebook | Welcome</title>'
828        head += '\n<script language=javascript src="/__main__.js"></script>\n'
829        head += '\n<link rel=stylesheet href="/__main__.css" type="text/css" />\n'
830       
831        if css_href:
832            head += '\n<link rel=stylesheet type="text/css" href=%s />\n'%(css_href)
833
834        if JSMATH:
835            head += '<script>jsMath = {Controls: {cookie: {scale: 125}}}</script>\n'
836            #head += '<script src="/jsmath/plugins/spriteImageFonts.js"></script>\n'
837            head +=' <script src="/jsmath/plugins/noImageFonts.js"></script>\n'
838            head += '<script src="/jsmath/jsMath.js"></script>\n'
839            head += "<script>jsMath.styles['#jsMath_button'] = jsMath.styles['#jsMath_button'].replace('right','left');</script>\n"
840        #head += '<script language=javascript>' + js.javascript() + '</script>\n'
841        return head
842
843    def _doc_html_body(self, worksheet_id):
844        worksheet = self.get_worksheet_with_id(worksheet_id)
845        if worksheet.computing():
846            interrupt_class = "interrupt"
847        else:
848            interrupt_class = "interrupt_grey"
849        main_body = worksheet.html(authorized = True)
850       
851        vbar = '<span class="vbar"></span>'
852
853        body = ''
854        body += '<div class="top_control_bar">\n'
855        body += '  <span class="banner"><a class="banner" href="http://sage.math.washington.edu/sage">'
856        body += '  <img src="sagelogo.png"/></a></span>\n'
857        body += '  <span class="control_commands" id="cell_controls">\n'
858        body += '    <a class="%s" onClick="interrupt()" id="interrupt">Interrupt</a>'%interrupt_class + vbar
859        body += '    <a class="restart_sage" onClick="restart_sage()" id="restart_sage">Restart</a>' + vbar
860        body += '    <a class="history_link" onClick="history_window()">History</a>' + vbar
861        body += '    <a class="help" onClick="show_help_window()">Help</a>' + vbar
862        # body += '    <a class="slide_mode" onClick="slide_mode()">Slideshow</a>'
863        body += '  </span>\n'
864
865        #these divs appear in backwards order because they're float:right
866        body += '  <div class="hidden" id="slide_controls">\n'
867        body += '    <div class="slideshow_control">'
868        body += '      <a class="slide_arrow" onClick="slide_next()">&gt;</a>'
869        body += '      <a class="slide_arrow" onClick="slide_last()">&gt;&gt;</a>' + vbar
870        body += '      <a class="cell_mode" onClick="cell_mode()">Worksheet</a>'
871        body += '    </div>'
872        body += '    <div class="slideshow_progress" id="slideshow_progress" onClick="slide_next()">'
873        body += '      <div class="slideshow_progress_bar" id="slideshow_progress_bar">&nbsp;</div>'
874        body += '      <div class="slideshow_progress_text" id="slideshow_progress_text">&nbsp;</div>'
875        body += '    </div>'
876        body += '    <div class="slideshow_control">'
877        body += '      <a class="slide_arrow" onClick="slide_first()">&lt;&lt;</a>'
878        body += '      <a class="slide_arrow" onClick="slide_prev()">&lt;</a>'
879        body += '    </div>'
880        body += '  </span>\n'
881
882        body += '</div>'
883        body += '\n<div class="slideshow" id="worksheet">\n'
884
885        body += main_body + '\n</div>\n'
886
887        # The blank space given by '<br>'*15  is needed so the input doesn't get
888        # stuck at the bottom of the screen. This could be replaced by a region
889        # such that clicking on it creates a new cell at the bottom of the worksheet.
890        body += '<br>'*15
891        body += '\n</div>\n'
892       
893        body += '<span class="pane" id="left_pane"><table bgcolor="white"><tr><td>\n'
894        body += '</td></tr></table></span>\n'
895       
896        body += '  <div class="worksheet_list" id="worksheet_list">%s</div>\n'%self.worksheet_list_html(worksheet)
897        body += '<script language=javascript>focus(%s)</script>\n'%(worksheet[0].id())
898        body += '<script language=javascript>jsmath_init();</script>\n'
899        body += '<script language=javascript>worksheet_locked=false;</script>'
900
901        if worksheet.computing():
902            # Set the update checking back in motion.
903            body += '<script language=javascript> active_cell_list = %r; \n'%worksheet.queue_id_list()
904            body += 'for(var i = 0; i < active_cell_list.length; i++)'
905            body += '    cell_set_running(active_cell_list[i]); \n'
906            body += 'start_update_check(); </script>\n'
907        return body
908
909    def _html_head(self, worksheet_id):
910        if worksheet_id is not None:
911            worksheet = self.get_worksheet_with_id(worksheet_id)
912            head = '\n<title>%s (%s)</title>'%(worksheet.name(), self.directory())
913        else:
914            head = '\n<title>SAGE Notebook | Welcome</title>'
915        head += '\n<script language=javascript src="/__main__.js"></script>\n'
916        head += '\n<link rel=stylesheet href="/__main__.css" type="text/css" />\n'
917
918        if JSMATH:
919            head += '<script>jsMath = {Controls: {cookie: {scale: 115}}}</script>\n'
920            head +=' <script src="/jsmath/plugins/noImageFonts.js"></script>\n'
921            head += '<script src="/jsmath/jsMath.js"></script>\n'
922            head += "<script>jsMath.styles['#jsMath_button'] = jsMath.styles['#jsMath_button'].replace('right','left');</script>\n"
923        #head += '<script language=javascript>' + js.javascript() + '</script>\n'
924
925        return head
926
927    def _html_body(self, worksheet_id, show_debug=False, worksheet_authorized=False):
928        if worksheet_id is None or worksheet_id == '':
929            main_body = '<div class="worksheet_title">Welcome to the SAGE Notebook</div>\n'
930            if os.path.isfile(self.directory() + "/index.html"):
931                splash_file = open(self.directory() + "/index.html")
932                main_body+= splash_file.read()
933                splash_file.close()
934            else:
935                dir = os.path.abspath('%s'%self.directory())
936                main_body+= "<br>&nbsp;&nbsp;&nbsp;SAGE Notebook running from <tt>%s</tt>."%dir
937                main_body+= self.help_window()
938                main_body += "&nbsp;&nbsp;&nbsp;Create a file <tt>%s/index.html</tt> to replace this splash page.<br>"%(dir)
939            interrupt_class = "interrupt_grey"
940            worksheet = None
941        else:
942            worksheet = self.get_worksheet_with_id(worksheet_id)
943            if worksheet.computing():
944                interrupt_class = "interrupt"
945            else:
946                interrupt_class = "interrupt_grey"
947            main_body = worksheet.html(authorized = worksheet_authorized)
948
949        add_new_worksheet_menu = """
950             <div class="add_new_worksheet_menu" id="add_worksheet_menu">
951             Name: <input id="new_worksheet_box" class="add_new_worksheet_menu"
952                    onKeyPress="if(is_submit(event)) process_new_worksheet_menu_submit();"></input><br>
953             Password: <input id="new_worksheet_pass" class="add_new_worksheet_menu"
954                    onKeyPress="if(is_submit(event)) process_new_worksheet_menu_submit();"></input>
955
956             <button class="add_new_worksheet_menu"  onClick="process_new_worksheet_menu_submit();">add</button>
957             <span class="X" onClick="hide_add_new_worksheet_menu()">X</span>
958             </div>
959        """             
960
961        delete_worksheet_menu = """
962             <div class="delete_worksheet_menu" id="delete_worksheet_menu">
963             <input id="delete_worksheet_box" class="delete_worksheet_menu"
964                    onKeyPress="if(is_submit(event)) process_delete_worksheet_menu_submit();"></input>
965             <button class="delete_worksheet_menu" onClick="process_delete_worksheet_menu_submit();">delete</button>
966             &nbsp;&nbsp;&nbsp;<span class="X" onClick="hide_delete_worksheet_menu()">X</span>
967             </div>
968        """
969
970        vbar = '<span class="vbar"></span>'
971
972        body = ''
973        body += '<div class="top_control_bar">\n'
974        body += '  <span class="banner"><a class="banner" target="_new" href="http://sage.math.washington.edu/sage">'
975        body += '  <img src="sagelogo.png"/></a></span>\n'
976        body += '  <span class="control_commands" id="cell_controls">\n'
977        body += '    <a class="%s" onClick="interrupt()" id="interrupt">Interrupt</a>'%interrupt_class + vbar
978        body += '    <a class="restart_sage" onClick="restart_sage()" id="restart_sage">Restart</a>' + vbar
979        body += '    <a class="history_link" onClick="history_window()">History</a>' + vbar
980        body += '     <a onClick="show_upload_worksheet_menu()" class="upload_worksheet">Open</a>' + vbar
981        body += '     <a onClick="toggle_left_pane()" class="worksheets_button" id="worksheets_button">Worksheets</a>' + vbar
982        body += '    <a class="help" onClick="show_help_window()">Help</a>' + vbar
983        body += '    <a class="doc_browser" onClick="show_doc_browser()">Doc-Browser</a>' + vbar
984        body += '    <a class="slide_mode" onClick="slide_mode()">Slideshow</a>' 
985        body += '  </span>\n'
986
987        #these divs appear in backwards order because they're float:right
988        body += '  <div class="hidden" id="slide_controls">\n'
989        body += '    <div class="slideshow_control">'
990        body += '      <a class="slide_arrow" onClick="slide_next()">&gt;</a>'
991        body += '      <a class="slide_arrow" onClick="slide_last()">&gt;&gt;</a>' + vbar
992        body += '      <a class="cell_mode" onClick="cell_mode()">Worksheet</a>'
993        body += '    </div>'
994        body += '    <div class="slideshow_progress" id="slideshow_progress" onClick="slide_next()">'
995        body += '      <div class="slideshow_progress_bar" id="slideshow_progress_bar">&nbsp;</div>'
996        body += '      <div class="slideshow_progress_text" id="slideshow_progress_text">&nbsp;</div>'
997        body += '    </div>'
998        body += '    <div class="slideshow_control">'
999        body += '      <a class="slide_arrow" onClick="slide_first()">&lt;&lt;</a>'
1000        body += '      <a class="slide_arrow" onClick="slide_prev()">&lt;</a>'
1001        body += '    </div>'
1002        body += '  </span>\n'
1003
1004        body += '</div>'
1005        body += '\n<div class="worksheet" id="worksheet">\n'
1006        if self.__show_debug or show_debug:
1007            body += "<div class='debug_window'>"
1008            body += "<div class='debug_output'><pre id='debug_output'></pre></div>"
1009            body += "<textarea rows=5 id='debug_input' class='debug_input' "
1010            body += " onKeyPress='return debug_keypress(event);' "
1011            body += " onFocus='debug_focus();' onBlur='debug_blur();'></textarea>"
1012            body += "</div>"
1013
1014        body += main_body + '\n</div>\n'
1015
1016        # The blank space given by '<br>'*15  is needed so the input doesn't get
1017        # stuck at the bottom of the screen. This could be replaced by a region
1018        # such that clicking on it creates a new cell at the bottom of the worksheet.
1019        body += '<br>'*15
1020        body += '\n</div>\n'
1021
1022        body += '<div class="left_pane_bar" id="left_pane_bar" onClick="toggle_left_pane();"></div>\n'
1023        body += '<span class="pane" id="left_pane"><table bgcolor="white"><tr><td>\n'
1024        endpanespan = '</td></tr></table></span>\n'
1025
1026        body += '  <div class="worksheets_topbar">'
1027        body += '     <a onClick="show_add_new_worksheet_menu()" class="new_worksheet">New</a> '
1028        body += '     <a onClick="show_delete_worksheet_menu()" class="delete_worksheet">Delete</a> '       
1029        body += '  &nbsp;Worksheets</div>\n'
1030        body +=    add_new_worksheet_menu
1031        body +=    delete_worksheet_menu
1032        body += '  <div class="worksheet_list" id="worksheet_list">%s</div>\n'%self.worksheet_list_html(worksheet)
1033
1034        if worksheet is None:
1035            return body + endpanespan
1036
1037        body += '<div class="fivepix"></div>\n'
1038        body += '  <div class="objects_topbar"  onClick="toggle_menu(\'object_list\');">'
1039        body += '     <span class="plusminus" id="object_list_hider">[-]</span>'
1040        body += '     Saved Objects</div>\n'
1041        body += '  <div class="object_list" id="object_list">%s</div>\n'%self.object_list_html()
1042        body += '<div class="fivepix"></div>\n'
1043        body += '  <div class="variables_topbar" onClick="toggle_menu(\'variable_list\');">'
1044        body += '     <span class="plusminus" id="variable_list_hider">[-]</span>'
1045        body += '     Variables</div>\n'
1046        body += '  <div class="variable_list" id="variable_list">%s</div>\n'%\
1047                worksheet.variables_html()
1048        body += '<div class="fivepix"></div>\n'
1049        body += '  <div class="attached_topbar" onClick="toggle_menu(\'attached_list\');">'
1050        body += '     <span class="plusminus" id="attached_list_hider">[-]</span>'
1051        body += '     Attached Files</div>\n'
1052        body += '  <div class="attached_list" id="attached_list">%s</div><br>\n'%\
1053                worksheet.attached_html()
1054        body += endpanespan
1055        body += '<script language=javascript>focus(%s)</script>\n'%(worksheet[0].id())
1056        body += '<script language=javascript>jsmath_init();</script>\n'
1057
1058        if worksheet_authorized:
1059            body += '<script language=javascript>worksheet_locked=false;</script>'
1060        else:
1061            body += '<script language=javascript>worksheet_locked=true;</script>'
1062
1063        if worksheet.computing():
1064            # Set the update checking back in motion.
1065            body += '<script language=javascript> active_cell_list = %r; \n'%worksheet.queue_id_list()
1066            body += 'for(var i = 0; i < active_cell_list.length; i++)'
1067            body += '    cell_set_running(active_cell_list[i]); \n'
1068            body += 'start_update_check(); </script>\n'
1069        return body
1070
1071    def edit_window(self, worksheet):
1072        """
1073        Return a window for editing worksheet.
1074
1075        INPUT:
1076            worksheet -- a worksheet
1077        """
1078        t = worksheet.edit_text()
1079        t = t.replace('<','&lt;')
1080        body_html = ''
1081        body_html += '<h1 class="edit">SAGE Notebook: Editing Worksheet "%s"</h1>\n'%worksheet.name()
1082        body_html += """<b>Warnings:</b> You cannot undo after you save changes (yet).  All graphics will be deleted when you save.<br><br>"""
1083        body_html += '<form method="post" action="%s?edit" enctype="multipart/form-data">\n'%worksheet.filename()
1084        body_html += '<input type="submit" value="Save Changes" name="button_save"/>\n'
1085        #body_html += '<input type="submit" value="Preview" name="button_preview"/>\n'
1086        body_html += '<input type="submit" value="Cancel" name="button_cancel"/>\n'
1087        body_html += '<textarea class="edit" id="cell_intext" rows="30" name="textfield">'+t+'</textarea>'
1088        body_html += '</form>'
1089        body_html += """The format is as follows: <pre>
1090Arbitrary HTML       
1091{{{
1092Input
1093///
1094Output
1095}}}
1096Arbitrary HTML
1097{{{
1098Input
1099///
1100Output
1101}}}
1102...
1103</pre>"""
1104
1105         
1106        s = """
1107        <html><head><title>SAGE Wiki cell text </title>
1108        <style type="text/css">
1109
1110        textarea.edit {
1111            font-family: monospace;
1112            border: 1px solid #8cacbb; 
1113            color: black;
1114            background-color: white;
1115            padding: 3px;
1116            width: 100%%;
1117            margin-top: 0.5em;
1118        }
1119        </style>
1120       
1121        <script language=javascript> <!--
1122       
1123        %s
1124       
1125        function get_element(id) {
1126            if(document.getElementById)
1127                return document.getElementById(id);
1128            if(document.all)
1129                return document.all[id];
1130            if(document.layers)
1131                return document.layers[id];
1132        }
1133       
1134        function get_cell_list() {
1135            return window.opener.get_cell_list()
1136        }
1137
1138        function send_doc_html() {
1139            var cell_id_list = get_cell_list();
1140            var num = cell_id_list.length;
1141            var lastid = cell_id_list[num-1];
1142            var doc_intext = get_element('cell_intext').value; /*for testing doc_html*/
1143            window.opener.upload_doc_html(lastid,doc_intext);
1144        }
1145       
1146        function send_cell_text() {
1147            var cell_id_list = get_cell_list();
1148            var num = cell_id_list.length;
1149            var lastid = cell_id_list[num-1];
1150            var cell_intext = get_element('cell_intext').value;
1151            window.opener.upload_cell_text(lastid,cell_intext);
1152        }
1153
1154        function send_to_ws(do_eval) {
1155            var f = send_to_ws_callback;
1156            if (do_eval)
1157                f = send_to_ws_eval_callback;
1158            async_request('/delete_cell_all',f, "worksheet_id="+window.opener.get_worksheet_id());
1159        }
1160       
1161        function send_to_ws_callback(status, response_text) {
1162            window.opener.cell_delete_all_callback(status, response_text);
1163            var cell_intext = get_element('cell_intext').value;
1164            window.opener.insert_cells_from_wiki(cell_intext, false);
1165        }
1166       
1167        function send_to_ws_eval_callback(status, response_text) {
1168            window.opener.cell_delete_all_callback(status, response_text);
1169            var cell_intext = get_element('cell_intext').value;
1170            window.opener.insert_cells_from_wiki(cell_intext, true);
1171        }
1172       
1173
1174        function clear_wiki_window() {
1175            get_element('cell_intext').value = ' ';
1176        }
1177        --></script></head>
1178        <body>%s
1179        </body></html>"""%(js.async_lib(), body_html)
1180       
1181        return s
1182
1183
1184    def help_window(self):
1185        help = [
1186            ('HTML', 'Begin an input block with %html and it will be output as HTML.  Use the &lt;sage>...&lt;/sage> tag to do computations in an HTML block and have the typeset output inserted.  Use &lt;$>...&lt;/$> and &lt;$$>...&lt;/$$> to insert typeset math in the HTML block.  This does <i>not</i> require latex.'),
1187            ('Shell', 'Begin a block with %sh to have the rest of the block evaluated as a shell script.  The current working directory is maintained.'),
1188            ('Autoevaluate Cells on Load', 'Any cells with "#auto" in the input is automatically evaluated when the worksheet is first opened.'),
1189            ('Create New Worksheet', "Use the menu on the left, or simply put a new worksheet name in the URL, e.g., if your notebook is at http://localhost:8000, then visiting http://localhost:8000/tests will create a new worksheet named tests."),
1190               ('Evaluate Input', 'Press shift-enter.  You can start several calculations at once.  If you press alt-enter instead, then a new cell is created after the current one.'),
1191                ('Timed Evaluation', 'Type "%time" at the beginning of the cell.'),
1192                ('Evaluate all Cells', 'Click <u>Eval All</u> in the upper right.'),
1193                ('Evaluate Cell using <b>GAP, Singular, etc.', 'Put "%gap", "%singular", etc. as the first input line of a cell; the rest of the cell is evaluated in that system.'),
1194                ('Typeset a Cell', 'Make the first line of the cell "%latex". The rest of the cell should be the body of a latex document.  Use \\sage{expr} to access SAGE from within the latex.  Evaluated typeset cells hide their input.  Use "%latex_debug" for a debugging version.  You must have latex for this to work.'),
1195               ('Typeset a slide', 'Same as typesetting a cell but use "%slide" and "%slide_debug"; will use a large san serif font.  You must have latex for this to work.'),
1196                ('Typesetting', 'Type "latex(objname)" for latex that you can paste into your paper.  Type "view(objname)" or "show(objname)", which will display a nicely typeset image (using javascript!).  You do <i>not</i> need latex for this to work.  Type "lprint()" to make it so output is often typeset by default.'),
1197                ('Move between cells', 'Use the up and down arrows on your keyboard.'),
1198                ('Interrupt running calculations',
1199                 'Click <u>Interrupt</u> in the upper right or press escape in any input cell. This will (attempt) to interrupt SAGE by sending many interrupts for several seconds; if this fails, it restarts SAGE (your worksheet is unchanged, but your session is reset).'),
1200                ('Tab completion', 'Press tab while the cursor is on an identifier. On some web browsers (e.g., Opera) you must use control-space instead of tab.'),
1201                ('Print worksheet', 'Click the print button.'),
1202                ('Help About',
1203                 'Type ? immediately after the object or function and press tab.'),
1204                ('Source Code',
1205                 'Put ?? after the object and press tab.'),
1206                ('Hide Cell Input',
1207                 'Put %hide at the beginning of the cell.  This can be followed by %gap, %latex, %maxima, etc.  Note that %hide must be first.  From the edit screen, use %hideall to hide a complete cell.'),
1208                ('Detailed Help',
1209                 'Type "help(object)" and press shift-return.'),
1210                ('Insert New Cell',
1211                 'Put mouse between an output and input until the horizontal line appears and click.  Also if you press control-enter in a cell, a new cell is inserted after it.'),
1212                ('Delete Cell',
1213                 'Delete cell contents the press backspace.'),
1214                ('Text of Worksheet', 'Click the <u>Text</u> and <u>Doctext</u> links, which are very useful if you need to cut and paste chunks of your session into email or documentation.'),
1215                ('History', 'Click the <u>History</u> link for a history of commands entered in any worksheet of this notebook.  This appears in a popup window, which you can search (control-F) and copy and paste from.'),
1216                ('Hide/Show Output', 'Click on the left side of output to toggle between hidden, shown with word wrap, and shown without word wrap.'),
1217                ('Hide/Show All Output', 'Click <u>Hide</u> in the upper right to hide <i>all</i> output. Click <u>Show</u> to show all output.'),
1218                ('Variables',
1219                 'All variables and functions that you create during this session are listed on the left.  Even predefined variables that you overwrite will appear.'),
1220                ('Objects',
1221                 'All objects that you save in <i>any worksheet</i> are listed on the left.  Use "save(obj, name)" and "obj = load(name)" to save and load objects.'),
1222                ('Loading and Saving Sessions', 'Use "save_session name" to save all variables to an object with given name (if no name is given, defaults to name of worksheet).  Use "load_session name" to <i>merge</i> in all variables from a saved session.'),
1223                ('Loading and Saving Objects', 'Use "save obj1 obj2 ..." and "load obj1 obj2 ...".  This allows very easy moving of objects from one worksheet to another, and saving of objects for later use.'),
1224                ('Loading SAGE/Python Scripts', 'Use "load filename.sage" and "load filename.py".  Load is relative to the path you started the notebook in.  The .sage files are preparsed and .py files are not.   You may omit the .sage or .py extension.  Files may load other files.'),
1225                ('Attaching Scripts', 'Use "attach filename.sage" or "attach filename.py".  Attached files are automatically reloaded when the file changes.  The file $HOME/.sage/init.sage is attached on startup if it exists.'),
1226                ('Downloading and Uploading Worksheets',
1227                 'Click <u>Download</u> in the upper right to download a complete worksheet to a local .sws file, and click <u>Upload</u> to upload a saved worksheet to the notebook.  Note that <i>everything</i> that has been submitted is automatically saved to disk when you quit the notebook server (or type "%save_server" into a cell).'),
1228                ('Restart', 'Type "restart" to restart the SAGE interpreter for a given worksheet.  (You have to interrupt first.)'),
1229                ('Input Rules', "Code is evaluated by exec'ing (after preparsing).  Only the output of the last line of the cell is implicitly printed.  If any line starts with \"sage:\" or \">>>\" the entire block is assumed to contain text and examples, so only lines that begin with a prompt are executed.   Thus you can paste in complete examples from the docs without any editing, and you can write input cells that contains non-evaluated plain text mixed with examples by starting the block with \">>>\" or including an example."),
1230                ('Working Directory', 'Each block of code is run from its own directory.  The variable DIR contains the directory from which you started the SAGE notebook.  For example, to open a file in that directory, do "open(DIR+\'filename\')".'),
1231                ('Customizing the look', 'Learn about cascading style sheets (CSS), then create a file notebook.css in your $HOME/.sage directory.  Use "view source" on a notebook web page to see the CSS that you can override.'),
1232                ('Emacs Keybindings', 'If you are using GNU/Linux, you can change (or create) a <tt>.gtkrc-2.0</tt> file.  Add the line <tt>gtk-key-theme-name = "Emacs"</tt> to it.  See <a target="_blank" href="http://kb.mozillazine.org/Emacs_Keybindings_(Firefox)">this page</a> [mozillazine.org] for more details.'),
1233                ('More Help', 'Type "help(sage.server.notebook.notebook)" for a detailed discussion of the architecture of the SAGE notebook and a tutorial (or see the SAGE reference manual).'),
1234                ('Javascript Debugger', 'Type ?debug at the end of a worksheet url to enable the javascript debugger.  A pair of textareas will appear at the top of the worksheet -- the upper of which is for output, the lower is a direct interface to the page\'s javascript environment.  Type any eval()-able javascript into the input box and press shift+enter to execute it.  Type debug_append(str) to print to, and debug_clear() to clear the debug output window.'),
1235                ]
1236
1237        help.sort()
1238        s = """
1239        <br><hr>
1240        <style>
1241        div.help_window {
1242            background-color:white;
1243            border: 3px solid #3d86d0;
1244            top: 10ex;
1245            bottom:10%;
1246            left:25%;
1247            right:15%;
1248            padding:2ex;
1249        }
1250
1251
1252        table.help_window {
1253            background-color:white;
1254            width:100%;
1255        }
1256
1257        td.help_window_cmd {
1258            background-color: #f5e0aa;
1259            width:30%;
1260            padding:1ex;
1261            font-weight:bold;
1262        }
1263
1264        td.help_window_how {
1265            padding:1ex;
1266            width:70%;
1267        }
1268        </style>
1269        <div class="help_window">
1270       
1271        A <i>worksheet</i> is an ordered list of SAGE calculations with output.
1272        A <i>session</i> is a worksheet and a set of variables in some state.
1273        A <i>notebook</i> is a collection of worksheets and saved objects.
1274                 
1275        <table class="help_window">
1276        """
1277        for x, y in help:
1278            s += '<tr><td class="help_window_cmd">%s</td><td class="help_window_how">%s</td></tr>'%(x,y)
1279        s += '</table></div>'
1280
1281        s +="""
1282        <br>
1283        AUTHORS: William Stein, Tom Boothby, and Alex Clemesha (with feedback from many people,
1284        especially Fernando Perez and Joe Wetherell).<br><br>
1285        LICENSE: All code included with the standard SAGE install is <a href="__license__.html">licensed
1286        either under the GPL or a GPL-compatible license</a>.
1287        <br>
1288        """
1289        return s
1290
1291    def upload_window(self):
1292        return """
1293          <html>
1294            <head>
1295              <title>Upload File</title>
1296              <style>%s</style>
1297              <script language=javascript>%s</script>
1298            </head>
1299            <body onLoad="if(window.focus) window.focus()">
1300              <div class="upload_worksheet_menu" id="upload_worksheet_menu">
1301              <form method="POST" action="upload_worksheet" target="_new"
1302                    name="upload" enctype="multipart/form-data">
1303              <input class="upload_worksheet_menu" type="file" name="fileField" id="upload_worksheet_filename"></input><br>
1304              <input type="button" class="upload_worksheet_menu" value="upload" onClick="form.submit(); window.close();">
1305              </form><br>
1306              </div>
1307            </body>
1308          </html>
1309         """%(css.css(self.color()),js.javascript())
1310
1311    def doc_html(self,worksheet_id, css_href):
1312        try:
1313            W = self.get_worksheet_with_id(worksheet_id)
1314        except KeyError, msg:
1315            W = self.create_new_worksheet(worksheet_id)
1316            worksheet_id = W.id()
1317        head = self._doc_html_head(worksheet_id, css_href)
1318        body = self._doc_html_body(worksheet_id)
1319        if worksheet_id is not None:
1320            body += '<script language=javascript>worksheet_id="%s"; worksheet_filename="%s"; worksheet_name="%s"; toggle_left_pane(); </script>;'%(worksheet_id, W.filename(), W.name())
1321
1322        return """
1323        <html>
1324        <head>%s</head>
1325        <body>%s</body>
1326        </html>
1327        """%(head, body)
1328
1329    def html(self, worksheet_id=None, authorized=False, show_debug=False, worksheet_authorized=False):
1330        if worksheet_id is None or worksheet_id == '':
1331            if not self.splashpage():
1332                W = self.default_worksheet()
1333                worksheet_id = W.id()
1334            else:
1335                worksheet_id = None
1336                W = None
1337        else:
1338            try:
1339                W = self.get_worksheet_with_id(worksheet_id)
1340            except KeyError, msg:
1341                W = self.create_new_worksheet(worksheet_id)
1342                worksheet_id = W.id()
1343
1344        if authorized:
1345            body = self._html_body(worksheet_id, show_debug=show_debug,
1346                                   worksheet_authorized=worksheet_authorized)
1347        else:
1348            body = self._html_authorize()
1349
1350        if worksheet_id is not None:
1351            body += '<script language=javascript>worksheet_id="%s"; worksheet_filename="%s"; worksheet_name="%s";</script>;'%(worksheet_id, W.filename(), W.name())
1352
1353        head = self._html_head(worksheet_id)
1354        return """
1355        <html>
1356        <head>%s</head>
1357        <body>%s</body>
1358        </html>
1359        """%(head, body)
1360
1361    def _html_authorize(self):
1362        return """
1363        <h1>SAGE Notebook Server</h1>
1364        <div id="mainbody" class="login">Sign in to the SAGE Notebook<br>
1365        <form>
1366        <table>
1367        <tr><td>
1368          <span class="username">Username:</span></td>
1369          <td><input name="username" class="username"
1370                      onKeyPress="if(is_submit(event)) login(username.value, password.value)"></td>
1371        </tr>
1372        <tr><td>
1373           <span class="password">Password:</span></td>
1374           <td><input name="password" class="username" type="password"
1375                      onKeyPress="if(is_submit(event)) login(username.value, password.value)"></td>
1376        </tr>
1377        <td>&nbsp</td>
1378        <td>
1379           <input type='button' onClick="login(username.value,password.value);" value="Sign in">
1380           </td></table>
1381                   </form></div>
1382     
1383        """
1384
1385    def format_completions_as_html(self, cell_id, completions):
1386        if len(completions) == 0:
1387            return ''
1388        lists = []
1389
1390        # compute the width of each column
1391        column_width = []
1392        for i in range(len(completions[0])):
1393            column_width.append(max([len(x[i]) for x in completions if i < len(x)]))
1394               
1395        for i in range(len(completions)):
1396            row = completions[i]
1397            for j in range(len(row)):
1398                if len(lists) <= j:
1399                    lists.append([])     
1400                cell = """
1401   <li id='completion%s_%s_%s' class='completion_menu_two'>
1402    <a onClick='do_replacement(%s, "%s")'
1403       onMouseOver='this.focus(); select_replacement(%s,%s);'
1404    >%s</a>
1405   </li>"""%(cell_id, i, j, cell_id, row[j], i,j,
1406             row[j] + '&nbsp;'*(column_width[j]-len(row[j])) )
1407                lists[j].append(cell)
1408
1409        grid = "<ul class='completion_menu_one'>"
1410        for L in lists:
1411            s = "\n   ".join(L)
1412            grid += "\n <li class='completion_menu_one'>\n  <ul class='completion_menu_two'>\n%s\n  </ul>\n </li>"%s
1413
1414        return grid + "\n</ul>"
1415
1416
1417import sage.interfaces.sage0
1418import time
1419
1420## IMPORTANT!!! If you add any new input variable to notebook,
1421## you *must* similarly modify the restart_on_crash block
1422## at the beginning of the definition of notebook!!
1423def notebook(dir         ='sage_notebook',
1424             port        = 8000,
1425             address     = 'localhost',
1426             open_viewer = False,
1427             max_tries   = 10,
1428             username    = None,
1429             password    = None,
1430             color       = None,
1431             system      = None,
1432             jsmath      = True,
1433             show_debug  = False,
1434             splashpage  = None,
1435             warn        = True,
1436             ignore_lock = False,
1437             log_server = False,
1438             kill_idle   = 0,
1439             restart_on_crash = False,
1440             auto_restart = 1800):
1441    r"""
1442    Start a SAGE notebook web server at the given port.
1443
1444    INPUT:
1445        dir -- (default: 'sage_notebook') name of the server directory; your
1446                sessions are saved in a directory with that name.  If
1447                you restart the server with that same name then it will
1448                restart in the state you left it, but with none of the
1449                variables defined (you have to re-eval blocks).
1450        port -- (default: 8000) port on computer where the server is served
1451        address -- (default: 'localhost') address that the server
1452                   will listen on
1453        open_viewer -- bool (default:False); if True, pop up a web browser at the URL
1454        max_tries -- (default: 10) maximum number of ports > port to try in
1455                     case given port can't be opened.
1456        username -- user name used for authenticated logins
1457        password -- password used for authenticated logins
1458        color -- string or pair of html colors, e.g.,
1459                    'gmail'
1460                    'grey'
1461                    ('#ff0000', '#0000ff')
1462        system -- (string) default computer algebra system to use for new
1463                  worksheets, e.g., 'maxima', 'gp', 'axiom', 'mathematica', 'macaulay2',
1464                  'singular', 'gap', 'octave', 'maple', etc.  (even 'latex'!)
1465        jsmath -- whether not to enable javascript typset output for math.
1466        debug -- whether or not to show a javascript debugging window
1467        kill_idle -- if positive, kill any idle compute processes after
1468                     this many auto saves.  (NOT IMPLEMENTED)
1469
1470        splashpage -- whether or not to show a splash page when no worksheet is specified.
1471                      you can place a file named index.html into the notebook directory that
1472                      will be shown in place of the default.
1473
1474        restart_on_crash -- if True (the default is False), the server
1475                      will be automatically restarted if it crashes in
1476                      any way.  Use this on a public servers that many
1477                      people might use, and which might be subjected
1478                      to intense usage.  NOTE: Log messages are only displayed
1479                      every 5 seconds in this mode.
1480        auto_restart -- if restart_on_crash is True, always restart
1481                      the server every this many seconds.
1482
1483    NOTES:
1484
1485    When you type \code{notebook(...)}  you start a web server on the
1486    machine you type that command on.  You don't connect to another
1487    machine.  So do this if you want to start a SAGE notebook
1488    accessible from anywhere:
1489
1490    \begin{enumerate}
1491    \item Figure out the external address of your server, say
1492          'sage.math.washington.edu', for example.
1493    \item On your server, type
1494       server_http1('mysession', address='sage.math.washington.edu')
1495    \item Assuming you have permission to open a port on that
1496       machine, it will startup and display a URL, e.g.,
1497           \url{http://sage.math.washington.edu:8000}
1498       Note this URL.
1499    \item Go to any computer in the world (!), or at least
1500       behind your firewall, and use any web browser to
1501       visit the above URL.  You're using \sage.
1502    \end{enumerate}
1503   
1504    \note{There are no security precautions in place \emph{yet}!  If
1505    you open a server as above, and somebody figures this out, they
1506    could use their web browser to connect to the same sage session,
1507    and type something nasty like \code{os.system('cd; rm -rf *')}
1508    and your home directory would be hosed.   I'll be adding an
1509    authentication screen in the near future.  In the meantime
1510    (and even then), you should consider creating a user with
1511    very limited privileges (e.g., empty home directory).}
1512
1513    FIREFOX ISSUE:
1514    If your default web browser if Firefox, then notebook will
1515    open a copy of Firefox at the given URL.  You should
1516    definitely set the "open links in new tabs" option in
1517    Firefox, or you might loose a web page you were looking at.
1518    To do this, just go to
1519   
1520         Edit --> Preferences --> Tabs
1521         
1522    and in "Open links from other apps" select the middle button
1523    instead of the bottom button.
1524    """
1525    if restart_on_crash:
1526        # Start a new subprocess
1527        def f(x):  # format for passing on
1528            if x is None:
1529                return 'None'
1530            elif isinstance(x, str):
1531                return "'%s'"%x
1532            else:
1533                return str(x)
1534        while True:
1535            S = sage.interfaces.sage0.Sage()
1536            time.sleep(1)
1537            S.eval("from sage.server.notebook.notebook import notebook")
1538            cmd = "notebook(dir=%s,port=%s, address=%s, open_viewer=%s, max_tries=%s, username=%s, password=%s, color=%s, system=%s, jsmath=%s, show_debug=%s, splashpage=%s, warn=%s, ignore_lock=%s, log_server=%s, kill_idle=%s, restart_on_crash=False)"%(
1539                f(dir), f(port), f(address), f(open_viewer), f(max_tries), f(username),
1540                f(password), f(color), f(system), f(jsmath), f(show_debug), f(splashpage),
1541                f(warn), f(ignore_lock), f(log_server), f(kill_idle)
1542                )
1543            print cmd
1544            S._send(cmd)
1545            tm = 0
1546            while True:
1547                s = S._get()[1].strip()
1548                if len(s) > 0:
1549                    print s
1550                if not S.is_running():
1551                    break
1552                time.sleep(5)
1553                tm += 5
1554                if tm > auto_restart:
1555                    S.quit()
1556                    break
1557            # end while
1558        # end while
1559        S.quit()
1560        return
1561   
1562    if os.path.exists(dir):
1563        if not os.path.isdir(dir):
1564            raise RuntimeError, '"%s" is not a valid SAGE notebook directory (it is not even a directory).'%dir
1565        if not (os.path.exists('%s/nb.sobj'%dir) or os.path.exists('%s/nb-backup.sobj'%dir)):
1566            raise RuntimeError, '"%s" is not a valid SAGE notebook directory (missing nb.sobj).'%dir
1567        if os.path.exists('%s/pid'%dir) and not ignore_lock:
1568            f = file('%s/pid'%dir)
1569            p = f.read()
1570            f.close()
1571            try:
1572                #This is a hack to check whether or not the process is running.
1573                os.kill(int(p),0)
1574                print "\n".join([" This notebook appears to be running with PID %s.  If it is"%p,
1575                                 " not responding, you will need to kill that process to continue.",
1576                                 " If another (non-sage) process is running with that PID, call",
1577                                 " notebook(..., ignore_lock = True, ...). " ])
1578                return
1579            except OSError:
1580                pass
1581        f = file('%s/pid'%dir, 'w')
1582        f.write("%d"%os.getpid())
1583        f.close()
1584        try:
1585            nb = load('%s/nb.sobj'%dir, compress=False)
1586        except:
1587            print "****************************************************************"
1588            print "  * * * WARNING   * * * WARNING   * * * WARNING   * * * "
1589            print "WARNING -- failed to load notebook data. Trying the backup file."
1590            print "****************************************************************"
1591            try:
1592                nb = load('%s/nb-backup.sobj'%dir, compress=False)
1593            except:
1594                print "Recovering from last op save failed."
1595                print "Trying save from last startup."
1596                nb = load('%s/nb-older-backup.sobj'%dir, compress=False)
1597
1598        nb.set_directory(dir)
1599        if not (username is None):
1600            nb.set_auth(username=username, password=password)
1601        if not (color is None):
1602            nb.set_color(color)
1603        if not system is None:
1604            nb.set_system(system)
1605        if not splashpage is None:
1606            nb.set_splashpage(splashpage)
1607        nb.set_not_computing()
1608    else:
1609        nb = Notebook(dir,username=username,password=password, color=color,
1610                      system=system, kill_idle=kill_idle,splashpage=splashpage)
1611    nb.save()
1612    shutil.copy('%s/nb.sobj'%dir, '%s/nb-older-backup.sobj'%dir)
1613    nb.set_debug(show_debug)
1614    nb.set_log_server(log_server)
1615    if warn and address!='localhost' and username==None:
1616        print "WARNING -- it is *extremely* dangerous to let the server listen"
1617        print "on an external port without at least setting a username/password!!"
1618    nb.start(port, address, max_tries, open_viewer, jsmath=jsmath)
1619    from sage.interfaces.quit import expect_quitall
1620    expect_quitall(verbose=False)
1621    from sage.misc.misc import delete_tmpfiles
1622    delete_tmpfiles()
1623    if os.path.exists('%s/pid'%dir):
1624        os.remove('%s/pid'%dir)
1625    return nb
1626   
1627
1628########################################################################
1629# NOTES ABOUT THE restart_on_crash option to notebook.
1630## I made some changes to the notebook command so it has the option of running
1631## the server as a completely separate process, which will restart it if it
1632## dies in any way.   I installed this on sage.math and started those servers
1633## running with this.  So if anybody has any systematic way to crash the notebook
1634## servers, please try it!  (The only one I have is to tell a computer lab
1635## full of 40 high school students to all try to crash the server at once...)
1636##
1637## Basically what happens is this:
1638##
1639##   1. You start a Python process then run the notebook command with the
1640##      restart_on_kill option True.
1641##   2. The notebook command starts another Python running, and in that
1642##      it runs the notebook command.  This uses the Sage0 pexpect interface.
1643##   3. It then monitors the process started in 2 -- if the process dies
1644##      or terminates, then 2 occurs again.  The server log output
1645##      is updated every 5 seconds.   If ctrl-c is received,
1646##      then everything cleans up and terminates.
1647##
1648## This means that the SAGE notebook can only currently be totally
1649## crashed if the Python simple http server gets into a hung state.
1650## From looking at the server logs after past crashes, this doesn't
1651## seem to ever happen.  So now instead of the server crashing
1652## under crazy loads, etc., it will automatically reset within seconds --
1653## this would of course kill all running worksheets (which is bad),
1654## but is much better than having the whole sage notebook go down
1655## until it is manually restarted!   In the long run, of course, using
1656## Twisted for the web server should hopefully mean that it doesn't
1657## crash...
1658###############################################################
Note: See TracBrowser for help on using the repository browser.