                 setTimeout(f, 50)
 
     # purely client-side markdown rendering for a markdown block -- an optimization
-    execute_markdown_cell_on_client: (block) =>
+    execute_cell_client_side: (opts) =>
+        opts = defaults opts,
+            block     : required
+            mode_line : required
+            marker    : required
+
         cm = @codemirror
-        # get the text to be processed by markdown
-        console.log("block='#{cm.getRange({line:block.start,ch:0}, {line:block.end+1,ch:0})}'")
-        content = cm.getRange({line:block.start+2,ch:0}, {line:block.end+1,ch:0})
-        i = content.indexOf(MARKERS.output)
+        block = opts.block
+
+        console.log("execute_cell_client_side: block='#{cm.getRange({line:block.start,ch:0}, {line:block.end+1,ch:0})}'")
+
+        # get the input text
+        input = cm.getRange({line:block.start+2,ch:0}, {line:block.end+1,ch:0})
+        i = input.indexOf(MARKERS.output)
         if i != -1
-            content = content.slice(0,i)
-        console.log("content = '#{content}'")
+            output_uuid        = input.slice(i+1,i+37)
+            input              = input.slice(0,i)
+            has_output_already = true
+        else
+            output_uuid        = misc.uuid()
+            has_output_already = false
+
+        console.log("input = '#{input}'")
+        console.log("output_uuid = '#{output_uuid}'")
+
+        # determine the mode
+        i = opts.mode_line.indexOf('(')
+        if i == -1
+            mode = opts.mode_line.slice(1)
+        else
+            mode = opts.mode_line.slice(1,i)
+        hide = false
+        if mode in ['html', 'md'] and opts.mode_line.indexOf('false') == -1
+            hide = true
+
         # create corresponding output line: this is important since it ensures that all clients will *see* the new output too
-        output_line = MARKERS.output + misc.uuid() + MARKERS.output + misc.to_json(md:content) + MARKERS.output + '\n'
-        s = cm.getLine(block.end)
-        if s[0] == MARKERS.output
-            cm.replaceRange(output_line, {line:block.end,ch:0},{line:block.end+1,ch:0})
+        mesg = {}
+        if mode == 'javascript'
+            mesg['javascript'] = {code: input}
+        else if mode == 'coffeescript'
+            mesg['javascript'] = {coffeescript:true, code:input}
         else
-            cm.replaceRange(output_line, {line:block.end+1,ch:0},{line:block.end+1,ch:0})
+            mesg[mode] = input
+        output_line = MARKERS.output + output_uuid + MARKERS.output + misc.to_json(mesg) + MARKERS.output + '\n'
+        if has_output_already
+            cm.replaceRange(output_line, {line:block.end,ch:0},   {line:block.end+1,ch:0})
+        else
+            cm.replaceRange(output_line, {line:block.end+1,ch:0}, {line:block.end+1,ch:0})
+
+        # hide input if necessary
+        if hide
+            @set_cell_flag(opts.marker, FLAGS.hide_input)
+        else
+            @remove_cell_flag(opts.marker, FLAGS.hide_input)
 
         # update so that client sees rendering
         @process_sage_updates()

salvus_threejs = require("salvus_threejs")

account = require('account')

# Return true if there are currently unsynchronized changes, e.g., due to the network
# connection being down, or SageMathCloud not working, or a bug.

        k = n - (m - (j + 1))
        if k > 0
            cursor = cm.getCursor()
            cm.replaceRange(Array(k+1).join('\n'), {line:m+1, ch:0} )
            cm.setCursor(cursor)

    process_sage_updates: (start) =>
