diff options
Diffstat (limited to 'doc/codewalk')
-rw-r--r-- | doc/codewalk/codewalk.css | 234 | ||||
-rw-r--r-- | doc/codewalk/codewalk.js | 305 | ||||
-rw-r--r-- | doc/codewalk/codewalk.xml | 124 | ||||
-rw-r--r-- | doc/codewalk/popout.png | bin | 0 -> 213 bytes |
4 files changed, 663 insertions, 0 deletions
diff --git a/doc/codewalk/codewalk.css b/doc/codewalk/codewalk.css new file mode 100644 index 000000000..a0814e4d2 --- /dev/null +++ b/doc/codewalk/codewalk.css @@ -0,0 +1,234 @@ +/* + Copyright 2010 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +*/ + +#codewalk-main { + text-align: left; + width: 100%; + overflow: auto; +} + +#code-display { + border: 0; + width: 100%; +} + +.setting { + font-size: 8pt; + color: #888888; + padding: 5px; +} + +.hotkey { + text-decoration: underline; +} + +/* Style for Comments (the left-hand column) */ + +#comment-column { + margin: 0pt; + width: 30%; +} + +#comment-column.right { + float: right; +} + +#comment-column.left { + float: left; +} + +#comment-area { + overflow-x: hidden; + overflow-y: auto; +} + +.comment { + cursor: pointer; + font-size: 16px; + border: 2px solid #ba9836; + margin-bottom: 10px; + margin-right: 10px; /* yes, for both .left and .right */ +} + +.comment:last-child { + margin-bottom: 0px; +} + +.right .comment { + margin-left: 10px; +} + +.right .comment.first { +} + +.right .comment.last { +} + +.left .comment.first { +} + +.left .comment.last { +} + +.comment.selected { + border-color: #99b2cb; +} + +.right .comment.selected { + border-left-width: 12px; + margin-left: 0px; +} + +.left .comment.selected { + border-right-width: 12px; + margin-right: 0px; +} + +.comment-link { + display: none; +} + +.comment-title { + font-size: small; + font-weight: bold; + background-color: #fffff0; + padding-right: 10px; + padding-left: 10px; + padding-top: 5px; + padding-bottom: 5px; +} + +.right .comment-title { +} + +.left .comment-title { +} + +.comment.selected .comment-title { + background-color: #f8f8ff; +} + +.comment-text { + overflow: auto; + padding-left: 10px; + padding-right: 10px; + padding-top: 10px; + padding-bottom: 5px; + font-size: small; + line-height: 1.3em; +} + +.comment-text p { + margin-top: 0em; + margin-bottom: 0.5em; +} + +.comment-text p:last-child { + margin-bottom: 0em; +} + +.file-name { + font-size: x-small; + padding-top: 0px; + padding-bottom: 5px; +} + +.hidden-filepaths .file-name { + display: none; +} + +.path-dir { + color: #555; +} + +.path-file { + color: #555; +} + + +/* Style for Code (the right-hand column) */ + +/* Wrapper for the code column to make widths get calculated correctly */ +#code-column { + display: block; + position: relative; + margin: 0pt; + width: 70%; +} + +#code-column.left { + float: left; +} + +#code-column.right { + float: right; +} + +#code-area { + background-color: #f8f8ff; + border: 2px solid #99b2cb; + padding: 5px; +} + +.left #code-area { + margin-right: -1px; +} + +.right #code-area { + margin-left: -1px; +} + +#code-header { + margin-bottom: 5px; +} + +#code { + background-color: white; +} + +code { + font-size: 100%; +} + +.codewalkhighlight { + font-weight: bold; + background-color: #f8f8ff; +} + +#code-display { + margin-top: 0px; + margin-bottom: 0px; +} + +#sizer { + position: absolute; + cursor: col-resize; + left: 0px; + top: 0px; + width: 8px; +} + +/* Style for options (bottom strip) */ + +#code-options { + display: none; +} + +#code-options > span { + padding-right: 20px; +} + +#code-options .selected { + border-bottom: 1px dotted; +} + +#comment-options { + text-align: center; +} + +div#content { + padding-bottom: 0em; +} diff --git a/doc/codewalk/codewalk.js b/doc/codewalk/codewalk.js new file mode 100644 index 000000000..f780bc7a5 --- /dev/null +++ b/doc/codewalk/codewalk.js @@ -0,0 +1,305 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/** + * A class to hold information about the Codewalk Viewer. + * @param {jQuery} context The top element in whose context the viewer should + * operate. It will not touch any elements above this one. + * @constructor + */ + var CodewalkViewer = function(context) { + this.context = context; + + /** + * The div that contains all of the comments and their controls. + */ + this.commentColumn = this.context.find('#comment-column'); + + /** + * The div that contains the comments proper. + */ + this.commentArea = this.context.find('#comment-area'); + + /** + * The div that wraps the iframe with the code, as well as the drop down menu + * listing the different files. + * @type {jQuery} + */ + this.codeColumn = this.context.find('#code-column'); + + /** + * The div that contains the code but excludes the options strip. + * @type {jQuery} + */ + this.codeArea = this.context.find('#code-area'); + + /** + * The iframe that holds the code (from Sourcerer). + * @type {jQuery} + */ + this.codeDisplay = this.context.find('#code-display'); + + /** + * The overlaid div used as a grab handle for sizing the code/comment panes. + * @type {jQuery} + */ + this.sizer = this.context.find('#sizer'); + + /** + * The full-screen overlay that ensures we don't lose track of the mouse + * while dragging. + * @type {jQuery} + */ + this.overlay = this.context.find('#overlay'); + + /** + * The hidden input field that we use to hold the focus so that we can detect + * shortcut keypresses. + * @type {jQuery} + */ + this.shortcutInput = this.context.find('#shortcut-input'); + + /** + * The last comment that was selected. + * @type {jQuery} + */ + this.lastSelected = null; +}; + +/** + * Minimum width of the comments or code pane, in pixels. + * @type {number} + */ +CodewalkViewer.MIN_PANE_WIDTH = 200; + +/** + * Navigate the code iframe to the given url and update the code popout link. + * @param {string} url The target URL. + * @param {Object} opt_window Window dependency injection for testing only. + */ +CodewalkViewer.prototype.navigateToCode = function(url, opt_window) { + if (!opt_window) opt_window = window; + // Each iframe is represented by two distinct objects in the DOM: an iframe + // object and a window object. These do not expose the same capabilities. + // Here we need to get the window representation to get the location member, + // so we access it directly through window[] since jQuery returns the iframe + // representation. + // We replace location rather than set so as not to create a history for code + // navigation. + opt_window['code-display'].location.replace(url); + var k = url.indexOf('&'); + if (k != -1) url = url.slice(0, k); + k = url.indexOf('fileprint='); + if (k != -1) url = url.slice(k+10, url.length); + this.context.find('#code-popout-link').attr('href', url); +}; + +/** + * Selects the first comment from the list and forces a refresh of the code + * view. + */ +CodewalkViewer.prototype.selectFirstComment = function() { + // TODO(rsc): handle case where there are no comments + var firstSourcererLink = this.context.find('.comment:first'); + this.changeSelectedComment(firstSourcererLink); +}; + +/** + * Sets the target on all links nested inside comments to be _blank. + */ +CodewalkViewer.prototype.targetCommentLinksAtBlank = function() { + this.context.find('.comment a[href], #description a[href]').each(function() { + if (!this.target) this.target = '_blank'; + }); +}; + +/** + * Installs event handlers for all the events we care about. + */ +CodewalkViewer.prototype.installEventHandlers = function() { + var self = this; + + this.context.find('.comment') + .click(function(event) { + if (jQuery(event.target).is('a[href]')) return true; + self.changeSelectedComment(jQuery(this)); + return false; + }); + + this.context.find('#code-selector') + .change(function() {self.navigateToCode(jQuery(this).val());}); + + this.context.find('#description-table .quote-feet.setting') + .click(function() {self.toggleDescription(jQuery(this)); return false;}); + + this.sizer + .mousedown(function(ev) {self.startSizerDrag(ev); return false;}); + this.overlay + .mouseup(function(ev) {self.endSizerDrag(ev); return false;}) + .mousemove(function(ev) {self.handleSizerDrag(ev); return false;}); + + this.context.find('#prev-comment') + .click(function() { + self.changeSelectedComment(self.lastSelected.prev()); return false; + }); + + this.context.find('#next-comment') + .click(function() { + self.changeSelectedComment(self.lastSelected.next()); return false; + }); + + // Workaround for Firefox 2 and 3, which steal focus from the main document + // whenever the iframe content is (re)loaded. The input field is not shown, + // but is a way for us to bring focus back to a place where we can detect + // keypresses. + this.context.find('#code-display') + .load(function(ev) {self.shortcutInput.focus();}); + + jQuery(document).keypress(function(ev) { + switch(ev.which) { + case 110: // 'n' + self.changeSelectedComment(self.lastSelected.next()); + return false; + case 112: // 'p' + self.changeSelectedComment(self.lastSelected.prev()); + return false; + default: // ignore + } + }); + + window.onresize = function() {self.updateHeight();}; +}; + +/** + * Starts dragging the pane sizer. + * @param {Object} ev The mousedown event that started us dragging. + */ +CodewalkViewer.prototype.startSizerDrag = function(ev) { + this.initialCodeWidth = this.codeColumn.width(); + this.initialCommentsWidth = this.commentColumn.width(); + this.initialMouseX = ev.pageX; + this.overlay.show(); +}; + +/** + * Handles dragging the pane sizer. + * @param {Object} ev The mousemove event updating dragging position. + */ +CodewalkViewer.prototype.handleSizerDrag = function(ev) { + var delta = ev.pageX - this.initialMouseX; + if (this.codeColumn.is('.right')) delta = -delta; + var proposedCodeWidth = this.initialCodeWidth + delta; + var proposedCommentWidth = this.initialCommentsWidth - delta; + var mw = CodewalkViewer.MIN_PANE_WIDTH; + if (proposedCodeWidth < mw) delta = mw - this.initialCodeWidth; + if (proposedCommentWidth < mw) delta = this.initialCommentsWidth - mw; + proposedCodeWidth = this.initialCodeWidth + delta; + proposedCommentWidth = this.initialCommentsWidth - delta; + // If window is too small, don't even try to resize. + if (proposedCodeWidth < mw || proposedCommentWidth < mw) return; + this.codeColumn.width(proposedCodeWidth); + this.commentColumn.width(proposedCommentWidth); + this.options.codeWidth = parseInt( + this.codeColumn.width() / + (this.codeColumn.width() + this.commentColumn.width()) * 100); + this.context.find('#code-column-width').text(this.options.codeWidth + '%'); +}; + +/** + * Ends dragging the pane sizer. + * @param {Object} ev The mouseup event that caused us to stop dragging. + */ +CodewalkViewer.prototype.endSizerDrag = function(ev) { + this.overlay.hide(); + this.updateHeight(); +}; + +/** + * Toggles the Codewalk description between being shown and hidden. + * @param {jQuery} target The target that was clicked to trigger this function. + */ +CodewalkViewer.prototype.toggleDescription = function(target) { + var description = this.context.find('#description'); + description.toggle(); + target.find('span').text(description.is(':hidden') ? 'show' : 'hide'); + this.updateHeight(); +}; + +/** + * Changes the side of the window on which the code is shown and saves the + * setting in a cookie. + * @param {string?} codeSide The side on which the code should be, either + * 'left' or 'right'. + */ +CodewalkViewer.prototype.changeCodeSide = function(codeSide) { + var commentSide = codeSide == 'left' ? 'right' : 'left'; + this.context.find('#set-code-' + codeSide).addClass('selected'); + this.context.find('#set-code-' + commentSide).removeClass('selected'); + // Remove previous side class and add new one. + this.codeColumn.addClass(codeSide).removeClass(commentSide); + this.commentColumn.addClass(commentSide).removeClass(codeSide); + this.sizer.css(codeSide, 'auto').css(commentSide, 0); + this.options.codeSide = codeSide; +}; + +/** + * Adds selected class to newly selected comment, removes selected style from + * previously selected comment, changes drop down options so that the correct + * file is selected, and updates the code popout link. + * @param {jQuery} target The target that was clicked to trigger this function. + */ +CodewalkViewer.prototype.changeSelectedComment = function(target) { + var currentFile = target.find('.comment-link').attr('href'); + if (!currentFile) return; + + if (!(this.lastSelected && this.lastSelected.get(0) === target.get(0))) { + if (this.lastSelected) this.lastSelected.removeClass('selected'); + target.addClass('selected'); + this.lastSelected = target; + var targetTop = target.position().top; + var parentTop = target.parent().position().top; + if (targetTop + target.height() > parentTop + target.parent().height() || + targetTop < parentTop) { + var delta = targetTop - parentTop; + target.parent().animate( + {'scrollTop': target.parent().scrollTop() + delta}, + Math.max(delta / 2, 200), 'swing'); + } + var fname = currentFile.match(/(?:select=|fileprint=)\/[^&]+/)[0]; + fname = fname.slice(fname.indexOf('=')+2, fname.length); + this.context.find('#code-selector').val(fname); + this.context.find('#prev-comment').toggleClass( + 'disabled', !target.prev().length); + this.context.find('#next-comment').toggleClass( + 'disabled', !target.next().length); + } + + // Force original file even if user hasn't changed comments since they may + // have nagivated away from it within the iframe without us knowing. + this.navigateToCode(currentFile); +}; + +/** + * Updates the viewer by changing the height of the comments and code so that + * they fit within the height of the window. The function is typically called + * after the user changes the window size. + */ +CodewalkViewer.prototype.updateHeight = function() { + var windowHeight = jQuery(window).height() - 5 // GOK + var areaHeight = windowHeight - this.codeArea.offset().top + var footerHeight = this.context.find('#footer').outerHeight(true) + this.commentArea.height(areaHeight - footerHeight - this.context.find('#comment-options').outerHeight(true)) + var codeHeight = areaHeight - footerHeight - 15 // GOK + this.codeArea.height(codeHeight) + this.codeDisplay.height(codeHeight - this.codeDisplay.offset().top + this.codeArea.offset().top); + this.sizer.height(codeHeight); +}; + +jQuery(document).ready(function() { + var viewer = new CodewalkViewer(jQuery()); + viewer.selectFirstComment(); + viewer.targetCommentLinksAtBlank(); + viewer.installEventHandlers(); + viewer.updateHeight(); +}); diff --git a/doc/codewalk/codewalk.xml b/doc/codewalk/codewalk.xml new file mode 100644 index 000000000..9cd8361e8 --- /dev/null +++ b/doc/codewalk/codewalk.xml @@ -0,0 +1,124 @@ +<codewalk title="How to Write a Codewalk"> + +<step title="Introduction" src="doc/codewalk/codewalk.xml"> + A codewalk is a guided tour through a piece of code. + It consists of a sequence of steps, each typically explaining + a highlighted section of code. + <br/><br/> + + The <a href="/cmd/godoc">godoc</a> web server translates + an XML file like the one in the main window pane into the HTML + page that you're viewing now. + <br/><br/> + + The codewalk with URL path <code>/doc/codewalk/</code><i>name</i> + is loaded from the input file <code>$GOROOT/doc/codewalk/</code><i>name</i><code>.xml</code>. + <br/><br/> + + This codewalk explains how to write a codewalk by examining + its own source code, + <code><a href="/doc/codewalk/codewalk.xml">$GOROOT/doc/codewalk/codewalk.xml</a></code>, + shown in the main window pane to the left. +</step> + +<step title="Title" src="doc/codewalk/codewalk.xml:/title=/"> + The codewalk input file is an XML file containing a single + <code><codewalk></code> element. + That element's <code>title</code> attribute gives the title + that is used both on the codewalk page and in the codewalk list. +</step> + +<step title="Steps" src="doc/codewalk/codewalk.xml:/<step/,/step>/"> + Each step in the codewalk is a <code><step></code> element + nested inside the main <code><codewalk></code>. + The step element's <code>title</code> attribute gives the step's title, + which is shown in a shaded bar above the main step text. + The element's <code>src</code> attribute specifies the source + code to show in the main window pane and, optionally, a range of + lines to highlight. + <br/><br/> + + The first step in this codewalk does not highlight any lines: + its <code>src</code> is just a file name. +</step> + +<step title="Specifiying a source line" src='doc/codewalk/codewalk.xml:/title="Title"/'> + The most complex part of the codewalk specification is + saying what lines to highlight. + Instead of ordinary line numbers, + the codewalk uses an address syntax that makes it possible + to describe the match by its content. + As the file gets edited, this descriptive address has a better + chance to continue to refer to the right section of the file. + <br/><br/> + + To specify a source line, use a <code>src</code> attribute of the form + <i>filename</i><code>:</code><i>address</i>, + where <i>address</i> is an address in the syntax used by the text editors <i>sam</i> and <i>acme</i>. + <br/><br/> + + The simplest address is a single regular expression. + The highlighted line in the main window pane shows that the + address for the “Title” step was <code>/title=/</code>, + which matches the first instance of that <a href="/pkg/regexp">regular expression</a> (<code>title=</code>) in the file. +</step> + +<step title="Specifying a source range" src='doc/codewalk/codewalk.xml:/title="Steps"/'> + To highlight a range of source lines, the simplest address to use is + a pair of regular expressions + <code>/</code><i>regexp1</i><code>/,/</code><i>regexp2</i><code>/</code>. + The highlight begins with the line containing the first match for <i>regexp1</i> + and ends with the line containing the first match for <i>regexp2</i> + after the end of the match for <i>regexp1</i>. + Ignoring the HTML quoting, + The line containing the first match for <i>regexp1</i> will be the first one highlighted, + and the line containing the first match for <i>regexp2</i>. + <br/><br/> + + The address <code>/<step/,/step>/</code> looks for the first instance of + <code><step</code> in the file, and then starting after that point, + looks for the first instance of <code>step></code>. + (Click on the “Steps” step above to see the highlight in action.) + Note that the <code><</code> and <code>></code> had to be written + using XML escapes in order to be valid XML. +</step> + +<step title="Advanced addressing" src="doc/codewalk/codewalk.xml:/Advanced/,/step>/"> + The <code>/</code><i>regexp</i><code>/</code> + and <code>/</code><i>regexp1</i><code>/,/</code><i>regexp2</i><code>/</code> + forms suffice for most highlighting. + <br/><br/> + + The full address syntax is summarized in this table + (an excerpt of Table II from + <a href="http://plan9.bell-labs.com/sys/doc/sam/sam.html">The text editor <code>sam</code></a>): + <br/><br/> + + <table> + <tr><td colspan="2"><b>Simple addresses</b></td></tr> + <tr><td><code>#</code><i>n</i></td> + <td>The empty string after character <i>n</i></td></tr> + <tr><td><i>n</i></td> + <td>Line <i>n</i></td></tr> + <tr><td><code>/</code><i>regexp</i><code>/</code></td> + <td>The first following match of the regular expression</td></tr> + <!-- not supported (yet?) + <tr><td><code>–/</code><i>regexp</i><code>/</code></td> + <td>The first previous match of the regular expression</td></tr> + --> + <tr><td><code>$</code></td> + <td>The null string at the end of the file</td></tr> + + <tr><td colspan="2"><b>Compound addresses</b></td></tr> + <tr><td><i>a1</i><code>+</code><i>a2</i></td> + <td>The address <i>a2</i> evaluated starting at the right of <i>a1</i></td></tr> + <tr><td><i>a1</i><code>-</code><i>a2</i></td> + <td>The address <i>a2</i> evaluated in the reverse direction starting at the left of <i>a1</i></td></tr> + <tr><td><i>a1</i><code>,</code><i>a2</i></td> + <td>From the left of <i>a1</i> to the right of <i>a2</i> (default <code>0,$</code>).</td></tr> + </table> +</step> + + + +</codewalk> diff --git a/doc/codewalk/popout.png b/doc/codewalk/popout.png Binary files differnew file mode 100644 index 000000000..9c0c23638 --- /dev/null +++ b/doc/codewalk/popout.png |