mirror of
https://github.com/golang/go
synced 2024-11-23 19:40:08 -07:00
godoc: add codewalk support
R=adg, gri CC=golang-dev, r https://golang.org/cl/1008042
This commit is contained in:
parent
72d9322032
commit
2a591bdf8a
234
doc/codewalk/codewalk.css
Normal file
234
doc/codewalk/codewalk.css
Normal file
@ -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;
|
||||||
|
}
|
305
doc/codewalk/codewalk.js
Normal file
305
doc/codewalk/codewalk.js
Normal file
@ -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();
|
||||||
|
});
|
124
doc/codewalk/codewalk.xml
Normal file
124
doc/codewalk/codewalk.xml
Normal file
@ -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>
|
BIN
doc/codewalk/popout.png
Normal file
BIN
doc/codewalk/popout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 213 B |
@ -45,6 +45,7 @@ span.alert {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
font: 13px Helvetica, Arial, sans-serif;
|
font: 13px Helvetica, Arial, sans-serif;
|
||||||
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@ -102,6 +103,7 @@ div#content {
|
|||||||
margin-left: 20%;
|
margin-left: 20%;
|
||||||
padding: 0 1em 2em 1em;
|
padding: 0 1em 2em 1em;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
/*
|
/*
|
||||||
border-left: 2px solid #e5ecf9;
|
border-left: 2px solid #e5ecf9;
|
||||||
border-right: 2px solid #e5ecf9;
|
border-right: 2px solid #e5ecf9;
|
||||||
@ -156,12 +158,16 @@ div#linkList li.navhead {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
margin: 2em;
|
margin: 2em 0 0 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #555;
|
color: #555;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#footer p {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
#footer a {
|
#footer a {
|
||||||
color: #555;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
58
lib/godoc/codewalk.html
Normal file
58
lib/godoc/codewalk.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script src="http://www.google.com/jsapi"></script>
|
||||||
|
<script>google.load("jquery", "1.3.2");</script>
|
||||||
|
<style type='text/css'>@import "/doc/codewalk/codewalk.css";</style>
|
||||||
|
<script type="text/javascript" src="/doc/codewalk/codewalk.js"></script>
|
||||||
|
|
||||||
|
<div id="codewalk-main">
|
||||||
|
<div class="left" id="code-column">
|
||||||
|
<div id='sizer'></div>
|
||||||
|
<div id="code-area">
|
||||||
|
<div id="code-header" align="center">
|
||||||
|
<a id="code-popout-link" href="" target="_blank">
|
||||||
|
<img title="View code in new window" alt="Pop Out Code" src="popout.png" style="display: block; float: right;"/>
|
||||||
|
</a>
|
||||||
|
<select id="code-selector">
|
||||||
|
{.repeated section File}
|
||||||
|
<option value="/doc/codewalk/?fileprint=/{@|html-esc}">{@|html-esc}</option>
|
||||||
|
{.end}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="code">
|
||||||
|
<iframe class="code-display" name="code-display" id="code-display"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="code-options" class="setting">
|
||||||
|
<span>code on <a id="set-code-left" class="selected" href="#">left</a> • <a id="set-code-right" href="#">right</a></span>
|
||||||
|
<span>code width <span id="code-column-width">70%</span></span>
|
||||||
|
<span>filepaths <a id="show-filepaths" class="selected" href="#">shown</a> • <a id="hide-filepaths" href="#">hidden</a></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right" id="comment-column">
|
||||||
|
<div id="comment-area">
|
||||||
|
{.repeated section Step}
|
||||||
|
<div class="comment first last">
|
||||||
|
<a class="comment-link" href="/doc/codewalk/?fileprint=/{File|html-esc}&lo={Lo|html-esc}&hi={Hi|html-esc}#mark" target="code-display"></a>
|
||||||
|
<div class="comment-title">{Title|html-esc}</div>
|
||||||
|
<div class="comment-text">
|
||||||
|
{.section Err}
|
||||||
|
ERROR LOADING FILE: {Err|html-esc}<br/><br/>
|
||||||
|
{.end}
|
||||||
|
{XML}
|
||||||
|
</div>
|
||||||
|
<div class="comment-text file-name"><span class="path-file">{@|html-esc}</span></div>
|
||||||
|
</div>
|
||||||
|
{.end}
|
||||||
|
</div>
|
||||||
|
<div id="comment-options" class="setting">
|
||||||
|
<a id="prev-comment" href="#"><span class="hotkey">p</span>revious step</a>
|
||||||
|
•
|
||||||
|
<a id="next-comment" href="#"><span class="hotkey">n</span>ext step</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
15
lib/godoc/codewalkdir.html
Normal file
15
lib/godoc/codewalkdir.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<table class="layout">
|
||||||
|
{.repeated section @}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{Name|html-esc}">{Name|html-esc}</a></td>
|
||||||
|
<td width="25"> </td>
|
||||||
|
<td>{Title|html-esc}</td>
|
||||||
|
</tr>
|
||||||
|
{.end}
|
||||||
|
</table>
|
@ -6,6 +6,7 @@ include ../../Make.$(GOARCH)
|
|||||||
|
|
||||||
TARG=godoc
|
TARG=godoc
|
||||||
GOFILES=\
|
GOFILES=\
|
||||||
|
codewalk.go\
|
||||||
godoc.go\
|
godoc.go\
|
||||||
index.go\
|
index.go\
|
||||||
main.go\
|
main.go\
|
||||||
|
493
src/cmd/godoc/codewalk.go
Normal file
493
src/cmd/godoc/codewalk.go
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// The /doc/codewalk/ tree is synthesized from codewalk descriptions,
|
||||||
|
// files named $GOROOT/doc/codewalk/*.xml.
|
||||||
|
// For an example and a description of the format, see
|
||||||
|
// http://golang.org/doc/codewalk/codewalk or run godoc -http=:6060
|
||||||
|
// and see http://localhost:6060/doc/codewalk/codewalk .
|
||||||
|
// That page is itself a codewalk; the source code for it is
|
||||||
|
// $GOROOT/doc/codewalk/codewalk.xml.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/vector"
|
||||||
|
"fmt"
|
||||||
|
"http"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"template"
|
||||||
|
"utf8"
|
||||||
|
"xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// Handler for /doc/codewalk/ and below.
|
||||||
|
func codewalk(c *http.Conn, r *http.Request) {
|
||||||
|
relpath := r.URL.Path[len("/doc/codewalk/"):]
|
||||||
|
abspath := absolutePath(r.URL.Path[1:], *goroot)
|
||||||
|
|
||||||
|
r.ParseForm()
|
||||||
|
if f := r.FormValue("fileprint"); f != "" {
|
||||||
|
codewalkFileprint(c, r, f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If directory exists, serve list of code walks.
|
||||||
|
dir, err := os.Lstat(abspath)
|
||||||
|
if err == nil && dir.IsDirectory() {
|
||||||
|
codewalkDir(c, r, relpath, abspath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If file exists, serve using standard file server.
|
||||||
|
if err == nil {
|
||||||
|
serveFile(c, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise append .xml and hope to find
|
||||||
|
// a codewalk description.
|
||||||
|
cw, err := loadCodewalk(abspath + ".xml")
|
||||||
|
if err != nil {
|
||||||
|
log.Stderr(err)
|
||||||
|
serveError(c, r, relpath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := applyTemplate(codewalkHTML, "codewalk", cw)
|
||||||
|
servePage(c, "Codewalk: "+cw.Title, "", "", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A Codewalk represents a single codewalk read from an XML file.
|
||||||
|
type Codewalk struct {
|
||||||
|
Title string "attr"
|
||||||
|
File []string
|
||||||
|
Step []*Codestep
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A Codestep is a single step in a codewalk.
|
||||||
|
type Codestep struct {
|
||||||
|
// Filled in from XML
|
||||||
|
Src string "attr"
|
||||||
|
Title string "attr"
|
||||||
|
XML string "innerxml"
|
||||||
|
|
||||||
|
// Derived from Src; not in XML.
|
||||||
|
Err os.Error
|
||||||
|
File string
|
||||||
|
Lo int
|
||||||
|
LoByte int
|
||||||
|
Hi int
|
||||||
|
HiByte int
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// String method for printing in template.
|
||||||
|
// Formats file address nicely.
|
||||||
|
func (st *Codestep) String() string {
|
||||||
|
s := st.File
|
||||||
|
if st.Lo != 0 || st.Hi != 0 {
|
||||||
|
s += fmt.Sprintf(":%d", st.Lo)
|
||||||
|
if st.Lo != st.Hi {
|
||||||
|
s += fmt.Sprintf(",%d", st.Hi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// loadCodewalk reads a codewalk from the named XML file.
|
||||||
|
func loadCodewalk(file string) (*Codewalk, os.Error) {
|
||||||
|
f, err := os.Open(file, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
cw := new(Codewalk)
|
||||||
|
p := xml.NewParser(f)
|
||||||
|
p.Entity = xml.HTMLEntity
|
||||||
|
err = p.Unmarshal(cw, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &os.PathError{"parsing", file, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute file list, evaluate line numbers for addresses.
|
||||||
|
m := make(map[string]bool)
|
||||||
|
for _, st := range cw.Step {
|
||||||
|
i := strings.Index(st.Src, ":")
|
||||||
|
if i < 0 {
|
||||||
|
i = len(st.Src)
|
||||||
|
}
|
||||||
|
file := st.Src[0:i]
|
||||||
|
data, err := ioutil.ReadFile(absolutePath(file, *goroot))
|
||||||
|
if err != nil {
|
||||||
|
st.Err = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i < len(st.Src) {
|
||||||
|
lo, hi, err := addrToByteRange(st.Src[i+1:], 0, data)
|
||||||
|
if err != nil {
|
||||||
|
st.Err = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Expand match to line boundaries.
|
||||||
|
for lo > 0 && data[lo-1] != '\n' {
|
||||||
|
lo--
|
||||||
|
}
|
||||||
|
for hi < len(data) && (hi == 0 || data[hi-1] != '\n') {
|
||||||
|
hi++
|
||||||
|
}
|
||||||
|
st.Lo = byteToLine(data, lo)
|
||||||
|
st.Hi = byteToLine(data, hi-1)
|
||||||
|
}
|
||||||
|
st.Data = data
|
||||||
|
st.File = file
|
||||||
|
m[file] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make list of files
|
||||||
|
cw.File = make([]string, len(m))
|
||||||
|
i := 0
|
||||||
|
for f := range m {
|
||||||
|
cw.File[i] = f
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.SortStrings(cw.File)
|
||||||
|
|
||||||
|
return cw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// codewalkDir serves the codewalk directory listing.
|
||||||
|
// It scans the directory for subdirectories or files named *.xml
|
||||||
|
// and prepares a table.
|
||||||
|
func codewalkDir(c *http.Conn, r *http.Request, relpath, abspath string) {
|
||||||
|
type elem struct {
|
||||||
|
Name string
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err := ioutil.ReadDir(abspath)
|
||||||
|
if err != nil {
|
||||||
|
log.Stderr(err)
|
||||||
|
serveError(c, r, relpath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var v vector.Vector
|
||||||
|
for _, fi := range dir {
|
||||||
|
if fi.IsDirectory() {
|
||||||
|
v.Push(&elem{fi.Name + "/", ""})
|
||||||
|
} else if strings.HasSuffix(fi.Name, ".xml") {
|
||||||
|
cw, err := loadCodewalk(abspath + "/" + fi.Name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v.Push(&elem{fi.Name[0 : len(fi.Name)-len(".xml")], cw.Title})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b := applyTemplate(codewalkdirHTML, "codewalkdir", v)
|
||||||
|
servePage(c, "Codewalks", "", "", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// codewalkFileprint serves requests with ?fileprint=f&lo=lo&hi=hi.
|
||||||
|
// The filename f has already been retrieved and is passed as an argument.
|
||||||
|
// Lo and hi are the numbers of the first and last line to highlight
|
||||||
|
// in the response. This format is used for the middle window pane
|
||||||
|
// of the codewalk pages. It is a separate iframe and does not get
|
||||||
|
// the usual godoc HTML wrapper.
|
||||||
|
func codewalkFileprint(c *http.Conn, r *http.Request, f string) {
|
||||||
|
abspath := absolutePath(f, *goroot)
|
||||||
|
data, err := ioutil.ReadFile(abspath)
|
||||||
|
if err != nil {
|
||||||
|
serveError(c, r, f, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lo, _ := strconv.Atoi(r.FormValue("lo"))
|
||||||
|
hi, _ := strconv.Atoi(r.FormValue("hi"))
|
||||||
|
if hi < lo {
|
||||||
|
hi = lo
|
||||||
|
}
|
||||||
|
lo = lineToByte(data, lo)
|
||||||
|
hi = lineToByte(data, hi+1)
|
||||||
|
|
||||||
|
// Put the mark 4 lines before lo, so that the iframe
|
||||||
|
// shows a few lines of context before the highlighted
|
||||||
|
// section.
|
||||||
|
n := 4
|
||||||
|
mark := lo
|
||||||
|
for ; mark > 0 && n > 0; mark-- {
|
||||||
|
if data[mark-1] == '\n' {
|
||||||
|
if n--; n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteString(c, `<style type="text/css">@import "/doc/codewalk/codewalk.css";</style><pre>`)
|
||||||
|
template.HTMLEscape(c, data[0:mark])
|
||||||
|
io.WriteString(c, "<a name='mark'></a>")
|
||||||
|
template.HTMLEscape(c, data[mark:lo])
|
||||||
|
if lo < hi {
|
||||||
|
io.WriteString(c, "<div class='codewalkhighlight'>")
|
||||||
|
template.HTMLEscape(c, data[lo:hi])
|
||||||
|
io.WriteString(c, "</div>")
|
||||||
|
}
|
||||||
|
template.HTMLEscape(c, data[hi:])
|
||||||
|
io.WriteString(c, "</pre>")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// addrToByte evaluates the given address starting at offset start in data.
|
||||||
|
// It returns the lo and hi byte offset of the matched region within data.
|
||||||
|
// See http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II
|
||||||
|
// for details on the syntax.
|
||||||
|
func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err os.Error) {
|
||||||
|
var (
|
||||||
|
dir byte
|
||||||
|
prevc byte
|
||||||
|
charOffset bool
|
||||||
|
)
|
||||||
|
lo = start
|
||||||
|
hi = start
|
||||||
|
for addr != "" && err == nil {
|
||||||
|
c := addr[0]
|
||||||
|
switch c {
|
||||||
|
default:
|
||||||
|
err = os.NewError("invalid address syntax near " + string(c))
|
||||||
|
case ',':
|
||||||
|
if len(addr) == 1 {
|
||||||
|
hi = len(data)
|
||||||
|
} else {
|
||||||
|
_, hi, err = addrToByteRange(addr[1:], hi, data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case '+', '-':
|
||||||
|
if prevc == '+' || prevc == '-' {
|
||||||
|
lo, hi, err = addrNumber(data, lo, hi, prevc, 1, charOffset)
|
||||||
|
}
|
||||||
|
dir = c
|
||||||
|
|
||||||
|
case '$':
|
||||||
|
lo = len(data)
|
||||||
|
hi = len(data)
|
||||||
|
if len(addr) > 1 {
|
||||||
|
dir = '+'
|
||||||
|
}
|
||||||
|
|
||||||
|
case '#':
|
||||||
|
charOffset = true
|
||||||
|
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
var i int
|
||||||
|
for i = 1; i < len(addr); i++ {
|
||||||
|
if addr[i] < '0' || addr[i] > '9' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var n int
|
||||||
|
n, err = strconv.Atoi(addr[0:i])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lo, hi, err = addrNumber(data, lo, hi, dir, n, charOffset)
|
||||||
|
dir = 0
|
||||||
|
charOffset = false
|
||||||
|
prevc = c
|
||||||
|
addr = addr[i:]
|
||||||
|
continue
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
var i, j int
|
||||||
|
Regexp:
|
||||||
|
for i = 1; i < len(addr); i++ {
|
||||||
|
switch addr[i] {
|
||||||
|
case '\\':
|
||||||
|
i++
|
||||||
|
case '/':
|
||||||
|
j = i + 1
|
||||||
|
break Regexp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if j == 0 {
|
||||||
|
j = i
|
||||||
|
}
|
||||||
|
pattern := addr[1:i]
|
||||||
|
lo, hi, err = addrRegexp(data, lo, hi, dir, pattern)
|
||||||
|
prevc = c
|
||||||
|
addr = addr[j:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prevc = c
|
||||||
|
addr = addr[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && dir != 0 {
|
||||||
|
lo, hi, err = addrNumber(data, lo, hi, dir, 1, charOffset)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return lo, hi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// addrNumber applies the given dir, n, and charOffset to the address lo, hi.
|
||||||
|
// dir is '+' or '-', n is the count, and charOffset is true if the syntax
|
||||||
|
// used was #n. Applying +n (or +#n) means to advance n lines
|
||||||
|
// (or characters) after hi. Applying -n (or -#n) means to back up n lines
|
||||||
|
// (or characters) before lo.
|
||||||
|
// The return value is the new lo, hi.
|
||||||
|
func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, int, os.Error) {
|
||||||
|
switch dir {
|
||||||
|
case 0:
|
||||||
|
lo = 0
|
||||||
|
hi = 0
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case '+':
|
||||||
|
if charOffset {
|
||||||
|
pos := hi
|
||||||
|
for ; n > 0 && pos < len(data); n-- {
|
||||||
|
_, size := utf8.DecodeRune(data[pos:])
|
||||||
|
pos += size
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return pos, pos, nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// find next beginning of line
|
||||||
|
if hi > 0 {
|
||||||
|
for hi < len(data) && data[hi-1] != '\n' {
|
||||||
|
hi++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lo = hi
|
||||||
|
if n == 0 {
|
||||||
|
return lo, hi, nil
|
||||||
|
}
|
||||||
|
for ; hi < len(data); hi++ {
|
||||||
|
if data[hi] != '\n' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch n--; n {
|
||||||
|
case 1:
|
||||||
|
lo = hi + 1
|
||||||
|
case 0:
|
||||||
|
return lo, hi + 1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
if charOffset {
|
||||||
|
// Scan backward for bytes that are not UTF-8 continuation bytes.
|
||||||
|
pos := lo
|
||||||
|
for ; pos > 0 && n > 0; pos-- {
|
||||||
|
if data[pos]&0xc0 != 0x80 {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return pos, pos, nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// find earlier beginning of line
|
||||||
|
for lo > 0 && data[lo-1] != '\n' {
|
||||||
|
lo--
|
||||||
|
}
|
||||||
|
hi = lo
|
||||||
|
if n == 0 {
|
||||||
|
return lo, hi, nil
|
||||||
|
}
|
||||||
|
for ; lo >= 0; lo-- {
|
||||||
|
if lo > 0 && data[lo-1] != '\n' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch n--; n {
|
||||||
|
case 1:
|
||||||
|
hi = lo
|
||||||
|
case 0:
|
||||||
|
return lo, hi, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, 0, os.NewError("address out of range")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// addrRegexp searches for pattern in the given direction starting at lo, hi.
|
||||||
|
// The direction dir is '+' (search forward from hi) or '-' (search backward from lo).
|
||||||
|
// Backward searches are unimplemented.
|
||||||
|
func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, os.Error) {
|
||||||
|
re, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
if dir == '-' {
|
||||||
|
// Could implement reverse search using binary search
|
||||||
|
// through file, but that seems like overkill.
|
||||||
|
return 0, 0, os.NewError("reverse search not implemented")
|
||||||
|
}
|
||||||
|
m := re.Execute(data[hi:])
|
||||||
|
if len(m) > 0 {
|
||||||
|
m[0] += hi
|
||||||
|
m[1] += hi
|
||||||
|
} else if hi > 0 {
|
||||||
|
// No match. Wrap to beginning of data.
|
||||||
|
m = re.Execute(data)
|
||||||
|
}
|
||||||
|
if len(m) == 0 {
|
||||||
|
return 0, 0, os.NewError("no match for " + pattern)
|
||||||
|
}
|
||||||
|
return m[0], m[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// lineToByte returns the byte index of the first byte of line n.
|
||||||
|
// Line numbers begin at 1.
|
||||||
|
func lineToByte(data []byte, n int) int {
|
||||||
|
if n <= 1 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
n--
|
||||||
|
for i, c := range data {
|
||||||
|
if c == '\n' {
|
||||||
|
if n--; n == 0 {
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// byteToLine returns the number of the line containing the byte at index i.
|
||||||
|
func byteToLine(data []byte, i int) int {
|
||||||
|
l := 1
|
||||||
|
for j, c := range data {
|
||||||
|
if j == i {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
if c == '\n' {
|
||||||
|
l++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
@ -108,8 +108,9 @@ func initHandlers() {
|
|||||||
func registerPublicHandlers(mux *http.ServeMux) {
|
func registerPublicHandlers(mux *http.ServeMux) {
|
||||||
mux.Handle(cmdHandler.pattern, &cmdHandler)
|
mux.Handle(cmdHandler.pattern, &cmdHandler)
|
||||||
mux.Handle(pkgHandler.pattern, &pkgHandler)
|
mux.Handle(pkgHandler.pattern, &pkgHandler)
|
||||||
mux.Handle("/search", http.HandlerFunc(search))
|
mux.HandleFunc("/doc/codewalk/", codewalk)
|
||||||
mux.Handle("/", http.HandlerFunc(serveFile))
|
mux.HandleFunc("/search", search)
|
||||||
|
mux.HandleFunc("/", serveFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -849,6 +850,8 @@ func readTemplate(name string) *template.Template {
|
|||||||
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
codewalkHTML,
|
||||||
|
codewalkdirHTML,
|
||||||
dirlistHTML,
|
dirlistHTML,
|
||||||
errorHTML,
|
errorHTML,
|
||||||
godocHTML,
|
godocHTML,
|
||||||
@ -861,6 +864,8 @@ var (
|
|||||||
|
|
||||||
func readTemplates() {
|
func readTemplates() {
|
||||||
// have to delay until after flags processing since paths depend on goroot
|
// have to delay until after flags processing since paths depend on goroot
|
||||||
|
codewalkHTML = readTemplate("codewalk.html")
|
||||||
|
codewalkdirHTML = readTemplate("codewalkdir.html")
|
||||||
dirlistHTML = readTemplate("dirlist.html")
|
dirlistHTML = readTemplate("dirlist.html")
|
||||||
errorHTML = readTemplate("error.html")
|
errorHTML = readTemplate("error.html")
|
||||||
godocHTML = readTemplate("godoc.html")
|
godocHTML = readTemplate("godoc.html")
|
||||||
|
Loading…
Reference in New Issue
Block a user