Mercurial > dotfiles
changeset 0:c30d68fbd368
Initial import from svn.
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/.elisp/doctest-mode.el @@ -0,0 +1,2061 @@ +;;; doctest-mode.el --- Major mode for editing Python doctest files + +;; Copyright (C) 2004-2007 Edward Loper + +;; Author: Edward Loper +;; Maintainer: edloper@alum.mit.edu +;; Created: Aug 2004 +;; Keywords: python doctest unittest test docstring + +(defconst doctest-version "0.5 alpha" + "`doctest-mode' version number.") + +;; This software is provided as-is, without express or implied +;; warranty. Permission to use, copy, modify, distribute or sell this +;; software, without fee, for any purpose and by any individual or +;; organization, is hereby granted, provided that the above copyright +;; notice and this paragraph appear in all copies. + +;; This is a major mode for editing text files that contain Python +;; doctest examples. Doctest is a testing framework for Python that +;; emulates an interactive session, and checks the result of each +;; command. For more information, see the Python library reference: +;; <http://docs.python.org/lib/module-doctest.html> + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Table of Contents +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 1. Customization: use-editable variables to customize +;; doctest-mode. +;; +;; 2. Fonts: defines new font-lock faces. +;; +;; 3. Constants: various consts (mainly regexps) used by the rest +;; of the code. +;; +;; 4. Syntax Highlighting: defines variables and functions used by +;; font-lock-mode to perform syntax highlighting. +;; +;; 5. Source code editing & indentation: commands used to +;; automatically indent, dedent, & handle prompts. +;; +;; 6. Code Execution: commands used to start doctest processes, +;; and handle their output. +;; +;; 7. Markers: functions used to insert markers at the start of +;; doctest examples. These are used to keep track of the +;; correspondence between examples in the source buffer and +;; results in the output buffer. +;; +;; 8. Navigation: commands used to navigate between failed examples. +;; +;; 9. Replace Output: command used to replace a doctest example's +;; expected output with its actual output. +;; +;; 10. Helper functions: various helper functions used by the rest +;; of the code. +;; +;; 11. Emacs compatibility functions: defines compatible versions of +;; functions that are defined for some versions of emacs but not +;; others. +;; +;; 12. Doctest Results Mode: defines doctest-results-mode, which is +;; used for the output generated by doctest. +;; +;; 13. Doctest Mode: defines doctest-mode itself. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Customizable Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup doctest nil + "Support for the Python doctest framework" + :group 'languages + :prefix "doctest-") + +(defcustom doctest-default-margin 4 + "The default pre-prompt margin for doctest examples." + :type 'integer + :group 'doctest) + +(defcustom doctest-avoid-trailing-whitespace t + "If true, then delete trailing whitespace when inserting a newline." + :type 'boolean + :group 'doctest) + +(defcustom doctest-temp-directory + (let ((ok '(lambda (x) + (and x + (setq x (expand-file-name x)) ; always true + (file-directory-p x) + (file-writable-p x) + x)))) + (or (funcall ok (getenv "TMPDIR")) + (funcall ok "/usr/tmp") + (funcall ok "/tmp") + (funcall ok "/var/tmp") + (funcall ok ".") + (error (concat "Couldn't find a usable temp directory -- " + "set `doctest-temp-directory'")))) + "Directory used for temporary files created when running doctest. +By default, the first directory from this list that exists and that you +can write into: the value (if any) of the environment variable TMPDIR, +/usr/tmp, /tmp, /var/tmp, or the current directory." + :type 'string + :group 'doctest) + +(defcustom doctest-hide-example-source nil + "If true, then don't display the example source code for each +failure in the results buffer." + :type 'boolean + :group 'doctest) + +(defcustom doctest-python-command "python" + "Shell command used to start the python interpreter" + :type 'string + :group 'doctest) + +(defcustom doctest-results-buffer-name "*doctest-output (%N)*" + "The name of the buffer used to store the output of the doctest +command. This name can contain the following special sequences: + %n -- replaced by the doctest buffer's name. + %N -- replaced by the doctest buffer's name, with '.doctest' + stripped off. + %f -- replaced by the doctest buffer's filename." + :type 'string + :group 'doctest) + +(defcustom doctest-optionflags '() + "Option flags for doctest" + :group 'doctest + :type '(repeat (choice (const :tag "Select an option..." "") + (const :tag "Normalize whitespace" + "NORMALIZE_WHITESPACE") + (const :tag "Ellipsis" + "ELLIPSIS") + (const :tag "Don't accept True for 1" + "DONT_ACCEPT_TRUE_FOR_1") + (const :tag "Don't accept <BLANKLINE>" + "DONT_ACCEPT_BLANKLINE") + (const :tag "Ignore Exception detail" + "IGNORE_EXCEPTION_DETAIL") + (const :tag "Report only first failure" + "REPORT_ONLY_FIRST_FAILURE") + ))) + +(defcustom doctest-async t + "If true, then doctest will be run asynchronously." + :type 'boolean + :group 'doctest) + +(defcustom doctest-trim-exceptions t + "If true, then any exceptions inserted by doctest-replace-output +will have the stack trace lines trimmed." + :type 'boolean + :group 'doctest) + +(defcustom doctest-highlight-strings t + "If true, then highlight strings. If you find that doctest-mode +is responding slowly when you type, turning this off might help." + :type 'boolean + :group 'doctest) + +(defcustom doctest-follow-output t + "If true, then when doctest is run asynchronously, the output buffer +will scroll to display its output as it is generated. If false, then +the output buffer not scroll." + :type 'boolean + :group 'doctest) + +(defvar doctest-mode-hook nil + "Hook called by `doctest-mode'.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Fonts +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defface doctest-prompt-face + '((((class color) (background dark)) + (:foreground "#68f")) + (t (:foreground "#226"))) + "Face for Python prompts in doctest examples." + :group 'doctest) + +(defface doctest-output-face + '((((class color) (background dark)) + (:foreground "#afd")) + (t (:foreground "#262"))) + "Face for the output of doctest examples." + :group 'doctest) + +(defface doctest-output-marker-face + '((((class color) (background dark)) + (:foreground "#0f0")) + (t (:foreground "#080"))) + "Face for markers in the output of doctest examples." + :group 'doctest) + +(defface doctest-output-traceback-face + '((((class color) (background dark)) + (:foreground "#f88")) + (t (:foreground "#622"))) + "Face for traceback headers in the output of doctest examples." + :group 'doctest) + +(defface doctest-results-divider-face + '((((class color) (background dark)) + (:foreground "#08f")) + (t (:foreground "#00f"))) + "Face for dividers in the doctest results window." + :group 'doctest) + +(defface doctest-results-loc-face + '((((class color) (background dark)) + (:foreground "#0f8")) + (t (:foreground "#084"))) + "Face for location headers in the doctest results window." + :group 'doctest) + +(defface doctest-results-header-face + '((((class color) (background dark)) + (:foreground "#8ff")) + (t (:foreground "#088"))) + "Face for sub-headers in the doctest results window." + :group 'doctest) + +(defface doctest-results-selection-face + '((((class color) (background dark)) + (:foreground "#ff0" :background "#008")) + (t (:background "#088" :foreground "#fff"))) + "Face for selected failure's location header in the results window." + :group 'doctest) + +(defface doctest-selection-face + '((((class color) (background dark)) + (:foreground "#ff0" :background "#00f" :bold t)) + (t (:foreground "#f00"))) + "Face for selected example's prompt" + :group 'doctest) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst doctest-prompt-re + "^\\(?:\\([ \t]*\\)\\(>>> ?\\|[.][.][.] ?\\)\\([ \t]*\\)\\)" + "Regular expression for doctest prompts. It defines three groups: +the pre-prompt margin; the prompt; and the post-prompt indentation.") + +(defconst doctest-open-block-re + "[^\n]+:[ \t]*\\(#.*\\)?$" + "Regular expression for a line that opens a block") + +(defconst doctest-close-block-re + "\\(return\\|raise\\|break\\|continue\\|pass\\)\\b" + "Regular expression for a line that closes a block") + +(defconst doctest-example-source-re + "^Failed example:\n\\(\n\\| [^\n]*\n\\)+" + "Regular expression for example source in doctest's output.") + +(defconst doctest-results-divider-re + "^\\([*]\\{60,\\}\\)$" + "Regular expression for example dividers in doctest's output.") + +(defconst doctest-py24-results-loc-re + "^File \"[^\"]+\", line \\([0-9]+\\), in [^\n]+" + "Regular expression for example location markers in doctest's output.") + +(defconst doctest-py21-results-loc-re + "^from line #\\([0-9]+\\) of [^\n]*" + "Regular expression for example location markers in doctest's output, +when the output was generated by an old version of doctest.") + +(defconst doctest-results-header-re + "^\\([^ \n\t].+:\\|Expected nothing\\|Got nothing\\)$" + "Regular expression for example headers in doctest's output.") + +(defconst doctest-traceback-header-re + "^[ \t]*Traceback (\\(most recent call last\\|innermost last\\)):" + "Regular expression for Python traceback headers.") + +(defconst doctest-py21-results-re + "^from line #" + "Regular expression used to test which version of doctest was used.") + +;; nb: There's a bug in Python's traceback.print_exception function +;; which causes SyntaxError exceptions to be displayed incorrectly; +;; which prevents this regexp from matching. But there shouldn't be +;; too many people testing for SyntaxErrors, so I won't worry about +;; it. +(defconst doctest-traceback-re + (let ((nonprompt + ;; This matches any non-blank line that doesn't start with + ;; a prompt (... or >>>). + (concat + "\\(?:[.][.][^.\n]\\|[>][>][^>\n]\\|" + "[.][^.\n]\\|[>][^>\n]\\|[^.>\n \t]\\)[^\n]*"))) + (concat + "^\\(\\([ \t]*\\)Traceback " + "(\\(?:most recent call last\\|innermost last\\)):\n\\)" + "\\(?:\\2[ \t]+[^ \t\n][^\n]*\n\\)*" + "\\(\\(?:\\2" nonprompt "\n\\)" + "\\(?:\\2[ \t]*" nonprompt "\n\\)*\\)")) + "Regular expression that matches a complete exception traceback. +It contains three groups: group 1 is the header line; group 2 is +the indentation; and group 3 is the exception message.") + +(defconst doctest-blankline-re + "^[ \t]*<BLANKLINE>" + "Regular expression that matches blank line markers in doctest +output.") + +(defconst doctest-outdent-re + (concat "\\(" (mapconcat 'identity + '("else:" + "except\\(\\s +.*\\)?:" + "finally:" + "elif\\s +.*:") + "\\|") + "\\)") + "Regular expression for a line that should be outdented. Any line +that matches `doctest-outdent-re', but does not follow a line matching +`doctest-no-outdent-re', will be outdented.") + +;; It's not clear to me *why* the behavior given by this definition of +;; doctest-no-outdent-re is desirable; but it's basically just copied +;; from python-mode. +(defconst doctest-no-outdent-re + (concat + "\\(" + (mapconcat 'identity + (list "try:" + "except\\(\\s +.*\\)?:" + "while\\s +.*:" + "for\\s +.*:" + "if\\s +.*:" + "elif\\s +.*:" + "\\(return\\|raise\\|break\\|continue\\|pass\\)[ \t\n]" + ) + "\\|") + "\\)") + "Regular expression matching lines not to outdent after. Any line +that matches `doctest-outdent-re', but does not follow a line matching +`doctest-no-outdent-re', will be outdented.") + +(defconst doctest-script + "\ +from doctest import * +import sys +if '%m': + import imp + try: + m = imp.load_source('__imported__', '%m') + globs = m.__dict__ + except Exception, e: + print ('doctest-mode encountered an error while importing ' + 'the current buffer:\\n\\n %s' % e) + sys.exit(1) +else: + globs = {} +doc = open('%t').read() +if sys.version_info[:2] >= (2,4): + test = DocTestParser().get_doctest(doc, globs, '%n', '%f', 0) + r = DocTestRunner(optionflags=%l) + r.run(test) +else: + Tester(globs=globs).runstring(doc, '%f')" + ;; Docstring: + "Python script used to run doctest. +The following special sequences are defined: + %n -- replaced by the doctest buffer's name. + %f -- replaced by the doctest buffer's filename. + %l -- replaced by the doctest flags string. + %t -- replaced by the name of the tempfile containing the doctests." + ) + +(defconst doctest-keyword-re + (let* ((kw1 (mapconcat 'identity + '("and" "assert" "break" "class" + "continue" "def" "del" "elif" + "else" "except" "exec" "for" + "from" "global" "if" "import" + "in" "is" "lambda" "not" + "or" "pass" "print" "raise" + "return" "while" "yield" + ) + "\\|")) + (kw2 (mapconcat 'identity + '("else:" "except:" "finally:" "try:") + "\\|")) + (kw3 (mapconcat 'identity + '("ArithmeticError" "AssertionError" + "AttributeError" "DeprecationWarning" "EOFError" + "Ellipsis" "EnvironmentError" "Exception" "False" + "FloatingPointError" "FutureWarning" "IOError" + "ImportError" "IndentationError" "IndexError" + "KeyError" "KeyboardInterrupt" "LookupError" + "MemoryError" "NameError" "None" "NotImplemented" + "NotImplementedError" "OSError" "OverflowError" + "OverflowWarning" "PendingDeprecationWarning" + "ReferenceError" "RuntimeError" "RuntimeWarning" + "StandardError" "StopIteration" "SyntaxError" + "SyntaxWarning" "SystemError" "SystemExit" + "TabError" "True" "TypeError" "UnboundLocalError" + "UnicodeDecodeError" "UnicodeEncodeError" + "UnicodeError" "UnicodeTranslateError" + "UserWarning" "ValueError" "Warning" + "ZeroDivisionError" "__debug__" + "__import__" "__name__" "abs" "apply" "basestring" + "bool" "buffer" "callable" "chr" "classmethod" + "cmp" "coerce" "compile" "complex" "copyright" + "delattr" "dict" "dir" "divmod" + "enumerate" "eval" "execfile" "exit" "file" + "filter" "float" "getattr" "globals" "hasattr" + "hash" "hex" "id" "input" "int" "intern" + "isinstance" "issubclass" "iter" "len" "license" + "list" "locals" "long" "map" "max" "min" "object" + "oct" "open" "ord" "pow" "property" "range" + "raw_input" "reduce" "reload" "repr" "round" + "setattr" "slice" "staticmethod" "str" "sum" + "super" "tuple" "type" "unichr" "unicode" "vars" + "xrange" "zip") + "\\|")) + (pseudokw (mapconcat 'identity + '("self" "None" "True" "False" "Ellipsis") + "\\|")) + (string (concat "'\\(?:\\\\[^\n]\\|[^\n']*\\)'" "\\|" + "\"\\(?:\\\\[^\n]\\|[^\n\"]*\\)\"")) + (brk "\\(?:[ \t(]\\|$\\)")) + (concat + ;; Comments (group 1) + "\\(#.*\\)" + ;; Function & Class Definitions (groups 2-3) + "\\|\\b\\(class\\|def\\)" + "[ \t]+\\([a-zA-Z_][a-zA-Z0-9_]*\\)" + ;; Builtins preceeded by '.'(group 4) + "\\|[.][\t ]*\\(" kw3 "\\)" + ;; Keywords & builtins (group 5) + "\\|\\b\\(" kw1 "\\|" kw2 "\\|" + kw3 "\\|" pseudokw "\\)" brk + ;; Decorators (group 6) + "\\|\\(@[a-zA-Z_][a-zA-Z0-9_]*\\)" + )) + "A regular expression used for syntax highlighting of Python +source code.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Syntax Highlighting (font-lock mode) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Define the font-lock keyword table. +(defconst doctest-font-lock-keywords + `( + ;; The following pattern colorizes source lines. In particular, + ;; it first matches prompts, and then looks for any of the + ;; following matches *on the same line* as the prompt. It uses + ;; the form: + ;; + ;; (MATCHER MATCH-HIGHLIGHT + ;; (ANCHOR-MATCHER nil nil MATCH-HIGHLIGHT)) + ;; + ;; See the variable documentation for font-lock-keywords for a + ;; description of what each of those means. + ("^[ \t]*\\(>>>\\|\\.\\.\\.\\)" + (1 'doctest-prompt-face) + (doctest-source-matcher + nil nil + (1 'font-lock-comment-face t t) ; comments + (2 'font-lock-keyword-face t t) ; def/class + (3 'font-lock-type-face t t) ; func/class name + ;; group 4 (builtins preceeded by '.') gets no colorization. + (5 'font-lock-keyword-face t t) ; keywords & builtins + (6 'font-lock-preprocessor-face t t) ; decorators + (7 'font-lock-string-face t t) ; strings + )) + + ;; The following pattern colorizes output lines. In particular, + ;; it uses doctest-output-line-matcher to check if this is an + ;; output line, and if so, it colorizes it, and any special + ;; markers it contains. + (doctest-output-line-matcher + (0 'doctest-output-face t) + ("\\.\\.\\." (beginning-of-line) (end-of-line) + (0 'doctest-output-marker-face t)) + (,doctest-blankline-re (beginning-of-line) (end-of-line) + (0 'doctest-output-marker-face t)) + (doctest-traceback-line-matcher (beginning-of-line) (end-of-line) + (0 'doctest-output-traceback-face t)) + (,doctest-traceback-header-re (beginning-of-line) (end-of-line) + (0 'doctest-output-traceback-face t)) + ) + + ;; A PS1 prompt followed by a non-space is an error. + ("^[ \t]*\\(>>>[^ \t\n][^\n]*\\)" (1 'font-lock-warning-face t)) + ) + "Expressions to highlight in doctest-mode.") + +(defconst doctest-results-font-lock-keywords + `((,doctest-results-divider-re + (0 'doctest-results-divider-face)) + (,doctest-py24-results-loc-re + (0 'doctest-results-loc-face)) + (,doctest-results-header-re + (0 'doctest-results-header-face)) + (doctest-results-selection-matcher + (0 'doctest-results-selection-face t))) + "Expressions to highlight in doctest-results-mode.") + +(defun doctest-output-line-matcher (limit) + "A `font-lock-keyword' MATCHER that returns t if the current +line is the expected output for a doctest example, and if so, +sets `match-data' so that group 0 spans the current line." + ;; The real work is done by doctest-find-output-line. + (when (doctest-find-output-line limit) + ;; If we found one, then mark the entire line. + (beginning-of-line) + (re-search-forward "[^\n]*" limit))) + +(defun doctest-traceback-line-matcher (limit) + "A `font-lock-keyword' MATCHER that returns t if the current line is +the beginning of a traceback, and if so, sets `match-data' so that +group 0 spans the entire traceback. n.b.: limit is ignored." + (beginning-of-line) + (when (looking-at doctest-traceback-re) + (goto-char (match-end 0)) + t)) + +(defun doctest-source-matcher (limit) + "A `font-lock-keyword' MATCHER that returns t if the current line +contains a Python source expression that should be highlighted +after the point. If so, it sets `match-data' to cover the string +literal. The groups in `match-data' should be interpreted as follows: + + Group 1: comments + Group 2: def/class + Group 3: function/class name + Group 4: builtins preceeded by '.' + Group 5: keywords & builtins + Group 6: decorators + Group 7: strings +" + (let ((matchdata nil)) + ;; First, look for string literals. + (when doctest-highlight-strings + (save-excursion + (when (doctest-string-literal-matcher limit) + (setq matchdata + (list (match-beginning 0) (match-end 0) + nil nil nil nil nil nil nil nil nil nil nil nil + (match-beginning 0) (match-end 0)))))) + ;; Then, look for other keywords. If they occur before the + ;; string literal, then they take precedence. + (save-excursion + (when (and (re-search-forward doctest-keyword-re limit t) + (or (null matchdata) + (< (match-beginning 0) (car matchdata)))) + (setq matchdata (match-data)))) + (when matchdata + (set-match-data matchdata) + (goto-char (match-end 0)) + t))) + +(defun doctest-string-literal-matcher (limit &optional debug) + "A `font-lock-keyword' MATCHER that returns t if the current line +contains a string literal starting at or after the point. If so, it +expands `match-data' to cover the string literal. This matcher uses +`doctest-statement-info' to collect information about strings that +continue over multiple lines. It therefore might be a little slow for +very large statements." + (let* ((stmt-info (doctest-statement-info)) + (quotes (reverse (nth 5 stmt-info))) + (result nil)) + (if debug (doctest-debug "quotes %s" quotes)) + (while (and quotes (null result)) + (let* ((quote (pop quotes)) + (start (car quote)) + (end (min limit (or (cdr quote) limit)))) + (if debug (doctest-debug "quote %s-%s pt=%s lim=%s" + start end (point) limit)) + (when (or (and (<= (point) start) (< start limit)) + (and (< start (point)) (< (point) end))) + (setq start (max start (point))) + (set-match-data (list start end)) + (if debug (doctest-debug "marking string %s" (match-data))) + (goto-char end) + (setq result t)))) + result)) + +(defun doctest-results-selection-matcher (limit) + "Matches from `doctest-selected-failure' to the end of the +line. This is used to highlight the currently selected failure." + (when (and doctest-selected-failure + (<= (point) doctest-selected-failure) + (< doctest-selected-failure limit)) + (goto-char doctest-selected-failure) + (re-search-forward "[^\n]+" limit))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Source code editing & indentation +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun doctest-indent-source-line (&optional dedent-only) + "Re-indent the current line, as doctest source code. I.e., add a +prompt to the current line if it doesn't have one, and re-indent the +source code (to the right of the prompt). If `dedent-only' is true, +then don't increase the indentation level any." + (interactive "*") + (let ((indent-end nil)) + (save-excursion + (beginning-of-line) + (let ((new-indent (doctest-current-source-line-indentation dedent-only)) + (new-margin (doctest-current-source-line-margin)) + (line-had-prompt (looking-at doctest-prompt-re))) + ;; Delete the old prompt (if any). + (when line-had-prompt + (goto-char (match-beginning 2)) + (delete-char (- (match-end 2) (match-beginning 2)))) + ;; Delete the old indentation. + (delete-backward-char (skip-chars-forward " \t")) + ;; If it's a continuation line, or a new PS1 prompt, + ;; then copy the margin. + (when (or new-indent (not line-had-prompt)) + (beginning-of-line) + (delete-backward-char (skip-chars-forward " \t")) + (insert-char ?\ new-margin)) + ;; Add the new prompt. + (insert-string (if new-indent "... " ">>> ")) + ;; Add the new indentation + (if new-indent (insert-char ?\ new-indent)) + (setq indent-end (point)))) + ;; If we're left of the indentation end, then move up to the + ;; indentation end. + (if (< (point) indent-end) (goto-char indent-end)))) + +(defun doctest-current-source-line-indentation (&optional dedent-only) + "Return the post-prompt indent to use for this line. This is an +integer for a continuation lines, and nil for non-continuation lines." + (save-excursion + ;; Examine the previous doctest line (if present). + (let* ((prev-stmt-info (doctest-prev-statement-info)) + (prev-stmt-indent (nth 0 prev-stmt-info)) + (continuation-indent (nth 1 prev-stmt-info)) + (prev-stmt-opens-block (nth 2 prev-stmt-info)) + (prev-stmt-closes-block (nth 3 prev-stmt-info)) + (prev-stmt-blocks-outdent (nth 4 prev-stmt-info)) + ) + ;; Examine this doctest line. + (let* ((curr-line-indent 0) + (curr-line-outdented nil)) + (beginning-of-line) + (when (looking-at doctest-prompt-re) + (setq curr-line-indent (- (match-end 3) (match-beginning 3))) + (goto-char (match-end 3))) + (setq curr-line-outdented (and (looking-at doctest-outdent-re) + (not prev-stmt-blocks-outdent))) + ;; Compute the overall indent. + (let ((indent (or continuation-indent + (+ prev-stmt-indent + (if curr-line-outdented -4 0) + (if prev-stmt-opens-block 4 0) + (if prev-stmt-closes-block -4 0))))) + ;; If dedent-only is true, then make sure we don't indent. + (when dedent-only + (setq indent (min indent curr-line-indent))) + ;; If indent=0 and we're not outdented, then set indent to + ;; nil (to signify the start of a new source example). + (when (and (= indent 0) + (not (or curr-line-outdented continuation-indent))) + (setq indent nil)) + ;; Return the indentation. + indent))))) + +(defun doctest-prev-statement-info (&optional debug) + (save-excursion + (forward-line -1) + (doctest-statement-info debug))) + +(defun doctest-statement-info (&optional debug) + "Collect info about the previous statement, and return it as a list: + + (INDENT, CONTINUATION, OPENS-BLOCK, CLOSES-BLOCK, BLOCKS-OUTDENT, + QUOTES) + +INDENT -- The indentation of the previous statement (after the prompt) + +CONTINUATION -- If the previous statement is incomplete (e.g., has an +open paren or quote), then this is the appropriate indentation +level; otherwise, it's nil. + +OPENS-BLOCK -- True if the previous statement opens a Python control +block. + +CLOSES-BLOCK -- True if the previous statement closes a Python control +block. + +BLOCKS-OUTDENT -- True if the previous statement should 'block the +next statement from being considered an outdent. + +QUOTES -- A list of (START . END) pairs for all quotation strings. +" + (save-excursion + (end-of-line) + (let ((end (point))) + (while (and (doctest-on-source-line-p "...") (= (forward-line -1) 0))) + (cond + ;; If there's no previous >>> line, then give up. + ((not (doctest-on-source-line-p ">>>")) + '(0 nil nil nil nil)) + + ;; If there is a previous statement, walk through the source + ;; code, checking for operators that may be of interest. + (t + (beginning-of-line) + (let* ((quote-mark nil) (nesting 0) (indent-stack '()) + (stmt-indent 0) + (stmt-opens-block nil) + (stmt-closes-block nil) + (stmt-blocks-outdent nil) + (quotes '()) + (elt-re (concat "\\\\[^\n]\\|" + "(\\|)\\|\\[\\|\\]\\|{\\|}\\|" + "\"\"\"\\|\'\'\'\\|\"\\|\'\\|" + "#[^\n]*\\|" doctest-prompt-re))) + (while (re-search-forward elt-re end t) + (let* ((elt (match-string 0)) + (elt-first-char (substring elt 0 1))) + (if debug (doctest-debug "Debug: %s" elt)) + (cond + ;; Close quote -- set quote-mark back to nil. The + ;; second case is for cases like: ' ''' + (quote-mark + (cond + ((equal quote-mark elt) + (setq quote-mark nil) + (setcdr (car quotes) (point))) + ((equal quote-mark elt-first-char) + (setq quote-mark nil) + (setcdr (car quotes) (point)) + (backward-char 2)))) + ;; Prompt -- check if we're starting a new stmt. If so, + ;; then collect various info about it. + ((string-match doctest-prompt-re elt) + (when (and (null quote-mark) (= nesting 0)) + (let ((indent (- (match-end 3) (match-end 2)))) + (unless (looking-at "[ \t]*\n") + (setq stmt-indent indent) + (setq stmt-opens-block + (looking-at doctest-open-block-re)) + (setq stmt-closes-block + (looking-at doctest-close-block-re)) + (setq stmt-blocks-outdent + (looking-at doctest-no-outdent-re)))))) + ;; Open paren -- increment nesting, and update indent-stack. + ((string-match "(\\|\\[\\|{" elt-first-char) + (let ((elt-pos (point)) + (at-eol (looking-at "[ \t]*\n")) + (indent 0)) + (save-excursion + (re-search-backward doctest-prompt-re) + (if at-eol + (setq indent (+ 4 (- (match-end 3) (match-end 2)))) + (setq indent (- elt-pos (match-end 2)))) + (push indent indent-stack))) + (setq nesting (+ nesting 1))) + ;; Close paren -- decrement nesting, and pop indent-stack. + ((string-match ")\\|\\]\\|}" elt-first-char) + (setq indent-stack (cdr indent-stack)) + (setq nesting (max 0 (- nesting 1)))) + ;; Open quote -- set quote-mark. + ((string-match "\"\\|\'" elt-first-char) + (push (cons (- (point) (length elt)) nil) quotes) + (setq quote-mark elt))))) + + (let* ((continuation-indent + (cond + (quote-mark 0) + ((> nesting 0) (if (null indent-stack) 0 (car indent-stack))) + (t nil))) + (result + (list stmt-indent continuation-indent + stmt-opens-block stmt-closes-block + stmt-blocks-outdent quotes))) + (if debug (doctest-debug "Debug: %s" result)) + result))))))) + +(defun doctest-current-source-line-margin () + "Return the pre-prompt margin to use for this source line. This is +copied from the most recent source line, or set to +`doctest-default-margin' if there are no preceeding source lines." + (save-excursion + (save-restriction + (when (doctest-in-mmm-docstring-overlay) + (doctest-narrow-to-mmm-overlay)) + (beginning-of-line) + (forward-line -1) + (while (and (not (doctest-on-source-line-p)) + (re-search-backward doctest-prompt-re nil t)))) + (cond ((looking-at doctest-prompt-re) + (- (match-end 1) (match-beginning 1))) + ((doctest-in-mmm-docstring-overlay) + (doctest-default-margin-in-mmm-docstring-overlay)) + (t + doctest-default-margin)))) + +(defun doctest-electric-backspace () + "Delete the preceeding character, level of indentation, or +prompt. + +If point is at the leftmost column, delete the preceding newline. + +Otherwise, if point is at the first non-whitespace character +following an indented source line's prompt, then reduce the +indentation to the next multiple of 4; and update the source line's +prompt, when necessary. + +Otherwise, if point is at the first non-whitespace character +following an unindented source line's prompt, then remove the +prompt (converting the line to an output line or text line). + +Otherwise, if point is at the first non-whitespace character of a +line, the delete the line's indentation. + +Otherwise, delete the preceeding character. +" + (interactive "*") + (cond + ;; Beginning of line: delete preceeding newline. + ((bolp) (backward-delete-char 1)) + + ;; First non-ws char following prompt: dedent or remove prompt. + ((and (looking-at "[^ \t\n]\\|$") (doctest-looking-back doctest-prompt-re)) + (let* ((prompt-beg (match-beginning 2)) + (indent-beg (match-beginning 3)) (indent-end (match-end 3)) + (old-indent (- indent-end indent-beg)) + (new-indent (* (/ (- old-indent 1) 4) 4))) + (cond + ;; Indented source line: dedent it. + ((> old-indent 0) + (goto-char indent-beg) + (delete-region indent-beg indent-end) + (insert-char ?\ new-indent) + ;; Change prompt to PS1, when appropriate. + (when (and (= new-indent 0) (not (looking-at doctest-outdent-re))) + (delete-backward-char 4) + (insert-string ">>> "))) + ;; Non-indented source line: remove prompt. + (t + (goto-char indent-end) + (delete-region prompt-beg indent-end))))) + + ;; First non-ws char of a line: delete all indentation. + ((and (looking-at "[^ \n\t]\\|$") (doctest-looking-back "^[ \t]+")) + (delete-region (match-beginning 0) (match-end 0))) + + ;; Otherwise: delete a character. + (t + (backward-delete-char 1)))) + +(defun doctest-newline-and-indent () + "Insert a newline, and indent the new line appropriately. + +If the current line is a source line containing a bare prompt, +then clear the current line, and insert a newline. + +Otherwise, if the current line is a source line, then insert a +newline, and add an appropriately indented prompt to the new +line. + +Otherwise, if the current line is an output line, then insert a +newline and indent the new line to match the example's margin. + +Otherwise, insert a newline. + +If `doctest-avoid-trailing-whitespace' is true, then clear any +whitespace to the left of the point before inserting a newline. +" + (interactive "*") + ;; If we're avoiding trailing spaces, then delete WS before point. + (if doctest-avoid-trailing-whitespace + (delete-char (- (skip-chars-backward " \t")))) + (cond + ;; If we're on an empty prompt, delete it. + ((doctest-on-empty-source-line-p) + (delete-region (match-beginning 0) (match-end 0)) + (insert-char ?\n 1)) + ;; If we're on a doctest line, add a new prompt. + ((doctest-on-source-line-p) + (insert-char ?\n 1) + (doctest-indent-source-line)) + ;; If we're in doctest output, indent to the margin. + ((doctest-on-output-line-p) + (insert-char ?\n 1) + (insert-char ?\ (doctest-current-source-line-margin))) + ;; Otherwise, just add a newline. + (t (insert-char ?\n 1)))) + +(defun doctest-electric-colon () + "Insert a colon, and dedent the line when appropriate." + (interactive "*") + (insert-char ?: 1) + (when (doctest-on-source-line-p) + (doctest-indent-source-line t))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Code Execution +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun doctest-execute () + "Run doctest on the current buffer, or on the current docstring +if the point is inside an `mmm-mode' `doctest-docstring' region. +Display the results in the *doctest-output* buffer." + (interactive) + (doctest-execute-region (point-min) (point-max) nil t)) + +(defun doctest-execute-with-diff () + "Run doctest on the current buffer, or on the current docstring +if the point is inside an `mmm-mode' `doctest-docstring' region. +Display the results in the *doctest-output* buffer, using diff format." + (interactive) + (doctest-execute-region (point-min) (point-max) t t)) + +(defun doctest-execute-buffer-with-diff () + "Run doctest on the current buffer, and display the results in the +*doctest-output* buffer, using the diff format." + (interactive) + (doctest-execute-region (point-min) (point-max) t nil)) + +(defun doctest-execute-buffer () + "Run doctest on the current buffer, and display the results in the +*doctest-output* buffer." + (interactive) + (doctest-execute-region (point-min) (point-max) nil nil)) + +(defun doctest-execute-region (start end &optional diff + check-for-mmm-docstring-overlay) + "Run doctest on the current buffer, and display the results in the +*doctest-output* buffer." + (interactive "r") + ;; If it's already running, give the user a chance to restart it. + (when (doctest-process-live-p doctest-async-process) + (when (y-or-n-p "Doctest is already running. Restart it? ") + (doctest-cancel-async-process) + (message "Killing doctest..."))) + (cond + ((and doctest-async (doctest-process-live-p doctest-async-process)) + (message "Can't run two doctest processes at once!")) + (t + (let* ((results-buf-name (doctest-results-buffer-name)) + (in-docstring (and check-for-mmm-docstring-overlay + (doctest-in-mmm-docstring-overlay))) + (temp (doctest-temp-name)) (dir doctest-temp-directory) + (input-file (expand-file-name (concat temp ".py") dir)) + (globs-file (when in-docstring + (expand-file-name (concat temp "-globs.py") dir))) + (cur-buf (current-buffer)) + (in-buf (get-buffer-create "*doctest-input*")) + (script (doctest-script input-file globs-file diff))) + ;; If we're in a docstring, narrow start & end. + (when in-docstring + (let ((bounds (doctest-mmm-overlay-bounds))) + (setq start (max start (car bounds)) + end (min end (cdr bounds))))) + ;; Write the doctests to a file. + (save-excursion + (goto-char (min start end)) + (let ((lineno (doctest-line-number))) + (set-buffer in-buf) + ;; Add blank lines, to keep line numbers the same: + (dotimes (n (- lineno 1)) (insert-string "\n")) + ;; Add the selected region + (insert-buffer-substring cur-buf start end) + ;; Write it to a file + (write-file input-file))) + ;; If requested, write the buffer to a file for use as globs. + (when globs-file + (let ((cur-buf-start (point-min)) (cur-buf-end (point-max))) + (save-excursion + (set-buffer in-buf) + (delete-region (point-min) (point-max)) + (insert-buffer-substring cur-buf cur-buf-start cur-buf-end) + (write-file globs-file)))) + ;; Dispose of in-buf (we're done with it now. + (kill-buffer in-buf) + ;; Prepare the results buffer. Clear it, if it contains + ;; anything, and set its mode. + (setq doctest-results-buffer (get-buffer-create results-buf-name)) + (save-excursion + (set-buffer doctest-results-buffer) + (toggle-read-only 0) + (delete-region (point-min) (point-max)) + (doctest-results-mode) + (setq doctest-source-buffer cur-buf) + ) + ;; Add markers to examples, and record what line number each one + ;; starts at. That way, if the input buffer is edited, we can + ;; still find corresponding examples in the output. + (doctest-mark-examples) + + ;; Run doctest + (cond (doctest-async + ;; Asynchronous mode: + (let ((process (start-process "*doctest*" doctest-results-buffer + doctest-python-command + "-c" script))) + ;; Store some information about the process. + (setq doctest-async-process-buffer cur-buf) + (setq doctest-async-process process) + (push input-file doctest-async-process-tempfiles) + (when globs-file + (push globs-file doctest-async-process-tempfiles)) + ;; Set up a sentinel to respond when it's done running. + (set-process-sentinel process 'doctest-async-process-sentinel) + + ;; Show the output window. + (let ((w (display-buffer doctest-results-buffer))) + (when doctest-follow-output + ;; Insert a newline, which will move the buffer's + ;; point past the process's mark -- this causes the + ;; window to scroll as new output is generated. + (save-current-buffer + (set-buffer doctest-results-buffer) + (insert-string "\n") + (set-window-point w (point))))) + + ;; Let the user know the process is running. + (doctest-update-mode-line ":running") + (message "Running doctest..."))) + (t + ;; Synchronous mode: + (call-process doctest-python-command nil + doctest-results-buffer t "-c" script) + (doctest-handle-output) + (delete-file input-file) + (when globs-file + (delete-file globs-file)))))))) + +(defun doctest-handle-output () + "This function, which is called after the 'doctest' process spawned +by doctest-execute-buffer has finished, checks the doctest results +buffer. If that buffer is empty, it reports no errors and hides it; +if that buffer is not empty, it reports that errors occured, displays +the buffer, and runs doctest-postprocess-results." + ;; If any tests failed, display them. + (cond ((not (buffer-live-p doctest-results-buffer)) + (doctest-warn "Results buffer not found!")) + ((> (buffer-size doctest-results-buffer) 1) + (display-buffer doctest-results-buffer) + (doctest-postprocess-results) + (let ((num (length doctest-example-markers))) + (message "%d doctest example%s failed!" num + (if (= num 1) "" "s")))) + (t + (display-buffer doctest-results-buffer) + (delete-windows-on doctest-results-buffer) + (message "All doctest examples passed!")))) + +(defun doctest-async-process-sentinel (process state) + "A process sentinel, called when the asynchronous doctest process +completes, which calls doctest-handle-output." + ;; Check to make sure we got the process we're expecting. On + ;; some operating systems, this will end up getting called twice + ;; when we use doctest-cancel-async-process; this check keeps us + ;; from trying to clean up after the same process twice (since we + ;; set doctest-async-process to nil when we're done). + (when (and (equal process doctest-async-process) + (buffer-live-p doctest-async-process-buffer)) + (save-current-buffer + (set-buffer doctest-async-process-buffer) + (cond ((not (buffer-live-p doctest-results-buffer)) + (doctest-warn "Results buffer not found!")) + ((equal state "finished\n") + (doctest-handle-output) + (let ((window (get-buffer-window + doctest-async-process-buffer t))) + (when window (set-window-point window (point))))) + ((equal state "killed\n") + (message "Doctest killed.")) + (t + (message "Doctest failed -- %s" state) + (display-buffer doctest-results-buffer))) + (doctest-update-mode-line "") + (while doctest-async-process-tempfiles + (delete-file (pop doctest-async-process-tempfiles))) + (setq doctest-async-process nil)))) + +(defun doctest-cancel-async-process () + "If a doctest process is running, then kill it." + (interactive "") + (when (doctest-process-live-p doctest-async-process) + ;; Update the modeline + (doctest-update-mode-line ":killing") + ;; Kill the process. + (kill-process doctest-async-process) + ;; Run the sentinel. (Depending on what OS we're on, the sentinel + ;; may end up getting called once or twice.) + (doctest-async-process-sentinel doctest-async-process "killed\n") + )) + +(defun doctest-postprocess-results () + "Post-process the doctest results buffer: check what version of +doctest was used, and set doctest-results-py-version accordingly; +turn on read-only mode; filter the example markers; hide the example +source (if `doctest-hide-example-source' is non-nil); and select the +first failure." + (save-excursion + (set-buffer doctest-results-buffer) + ;; Check if we're using an old doctest version. + (goto-char (point-min)) + (if (re-search-forward doctest-py21-results-re nil t) + (setq doctest-results-py-version 'py21) + (setq doctest-results-py-version 'py24)) + ;; Turn on read-only mode. + (toggle-read-only t)) + + (doctest-filter-example-markers) + (if doctest-hide-example-source + (doctest-hide-example-source)) + (doctest-next-failure 1)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Markers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun doctest-mark-examples () + "Add a marker at the beginning of every (likely) example in the +input buffer; and create a list, `doctest-example-markers', +which maps from markers to the line numbers they originally occured +on. This will allow us to find the corresponding example in the +doctest output, even if the input buffer is edited." + (dolist (marker-info doctest-example-markers) + (set-marker (car marker-info) nil)) + (setq doctest-example-markers '()) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "^ *>>> " nil t) + (backward-char 4) + (push (cons (point-marker) (doctest-line-number)) + doctest-example-markers) + (forward-char 4)))) + +(defun doctest-filter-example-markers () + "Remove any entries from `doctest-example-markers' that do not +correspond to a failed example." + (let ((filtered nil) (markers doctest-example-markers)) + (save-excursion + (set-buffer doctest-results-buffer) + (goto-char (point-max)) + (while (re-search-backward (doctest-results-loc-re) nil t) + (let ((lineno (string-to-int (match-string 1)))) + (when (equal doctest-results-py-version 'py21) + (setq lineno (+ lineno 1))) + (while (and markers (< lineno (cdar markers))) + (set-marker (caar markers) nil) + (setq markers (cdr markers))) + (if (and markers (= lineno (cdar markers))) + (push (pop markers) filtered) + (doctest-warn "Example expected on line %d but not found %s" + lineno markers))))) + (dolist (marker-info markers) + (set-marker (car marker-info) nil)) + (setq doctest-example-markers filtered))) + +(defun doctest-prev-example-marker () + "Helper for doctest-replace-output: move to the preceeding example +marker, and return the corresponding 'original' lineno. If none is +found, return nil." + (let ((lineno nil) + (pos nil)) + (save-excursion + (end-of-line) + (when (re-search-backward "^\\( *\\)>>> " nil t) + (goto-char (match-end 1)) + (dolist (marker-info doctest-example-markers) + (when (= (marker-position (car marker-info)) (point)) + (setq lineno (cdr marker-info)) + (setq pos (point)))))) + (unless (null lineno) + (goto-char pos) + lineno))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Navigation +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun doctest-next-failure (count) + "Move to the top of the next failing example, and highlight the +example's failure description in *doctest-output*." + (interactive "p") + (cond + ((and doctest-async (doctest-process-live-p doctest-async-process)) + (message "Wait for doctest to finish running!")) + ((not (doctest-results-buffer-valid-p)) + (message "Run doctest first! (C-c C-c)")) + ((equal count 0) + t) + (t + (let ((marker nil) (example-markers doctest-example-markers) + (results-window (display-buffer doctest-results-buffer))) + (save-excursion + (set-buffer doctest-results-buffer) + ;; Pick up where we left off. + ;; (nb: doctest-selected-failure is buffer-local) + (goto-char (or doctest-selected-failure (point-min))) + ;; Skip past anything on *this* line. + (if (>= count 0) (end-of-line) (beginning-of-line)) + ;; Look for the next failure + (when + (if (>= count 0) + (re-search-forward (doctest-results-loc-re) nil t count) + (re-search-backward (doctest-results-loc-re) nil t (- count))) + ;; We found a failure: + (let ((old-selected-failure doctest-selected-failure)) + (beginning-of-line) + ;; Extract the line number for the doctest file. + (let ((orig-lineno (string-to-int (match-string 1)))) + (when (equal doctest-results-py-version 'py21) + (setq orig-lineno (+ orig-lineno 1))) + (dolist (marker-info example-markers) + (when (= orig-lineno (cdr marker-info)) + (setq marker (car marker-info))))) + + ;; Update the window cursor. + (beginning-of-line) + (set-window-point results-window (point)) + ;; Store our position for next time. + (setq doctest-selected-failure (point)) + ;; Update selection. + (doctest-fontify-line old-selected-failure) + (doctest-fontify-line doctest-selected-failure)))) + + (cond + ;; We found a failure -- move point to the selected failure. + (marker + (goto-char (marker-position marker)) + (beginning-of-line)) + ;; We didn't find a failure, but there is one -- wrap. + ((> (length doctest-example-markers) 0) + (if (>= count 0) (doctest-first-failure) (doctest-last-failure))) + ;; We didn't find a failure -- alert the user. + (t (message "No failures found!"))))))) + +(defun doctest-prev-failure (count) + "Move to the top of the previous failing example, and highlight +the example's failure description in *doctest-output*." + (interactive "p") + (doctest-next-failure (- count))) + +(defun doctest-first-failure () + "Move to the top of the first failing example, and highlight +the example's failure description in *doctest-output*." + (interactive) + (if (buffer-live-p doctest-results-buffer) + (save-excursion + (set-buffer doctest-results-buffer) + (let ((old-selected-failure doctest-selected-failure)) + (setq doctest-selected-failure (point-min)) + (doctest-fontify-line old-selected-failure)))) + (doctest-next-failure 1)) + +(defun doctest-last-failure () + "Move to the top of the last failing example, and highlight +the example's failure description in *doctest-output*." + (interactive) + (if (buffer-live-p doctest-results-buffer) + (save-excursion + (set-buffer doctest-results-buffer) + (let ((old-selected-failure doctest-selected-failure)) + (setq doctest-selected-failure (point-max)) + (doctest-fontify-line old-selected-failure)))) + (doctest-next-failure -1)) + +(defun doctest-select-failure () + "Move to the top of the currently selected example, and select that +example in the source buffer. Intended for use in the results +buffer." + (interactive) + (re-search-backward doctest-results-divider-re) + (let ((old-selected-failure doctest-selected-failure)) + (setq doctest-selected-failure (point)) + (doctest-fontify-line doctest-selected-failure) + (doctest-fontify-line old-selected-failure)) + (pop-to-buffer doctest-source-buffer) + (doctest-next-failure 1)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Replace Output +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun doctest-replace-output () + "Move to the top of the closest example, and replace its output +with the 'got' output from the *doctest-output* buffer. An error is +displayed if the chosen example is not listed in *doctest-output*, or +if the 'expected' output for the example does not exactly match the +output listed in the source buffer. The user is asked to confirm the +replacement." + (interactive) + ;; Move to the beginning of the example. + (cond + ((and doctest-async (doctest-process-live-p doctest-async-process)) + (message "Wait for doctest to finish running!")) + ((not (doctest-results-buffer-valid-p)) + (message "Run doctest first! (C-c C-c)")) + ((save-excursion (set-buffer doctest-results-buffer) + (equal doctest-results-py-version 'py21)) + (error "doctest-replace-output requires python 2.4+")) + (t + (save-excursion + (save-restriction + (when (doctest-in-mmm-docstring-overlay) + (doctest-narrow-to-mmm-overlay)) + + (let* ((orig-buffer (current-buffer)) + ;; Find an example, and look up its original lineno. + (lineno (doctest-prev-example-marker)) + ;; Find the example's indentation. + (prompt-indent (doctest-line-indentation))) + + ;; Switch to the output buffer, and look for the example. + ;; If we don't find one, complain. + (cond + ((null lineno) (message "Doctest example not found")) + (t + (set-buffer doctest-results-buffer) + (goto-char (point-min)) + (let ((output-re (format "^File .*, line %s," lineno))) + (when (not (re-search-forward output-re nil t)) + (message "This doctest example did not fail") + (setq lineno nil))))) + + ;; If we didn't find an example, give up. + (when (not (null lineno)) + ;; Get the output's 'expected' & 'got' texts. + (let ((doctest-got nil) (doctest-expected nil) (header nil)) + (while (setq header (doctest-results-next-header)) + (cond + ((equal header "Failed example:") + t) + ((equal header "Expected nothing") + (setq doctest-expected "")) + ((equal header "Expected:") + (unless (re-search-forward "^\\(\\( \\).*\n\\)*" nil t) + (error "Error parsing doctest output")) + (setq doctest-expected (doctest-replace-regexp-in-string + "^ " prompt-indent + (match-string 0)))) + ((equal header "Got nothing") + (setq doctest-got "")) + ((or (equal header "Got:") (equal header "Exception raised:")) + (unless (re-search-forward "^\\(\\( \\).*\n\\)*" nil t) + (error "Error parsing doctest output")) + (setq doctest-got (doctest-replace-regexp-in-string + "^ " prompt-indent (match-string 0)))) + ((string-match "^Differences" header) + (error (concat "doctest-replace-output can not be used " + "with diff style output"))) + (t (error "Unexpected header %s" header)))) + + ;; Go back to the source buffer. + (set-buffer orig-buffer) + + ;; Skip ahead to the output. + (beginning-of-line) + (unless (re-search-forward "^ *>>>.*") + (error "Error parsing doctest output")) + (re-search-forward "\\(\n *\\.\\.\\..*\\)*\n?") + (when (looking-at "\\'") (insert-char ?\n)) + + ;; Check that the output matches. + (let ((start (point)) end) + (cond ((re-search-forward "^ *\\(>>>.*\\|$\\)" nil t) + (setq end (match-beginning 0))) + (t + (goto-char (point-max)) + (insert-string "\n") + (setq end (point-max)))) + (when (and doctest-expected + (not (equal (buffer-substring start end) + doctest-expected))) + (warn "{%s} {%s}" (buffer-substring start end) + doctest-expected) + (error (concat "This example's output has been modified " + "since doctest was last run"))) + (setq doctest-expected (buffer-substring start end)) + (goto-char end)) + + ;; Trim exceptions + (when (and doctest-trim-exceptions + (string-match doctest-traceback-re + doctest-got)) + (let ((s1 0) (e1 (match-end 1)) + (s2 (match-beginning 2)) (e2 (match-end 2)) + (s3 (match-beginning 3)) (e3 (length doctest-got))) + (setq doctest-got + (concat (substring doctest-got s1 e1) + (substring doctest-got s2 e2) " . . .\n" + (substring doctest-got s3 e3))))) + + ;; Confirm it with the user. + (let ((confirm-buffer (get-buffer-create "*doctest-confirm*"))) + (set-buffer confirm-buffer) + ;; Erase anything left over in the buffer. + (delete-region (point-min) (point-max)) + ;; Write a confirmation message + (if (equal doctest-expected "") + (insert-string "Replace nothing\n") + (insert-string (concat "Replace:\n" doctest-expected))) + (if (equal doctest-got "") + (insert-string "With nothing\n") + (insert-string (concat "With:\n" doctest-got))) + (let ((confirm-window (display-buffer confirm-buffer))) + ;; Shrink the confirm window. + (shrink-window-if-larger-than-buffer confirm-window) + ;; Return to the original buffer. + (set-buffer orig-buffer) + ;; Match the old expected region. + (when doctest-expected + (search-backward doctest-expected)) + (when (equal doctest-expected "") (backward-char 1)) + ;; Get confirmation & do the replacement + (widen) + (cond ((y-or-n-p "Ok to replace? ") + (when (equal doctest-expected "") (forward-char 1)) + (replace-match doctest-got t t) + (message "Replaced.")) + (t + (message "Replace cancelled."))) + ;; Clean up our confirm window + (kill-buffer confirm-buffer) + (delete-window confirm-window))))))))))) + +(defun doctest-results-next-header () + "Move to the next header in the doctest results buffer, and return +the string contents of that header. If no header is found, return +nil." + (if (re-search-forward (concat doctest-results-header-re "\\|" + doctest-results-divider-re) nil t) + (let ((result (match-string 0))) + (if (string-match doctest-results-header-re result) + result + nil)) + nil)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; mmm-mode support +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; MMM Mode is a minor mode for Emacs which allows Multiple Major +;; Modes to coexist in a single buffer. + +;;;###autoload +(defun doctest-register-mmm-classes (&optional add-mode-ext-classes + fix-mmm-fontify-region-bug) + "Register doctest's mmm classes, allowing doctest to be used as a +submode region in other major modes, such as python-mode and rst-mode. +Two classes are registered: + +`doctest-docstring' + + Used to edit docstrings containing doctest examples in python- + mode. Docstring submode regions start and end with triple-quoted + strings (\"\"\"). In order to avoid confusing start-string + markers and end-string markers, all triple-quote strings in the + buffer are treated as submode regions (even if they're not + actually docstrings). Use (C-c % C-d) to insert a new doctest- + docstring region. When `doctest-execute' (C-c C-c) is called + inside a doctest-docstring region, it executes just the current + docstring. The globals for this execution are constructed by + importing the current buffer's contents in Python. + +`doctest-example' + + Used to edit doctest examples in text-editing modes, such as + `rst-mode' or `text-mode'. Docstring submode regions start with + optionally indented prompts (>>>) and end with blank lines. Use + (C-c % C-e) to insert a new doctest-example region. When + `doctest-execute' (C-c C-c) is called inside a doctest-example + region, it executes all examples in the buffer. + +If ADD-MODE-EXT-CLASSES is true, then register the new classes in +`mmm-mode-ext-classes-alist', which will cause them to be used by +default in the following modes: + + doctest-docstring: python-mode + doctest-example: rst-mode + +If FIX-MMM-FONTIFY-REGION-BUG is true, then register a hook that will +fix a bug in `mmm-fontify-region' that affects some (but not all) +versions of emacs. (See `doctest-fixed-mmm-fontify-region' for more +info.)" + (interactive) + (require 'mmm-auto) + (mmm-add-classes + '( + ;; === doctest-docstring === + (doctest-docstring :submode doctest-mode + + ;; The front is any triple-quote. Include it in the submode region, + ;; to prevent clashes between the two syntax tables over quotes. + :front "\\(\"\"\"\\|'''\\)" :include-front t + + ;; The back matches the front. Include just the first character + ;; of the quote. If we didn't include at least one quote, then + ;; the outer modes quote-counting would be thrown off. But if + ;; we include all three, we run into a bug in mmm-mode. See + ;; <http://tinyurl.com/2fa83w> for more info about the bug. + :save-matches t :back "~1" :back-offset 1 :end-not-begin t + + ;; Define a skeleton for entering new docstrings. + :insert ((?d docstring nil @ "\"\"" @ "\"" \n + _ \n "\"" @ "\"\"" @))) + + ;; === doctest-example === + (doctest-example + :submode doctest-mode + ;; The front is an optionally indented prompt. + :front "^[ \t]*>>>" :include-front t + ;; The back is a blank line. + :back "^[ \t]*$" + ;; Define a skeleton for entering new docstrings. + :insert ((?e doctest-example nil + @ @ " >>> " _ "\n\n" @ @))))) + + ;; Register some local variables that need to be saved. + (add-to-list 'mmm-save-local-variables + '(doctest-results-buffer buffer)) + (add-to-list 'mmm-save-local-variables + '(doctest-example-markers buffer)) + + ;; Register association with modes, if requested. + (when add-mode-ext-classes + (mmm-add-mode-ext-class 'python-mode nil 'doctest-docstring) + (mmm-add-mode-ext-class 'rst-mode nil 'doctest-example)) + + ;; Fix the buggy mmm-fontify-region, if requested. + (when fix-mmm-fontify-region-bug + (add-hook 'mmm-mode-hook 'doctest-fix-mmm-fontify-region-bug))) + +(defvar doctest-old-mmm-fontify-region 'nil + "Used to hold the original definition of `mmm-fontify-region' when it +is rebound by `doctest-fix-mmm-fontify-region-bug'.") + +(defun doctest-fix-mmm-fontify-region-bug () + "A function for `mmm-mode-hook' which fixes a potential bug in +`mmm-fontify-region' by using `doctest-fixed-mmm-fontify-region' +instead. (See `doctest-fixed-mmm-fontify-region' for more info.)" + (setq font-lock-fontify-region-function + 'doctest-fixed-mmm-fontify-region)) + +(defun doctest-fixed-mmm-fontify-region (start stop &optional loudly) + "A replacement for `mmm-fontify-region', which fixes a bug caused by +versions of emacs where post-command-hooks are run *before* +fontification. `mmm-mode' assumes that its post-command-hook will be +run after fontification; and if it's not, then mmm-mode can end up +with the wrong local variables, keymap, etc. after fontification. We +fix that here by redefining `mmm-fontify-region' to remember what +submode overlay it started in; and to return to that overlay after +fontification is complete. The original definition of +`mmm-fontify-region' is stored in `doctest-old-mmm-fontify-region'." + (let ((overlay mmm-current-overlay)) + (mmm-fontify-region start stop loudly) + (if (and overlay (or (< (point) (overlay-start overlay)) + (> (point) (overlay-end overlay)))) + (goto-char (overlay-start overlay))) + (mmm-update-submode-region))) + +(defun doctest-in-mmm-docstring-overlay () + (and (featurep 'mmm-auto) + (mmm-overlay-at (point)) + (save-excursion + (goto-char (overlay-start (mmm-overlay-at (point)))) + (looking-at "\"\"\"\\|\'\'\'")))) + +(defun doctest-narrow-to-mmm-overlay () + "If we're in an mmm-mode overlay, then narrow to that overlay. +This is useful, e.g., to keep from interpreting the close-quote of a +docstring as part of the example's output." + (let ((bounds (doctest-mmm-overlay-bounds))) + (when bounds (narrow-to-region (car bounds) (cdr bounds))))) + +(defun doctest-default-margin-in-mmm-docstring-overlay () + (save-excursion + (let ((pos (car (doctest-mmm-overlay-bounds)))) + (goto-char pos) + (when (doctest-looking-back "\"\"\"\\|\'\'\'") + (setq pos (- pos 3))) + (beginning-of-line) + (- pos (point))))) + +(defun doctest-mmm-overlay-bounds () + (when (featurep 'mmm-auto) + (let ((overlay (mmm-overlay-at (point)))) + (when overlay + (let ((start (overlay-start overlay)) + (end (overlay-end overlay))) + (when (doctest-in-mmm-docstring-overlay) + (save-excursion + (goto-char start) + (re-search-forward "[\"\']*") + (setq start (point)) + (goto-char end) + (while (doctest-looking-back "[\"\']") + (backward-char 1)) + (setq end (point)))) + (cons start end)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Helper functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun doctest-on-source-line-p (&optional prompt) + "Return true if the current line is a source line. The optional +argument prompt can be used to specify which type of source +line (... or >>>)." + (save-excursion + (beginning-of-line) + ;; Check if we're looking at a prompt (of the right type). + (when (and (looking-at doctest-prompt-re) + (or (null prompt) + (equal prompt (substring (match-string 2) 0 3)))) + ;; Scan backwards to make sure there's a >>> somewhere. Otherwise, + ;; this might be a '...' in the text or in an example's output. + (while (looking-at "^[ \t]*[.][.][.]") + (forward-line -1)) + (looking-at "^[ \t]*>>>")))) + +(defun doctest-on-empty-source-line-p () + "Return true if the current line contains a bare prompt." + (save-excursion + (beginning-of-line) + (and (doctest-on-source-line-p) + (looking-at (concat doctest-prompt-re "$"))))) + +(defun doctest-on-output-line-p () + "Return true if the current line is an output line." + (save-excursion + (beginning-of-line) + ;; The line must not be blank or a source line. + (when (not (or (doctest-on-source-line-p) (looking-at "[ \t]*$"))) + ;; The line must follow a source line, with no intervening blank + ;; lines. + (while (not (or (doctest-on-source-line-p) (looking-at "[ \t]*$") + (= (point) (point-min)))) + (forward-line -1)) + (doctest-on-source-line-p)))) + +(defun doctest-find-output-line (&optional limit) + "Move forward to the next doctest output line (staying within +the given bounds). Return the character position of the doctest +output line if one was found, and false otherwise." + (let ((found-it nil) ; point where we found an output line + (limit (or limit (point-max)))) ; default value for limit + (save-excursion + ;; Keep moving forward, one line at a time, until we find a + ;; doctest output line. + (while (and (not found-it) (< (point) limit) (not (eobp))) + (if (and (not (eolp)) (doctest-on-output-line-p)) + (setq found-it (point)) + (forward-line)))) + ;; If we found a doctest output line, then go to it. + (if found-it (goto-char found-it)))) + +(defun doctest-line-indentation () + "Helper for doctest-replace-output: return the whitespace indentation +at the beginning of this line." + (save-excursion + (end-of-line) + (re-search-backward "^\\( *\\)" nil t) + (match-string 1))) + +(defun doctest-optionflags (&optional diff) + "Return a string describing the optionflags that should be used +by doctest. If DIFF is non-nil, then add the REPORT_UDIFF flag." + (let ((flags "0")) + (dolist (flag doctest-optionflags) + (setq flags (concat flags "|" flag))) + (if diff (concat flags "|" "REPORT_UDIFF") flags))) + +(defun doctest-results-loc-re () + "Return the regexp that should be used to look for doctest example +location markers in doctest's output (based on which version of +doctest was used" + (cond + ((equal doctest-results-py-version 'py21) + doctest-py21-results-loc-re) + ((equal doctest-results-py-version 'py24) + doctest-py24-results-loc-re) + (t (error "bad value for doctest-results-py-version")))) + +(defun doctest-results-buffer-name () + "Return the buffer name that should be used for the doctest results +buffer. This is computed from the variable +`doctest-results-buffer-name'." + (doctest-replace-regexp-in-string + "%[nfN]" + (lambda (sym) + (cond ((equal sym "%n") (buffer-name)) + ((equal sym "%N") (doctest-replace-regexp-in-string + "[.]doctest$" "" (buffer-name) t)) + ((equal sym "%f") (buffer-file-name)))) + doctest-results-buffer-name t)) + +(defun doctest-script (input-file globs-file diff) + "..." + (doctest-replace-regexp-in-string + "%[tnflm]" + (lambda (sym) + (cond ((equal sym "%n") (buffer-name)) + ((equal sym "%f") (buffer-file-name)) + ((equal sym "%l") (doctest-optionflags diff)) + ((equal sym "%t") input-file) + ((equal sym "%m") (or globs-file "")))) + doctest-script t)) + +(defun doctest-hide-example-source () + "Delete the source code listings from the results buffer (since it's +easy enough to see them in the original buffer)" + (save-excursion + (set-buffer doctest-results-buffer) + (toggle-read-only 0) + (goto-char (point-min)) + (while (re-search-forward doctest-example-source-re nil t) + (replace-match "" nil nil)) + (toggle-read-only t))) + +(defun doctest-results-buffer-valid-p () + "Return true if this buffer has a live results buffer; and that +results buffer reports this buffer as its source buffer. (Two +buffers in doctest-mode might point to the same results buffer; +but only one of them will be equal to that results buffer's +source buffer." + (let ((cur-buf (current-buffer))) + (and (buffer-live-p doctest-results-buffer) + (save-excursion + (set-buffer doctest-results-buffer) + (equal cur-buf doctest-source-buffer))))) + +(defun doctest-update-mode-line (value) + "Update the doctest mode line with the given string value. This +is used to display information about asynchronous processes that +are run by doctest-mode." + (setq doctest-mode-line-process + value) + (force-mode-line-update t)) + +(defun doctest-version () + "Echo the current version of `doctest-mode' in the minibuffer." + (interactive) + (message "Using `doctest-mode' version %s" doctest-version)) + +(defun doctest-warn (msg &rest args) + "Display a doctest warning message." + (if (fboundp 'display-warning) + (display-warning 'doctest (apply 'format msg args)) + (apply 'message msg args))) + +(defun doctest-debug (msg &rest args) + "Display a doctest debug message." + (if (fboundp 'display-warning) + (display-warning 'doctest (apply 'format msg args) 'debug) + (apply 'message msg args))) + +(defvar doctest-serial-number 0) ;used if broken-temp-names. +(defun doctest-temp-name () + "Return a new temporary filename, for use in calling doctest." + (if (memq 'broken-temp-names features) + (let + ((sn doctest-serial-number) + (pid (and (fboundp 'emacs-pid) (emacs-pid)))) + (setq doctest-serial-number (1+ doctest-serial-number)) + (if pid + (format "doctest-%d-%d" sn pid) + (format "doctest-%d" sn))) + (make-temp-name "doctest-"))) + +(defun doctest-fontify-line (charpos) + "Run font-lock-fontify-region on the line containing the given +position." + (if (and charpos (functionp 'font-lock-fontify-region)) + (save-excursion + (goto-char charpos) + (let ((beg (progn (beginning-of-line) (point))) + (end (progn (end-of-line) (point)))) + (font-lock-fontify-region beg end))))) + +(defun doctest-do-auto-fill () + "If the current line is a soucre line or an output line, do nothing. +Otherwise, call (do-auto-fill)." + (cond + ;; Don't wrap source lines. + ((doctest-on-source-line-p) nil) + ;; Don't wrap output lines + ((doctest-on-output-line-p) nil) + ;; Wrap all other lines + (t (do-auto-fill)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Emacs Compatibility Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Define compatible versions of functions that are defined +;; for some versions of emacs but not others. + +;; Backwards compatibility: looking-back +(cond ((fboundp 'looking-back) ;; Emacs 22.x + (defalias 'doctest-looking-back 'looking-back)) + (t + (defun doctest-looking-back (regexp) + "Return true if text before point matches REGEXP." + (save-excursion + (let ((orig-pos (point))) + ;; Search backwards for the regexp. + (if (re-search-backward regexp nil t) + ;; Check if it ends at the original point. + (= orig-pos (match-end 0)))))))) + +;; Backwards compatibility: replace-regexp-in-string +(cond ((fboundp 'replace-regexp-in-string) + (defalias 'doctest-replace-regexp-in-string 'replace-regexp-in-string)) + (t ;; XEmacs 21.x or Emacs 20.x + (defun doctest-replace-regexp-in-string + (regexp rep string &optional fixedcase literal) + "Replace all matches for REGEXP with REP in STRING." + (let ((start 0)) + (while (and (< start (length string)) + (string-match regexp string start)) + (setq start (+ (match-end 0) 1)) + (let ((newtext (if (functionp rep) + (save-match-data + (funcall rep (match-string 0 string))) + rep))) + (setq string (replace-match newtext fixedcase + literal string))))) + string))) + +;; Backwards compatibility: line-number +(cond ((fboundp 'line-number) ;; XEmacs + (defalias 'doctest-line-number 'line-number)) + ((fboundp 'line-number-at-pos) ;; Emacs 22.x + (defalias 'doctest-line-number 'line-number-at-pos)) + (t ;; Emacs 21.x + (defun doctest-line-number (&optional pos) + "Return the line number of POS (default=point)." + (1+ (count-lines 1 + (save-excursion (progn (beginning-of-line) + (or pos (point))))))))) + +;; Backwards compatibility: process-live-p +(cond ((fboundp 'process-live-p) ;; XEmacs + (defalias 'doctest-process-live-p 'process-live-p)) + (t ;; Emacs + (defun doctest-process-live-p (process) + (and (processp process) + (equal (process-status process) 'run))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Doctest Results Mode (output of doctest-execute-buffer) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Register the font-lock keywords (xemacs) +(put 'doctest-results-mode 'font-lock-defaults + '(doctest-results-font-lock-keywords)) + +;; Register the font-lock keywords (older versions of gnu emacs) +(when (boundp 'font-lock-defaults-alist) + (add-to-list 'font-lock-defaults-alist + '(doctest-results-mode doctest-results-font-lock-keywords + nil nil nil nil))) + +(defvar doctest-selected-failure nil + "The location of the currently selected failure. +This variable is uffer-local to doctest-results-mode buffers.") + +(defvar doctest-source-buffer nil + "The buffer that spawned this one. +This variable is uffer-local to doctest-results-mode buffers.") + +(defvar doctest-results-py-version nil + "A symbol indicating which version of Python was used to generate +the results in a doctest-results-mode buffer. Can be either the +symbol `py21' or the symbol `py24'. +This variable is uffer-local to doctest-results-mode buffers.") + +;; Keymap for doctest-results-mode. +(defconst doctest-results-mode-map + (let ((map (make-keymap))) + (define-key map [return] 'doctest-select-failure) + map) + "Keymap for doctest-results-mode.") + +;; Syntax table for doctest-results-mode. +(defvar doctest-results-mode-syntax-table nil + "Syntax table used in `doctest-results-mode' buffers.") +(when (not doctest-results-mode-syntax-table) + (setq doctest-results-mode-syntax-table (make-syntax-table)) + (dolist (entry '(("(" . "()") ("[" . "(]") ("{" . "(}") + (")" . ")(") ("]" . ")[") ("}" . "){") + ("$%&*+-/<=>|'\"`" . ".") ("_" . "w"))) + (dolist (char (string-to-list (car entry))) + (modify-syntax-entry char (cdr entry) + doctest-results-mode-syntax-table)))) + +;; Define the mode +(defun doctest-results-mode () + "A major mode used to display the results of running doctest. +See `doctest-mode'. + +\\{doctest-results-mode-map}" + (interactive) + + ;; Declare local variables. + (kill-all-local-variables) + (make-local-variable 'font-lock-defaults) + (make-local-variable 'doctest-selected-failure) + (make-local-variable 'doctest-source-buffer) + (make-local-variable 'doctest-results-py-version) + + ;; Define local variables. + (setq major-mode 'doctest-results-mode + mode-name "Doctest-Results" + mode-line-process 'doctest-mode-line-process + font-lock-defaults '(doctest-results-font-lock-keywords + nil nil nil nil)) + ;; Define keymap. + (use-local-map doctest-results-mode-map) + + ;; Define the syntax table. + (set-syntax-table doctest-results-mode-syntax-table) + + ;; Enable font-lock mode. + (if (featurep 'font-lock) (font-lock-mode 1))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Doctest Mode +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Register the font-lock keywords (xemacs) +(put 'doctest-mode 'font-lock-defaults '(doctest-font-lock-keywords + nil nil nil nil)) + +;; Register the font-lock keywords (older versions of gnu emacs) +(when (boundp 'font-lock-defaults-alist) + (add-to-list 'font-lock-defaults-alist + '(doctest-mode doctest-font-lock-keywords + nil nil nil nil))) + +(defvar doctest-results-buffer nil + "The output buffer for doctest-mode. +This variable is buffer-local to doctest-mode buffers.") + +(defvar doctest-example-markers nil + "A list mapping markers to the line numbers at which they appeared +in the buffer at the time doctest was last run. This is used to find +'original' line numbers, which can be used to search the doctest +output buffer. It's encoded as a list of (MARKER . POS) tuples, in +reverse POS order. +This variable is buffer-local to doctest-mode buffers.") + +;; These are global, since we only one run process at a time: +(defvar doctest-async-process nil + "The process object created by the asynchronous doctest process") +(defvar doctest-async-process-tempfiles nil + "A list of tempfile names created by the asynchronous doctest process") +(defvar doctest-async-process-buffer nil + "The source buffer for the asynchronous doctest process") +(defvar doctest-mode-line-process "" + "A string displayed on the modeline, to indicate when doctest is +running asynchronously.") + +;; Keymap for doctest-mode. n.b.: we intentionally define [tab] +;; rather than overriding indent-line-function, since we don't want +;; doctest-indent-source-line to be called by do-auto-fill. +(defconst doctest-mode-map + (let ((map (make-keymap))) + (define-key map [backspace] 'doctest-electric-backspace) + (define-key map [return] 'doctest-newline-and-indent) + (define-key map [tab] 'doctest-indent-source-line) + (define-key map ":" 'doctest-electric-colon) + (define-key map "\C-c\C-v" 'doctest-version) + (define-key map "\C-c\C-c" 'doctest-execute) + (define-key map "\C-c\C-d" 'doctest-execute-with-diff) + (define-key map "\C-c\C-n" 'doctest-next-failure) + (define-key map "\C-c\C-p" 'doctest-prev-failure) + (define-key map "\C-c\C-a" 'doctest-first-failure) + (define-key map "\C-c\C-e" 'doctest-last-failure) + (define-key map "\C-c\C-z" 'doctest-last-failure) + (define-key map "\C-c\C-r" 'doctest-replace-output) + (define-key map "\C-c|" 'doctest-execute-region) + map) + "Keymap for doctest-mode.") + +;; Syntax table for doctest-mode. +(defvar doctest-mode-syntax-table nil + "Syntax table used in `doctest-mode' buffers.") +(when (not doctest-mode-syntax-table) + (setq doctest-mode-syntax-table (make-syntax-table)) + (dolist (entry '(("(" . "()") ("[" . "(]") ("{" . "(}") + (")" . ")(") ("]" . ")[") ("}" . "){") + ("$%&*+-/<=>|'\"`" . ".") ("_" . "w"))) + (dolist (char (string-to-list (car entry))) + (modify-syntax-entry char (cdr entry) doctest-mode-syntax-table)))) + +;; Use doctest mode for files ending in .doctest +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.doctest$" . doctest-mode)) + +;;;###autoload +(defun doctest-mode () + "A major mode for editing text files that contain Python +doctest examples. Doctest is a testing framework for Python that +emulates an interactive session, and checks the result of each +command. For more information, see the Python library reference: +<http://docs.python.org/lib/module-doctest.html> + +`doctest-mode' defines three kinds of line, each of which is +treated differently: + + - 'Source lines' are lines consisting of a Python prompt + ('>>>' or '...'), followed by source code. Source lines are + colored (similarly to `python-mode') and auto-indented. + + - 'Output lines' are non-blank lines immediately following + source lines. They are colored using several doctest- + specific output faces. + + - 'Text lines' are any other lines. They are not processed in + any special way. + +\\{doctest-mode-map}" + (interactive) + + ;; Declare local variables. + (kill-all-local-variables) + (make-local-variable 'font-lock-defaults) + (make-local-variable 'doctest-results-buffer) + (make-local-variable 'doctest-example-markers) + + ;; Define local variables. + (setq major-mode 'doctest-mode + mode-name "Doctest" + mode-line-process 'doctest-mode-line-process + font-lock-defaults '(doctest-font-lock-keywords + nil nil nil nil)) + + ;; Define keymap. + (use-local-map doctest-mode-map) + + ;; Define the syntax table. + (set-syntax-table doctest-mode-syntax-table) + + ;; Enable auto-fill mode. + (auto-fill-mode 1) + (setq auto-fill-function 'doctest-do-auto-fill) + + ;; Enable font-lock mode. + (if (featurep 'font-lock) (font-lock-mode 1)) + + ;; Run the mode hook. + (run-hooks 'doctest-mode-hook)) + +(provide 'doctest-mode) +;;; doctest-mode.el ends here
new file mode 100644 --- /dev/null +++ b/.elisp/pymacs.el @@ -0,0 +1,688 @@ +;;; Interface between Emacs Lisp and Python - Lisp part. -*- emacs-lisp -*- +;;; Copyright © 2001, 2002, 2003 Progiciels Bourbeau-Pinard inc. +;;; François Pinard <pinard@iro.umontreal.ca>, 2001. + +;;; This program is free software; you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 2, or (at your option) +;;; any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software Foundation, +;;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +;;; Portability stunts. + +(defvar pymacs-use-hash-tables + (and (fboundp 'make-hash-table) (fboundp 'gethash) (fboundp 'puthash)) + "Set to t if hash tables are available.") + +(eval-and-compile + + (if (fboundp 'multibyte-string-p) + (defalias 'pymacs-multibyte-string-p 'multibyte-string-p) + (defun pymacs-multibyte-string-p (string) + "Tell XEmacs if STRING should be handled as multibyte." + (not (equal (find-charset-string string) '(ascii)))))) + +(defalias 'pymacs-report-error (symbol-function 'error)) + +;;; Published variables and functions. + +(defvar pymacs-load-path nil + "List of additional directories to search for Python modules. +The directories listed will be searched first, in the order given.") + +(defvar pymacs-trace-transit '(5000 . 30000) + "Keep the communication buffer growing, for debugging. +When this variable is nil, the `*Pymacs*' communication buffer gets erased +before each communication round-trip. Setting it to `t' guarantees that +the full communication is saved, which is useful for debugging. +It could also be given as (KEEP . LIMIT): whenever the buffer exceeds LIMIT +bytes, it is reduced to approximately KEEP bytes.") + +(defvar pymacs-forget-mutability nil + "Transmit copies to Python instead of Lisp handles, as much as possible. +When this variable is nil, most mutable objects are transmitted as handles. +This variable is meant to be temporarily rebound to force copies.") + +(defvar pymacs-mutable-strings nil + "Prefer transmitting Lisp strings to Python as handles. +When this variable is nil, strings are transmitted as copies, and the +Python side thus has no way for modifying the original Lisp strings. +This variable is ignored whenever `forget-mutability' is set.") + +(defvar pymacs-timeout-at-start 30 + "Maximum reasonable time, in seconds, for starting the Pymacs helper. +A machine should be pretty loaded before one needs to increment this.") + +(defvar pymacs-timeout-at-reply 5 + "Expected maximum time, in seconds, to get the first line of a reply. +The status of the Pymacs helper is checked at every such timeout.") + +(defvar pymacs-timeout-at-line 2 + "Expected maximum time, in seconds, to get another line of a reply. +The status of the Pymacs helper is checked at every such timeout.") + +(defvar pymacs-dreadful-zombies nil + "If zombies should trigger hard errors, whenever they get called. +If `nil', calling a zombie will merely produce a diagnostic message.") + +(defun pymacs-load (module &optional prefix noerror) + "Import the Python module named MODULE into Emacs. +Each function in the Python module is made available as an Emacs function. +The Lisp name of each function is the concatenation of PREFIX with +the Python name, in which underlines are replaced by dashes. If PREFIX is +not given, it defaults to MODULE followed by a dash. +If NOERROR is not nil, do not raise error when the module is not found." + (interactive + (let* ((module (read-string "Python module? ")) + (default (concat (car (last (split-string module "\\."))) "-")) + (prefix (read-string (format "Prefix? [%s] " default) + nil nil default))) + (list module prefix))) + (message "Pymacs loading %s..." module) + (let ((lisp-code (pymacs-call "pymacs_load_helper" module prefix))) + (cond (lisp-code (let ((result (eval lisp-code))) + (message "Pymacs loading %s...done" module) + result)) + (noerror (message "Pymacs loading %s...failed" module) nil) + (t (pymacs-report-error "Pymacs loading %s...failed" module))))) + +(defun pymacs-eval (text) + "Compile TEXT as a Python expression, and return its value." + (interactive "sPython expression? ") + (let ((value (pymacs-serve-until-reply "eval" `(princ ,text)))) + (when (interactive-p) + (message "%S" value)) + value)) + +(defun pymacs-exec (text) + "Compile and execute TEXT as a sequence of Python statements. +This functionality is experimental, and does not appear to be useful." + (interactive "sPython statements? ") + (let ((value (pymacs-serve-until-reply "exec" `(princ ,text)))) + (when (interactive-p) + (message "%S" value)) + value)) + +(defun pymacs-call (function &rest arguments) + "Return the result of calling a Python function FUNCTION over ARGUMENTS. +FUNCTION is a string denoting the Python function, ARGUMENTS are separate +Lisp expressions, one per argument. Immutable Lisp constants are converted +to Python equivalents, other structures are converted into Lisp handles." + (pymacs-serve-until-reply + "eval" `(pymacs-print-for-apply ',function ',arguments))) + +(defun pymacs-apply (function arguments) + "Return the result of calling a Python function FUNCTION over ARGUMENTS. +FUNCTION is a string denoting the Python function, ARGUMENTS is a list of +Lisp expressions. Immutable Lisp constants are converted to Python +equivalents, other structures are converted into Lisp handles." + (pymacs-serve-until-reply + "eval" `(pymacs-print-for-apply ',function ',arguments))) + +;;; Integration details. + +;; Python functions and modules should ideally look like Lisp functions and +;; modules. This page tries to increase the integration seamlessness. + +(defadvice documentation (around pymacs-ad-documentation activate) + ;; Integration of doc-strings. + (let* ((reference (pymacs-python-reference function)) + (python-doc (when reference + (pymacs-eval (format "doc_string(%s)" reference))))) + (if (or reference python-doc) + (setq ad-return-value + (concat + "It interfaces to a Python function.\n\n" + (when python-doc + (if raw python-doc (substitute-command-keys python-doc))))) + ad-do-it))) + +(defun pymacs-python-reference (object) + ;; Return the text reference of a Python object if possible, else nil. + (when (functionp object) + (let* ((definition (indirect-function object)) + (body (and (pymacs-proper-list-p definition) + (> (length definition) 2) + (eq (car definition) 'lambda) + (cddr definition)))) + (when (and body (listp (car body)) (eq (caar body) 'interactive)) + ;; Skip the interactive specification of a function. + (setq body (cdr body))) + (when (and body + ;; Advised functions start with a string. + (not (stringp (car body))) + ;; Python trampolines hold exactly one expression. + (= (length body) 1)) + (let ((expression (car body))) + ;; EXPRESSION might now hold something like: + ;; (pymacs-apply (quote (pymacs-python . N)) ARGUMENT-LIST) + (when (and (pymacs-proper-list-p expression) + (= (length expression) 3) + (eq (car expression) 'pymacs-apply) + (eq (car (cadr expression)) 'quote)) + (setq object (cadr (cadr expression)))))))) + (when (eq (car-safe object) 'pymacs-python) + (format "python[%d]" (cdr object)))) + +;; The following functions are experimental -- they are not satisfactory yet. + +(defun pymacs-file-handler (operation &rest arguments) + ;; Integration of load-file, autoload, etc. + ;; Emacs might want the contents of some `MODULE.el' which does not exist, + ;; while there is a `MODULE.py' or `MODULE.pyc' file in the same directory. + ;; The goal is to generate a virtual contents for this `MODULE.el' file, as + ;; a set of Lisp trampoline functions to the Python module functions. + ;; Python modules can then be loaded or autoloaded as if they were Lisp. + (cond ((and (eq operation 'file-readable-p) + (let ((module (substring (car arguments) 0 -3))) + (or (pymacs-file-force operation arguments) + (file-readable-p (concat module ".py")) + (file-readable-p (concat module ".pyc")))))) + ((and (eq operation 'load) + (not (pymacs-file-force + 'file-readable-p (list (car arguments)))) + (file-readable-p (car arguments))) + (let ((lisp-code (pymacs-call "pymacs_load_helper" + (substring (car arguments) 0 -3) + nil))) + (unless lisp-code + (pymacs-report-error "Python import error")) + (eval lisp-code))) + ((and (eq operation 'insert-file-contents) + (not (pymacs-file-force + 'file-readable-p (list (car arguments)))) + (file-readable-p (car arguments))) + (let ((lisp-code (pymacs-call "pymacs_load_helper" + (substring (car arguments) 0 -3) + nil))) + (unless lisp-code + (pymacs-report-error "Python import error")) + (insert (prin1-to-string lisp-code)))) + (t (pymacs-file-force operation arguments)))) + +(defun pymacs-file-force (operation arguments) + ;; Bypass the file handler. + (let ((inhibit-file-name-handlers + (cons 'pymacs-file-handler + (and (eq inhibit-file-name-operation operation) + inhibit-file-name-handlers))) + (inhibit-file-name-operation operation)) + (apply operation arguments))) + +;(add-to-list 'file-name-handler-alist '("\\.el\\'" . pymacs-file-handler)) + +;;; Gargabe collection of Python IDs. + +;; Python objects which have no Lisp representation are allocated on the +;; Python side as `python[INDEX]', and INDEX is transmitted to Emacs, with +;; the value to use on the Lisp side for it. Whenever Lisp does not need a +;; Python object anymore, it should be freed on the Python side. The +;; following variables and functions are meant to fill this duty. + +(defvar pymacs-used-ids nil + "List of received IDs, currently allocated on the Python side.") + +(defvar pymacs-weak-hash nil + "Weak hash table, meant to find out which IDs are still needed.") + +(defvar pymacs-gc-wanted nil + "Flag if it is time to clean up unused IDs on the Python side.") + +(defvar pymacs-gc-running nil + "Flag telling that a Pymacs garbage collection is in progress.") + +(defvar pymacs-gc-timer nil + "Timer to trigger Pymacs garbage collection at regular time intervals. +The timer is used only if `post-gc-hook' is not available.") + +(defun pymacs-schedule-gc (&optional xemacs-list) + (unless pymacs-gc-running + (setq pymacs-gc-wanted t))) + +(defun pymacs-garbage-collect () + ;; Clean up unused IDs on the Python side. + (when pymacs-use-hash-tables + (let ((pymacs-gc-running t) + (pymacs-forget-mutability t) + (ids pymacs-used-ids) + used-ids unused-ids) + (while ids + (let ((id (car ids))) + (setq ids (cdr ids)) + (if (gethash id pymacs-weak-hash) + (setq used-ids (cons id used-ids)) + (setq unused-ids (cons id unused-ids))))) + (setq pymacs-used-ids used-ids + pymacs-gc-wanted nil) + (when unused-ids + (pymacs-apply "free_python" unused-ids))))) + +(defun pymacs-defuns (arguments) + ;; Take one argument, a list holding a number of items divisible by 3. The + ;; first argument is an INDEX, the second is a NAME, the third is the + ;; INTERACTION specification, and so forth. Register Python INDEX with a + ;; function with that NAME and INTERACTION on the Lisp side. The strange + ;; calling convention is to minimise quoting at call time. + (while (>= (length arguments) 3) + (let ((index (nth 0 arguments)) + (name (nth 1 arguments)) + (interaction (nth 2 arguments))) + (fset name (pymacs-defun index interaction)) + (setq arguments (nthcdr 3 arguments))))) + +(defun pymacs-defun (index interaction) + ;; Register INDEX on the Lisp side with a Python object that is a function, + ;; and return a lambda form calling that function. If the INTERACTION + ;; specification is nil, the function is not interactive. Otherwise, the + ;; function is interactive, INTERACTION is then either a string, or the + ;; index of an argument-less Python function returning the argument list. + (let ((object (pymacs-python index))) + (cond ((null interaction) + `(lambda (&rest arguments) + (pymacs-apply ',object arguments))) + ((stringp interaction) + `(lambda (&rest arguments) + (interactive ,interaction) + (pymacs-apply ',object arguments))) + (t `(lambda (&rest arguments) + (interactive (pymacs-call ',(pymacs-python interaction))) + (pymacs-apply ',object arguments)))))) + +(defun pymacs-python (index) + ;; Register on the Lisp side a Python object having INDEX, and return it. + ;; The result is meant to be recognised specially by `print-for-eval', and + ;; in the function position by `print-for-apply'. + (let ((object (cons 'pymacs-python index))) + (when pymacs-use-hash-tables + (puthash index object pymacs-weak-hash) + (setq pymacs-used-ids (cons index pymacs-used-ids))) + object)) + +;;; Generating Python code. + +;; Many Lisp expressions cannot fully be represented in Python, at least +;; because the object is mutable on the Lisp side. Such objects are allocated +;; somewhere into a vector of handles, and the handle index is used for +;; communication instead of the expression itself. + +(defvar pymacs-lisp nil + "Vector of handles to hold transmitted expressions.") + +(defvar pymacs-freed-list nil + "List of unallocated indices in Lisp.") + +;; When the Python GC is done with a Lisp object, a communication occurs so to +;; free the object on the Lisp side as well. + +(defun pymacs-allocate-lisp (expression) + ;; This function allocates some handle for an EXPRESSION, and return its + ;; index. + (unless pymacs-freed-list + (let* ((previous pymacs-lisp) + (old-size (length previous)) + (new-size (if (zerop old-size) 100 (+ old-size (/ old-size 2)))) + (counter new-size)) + (setq pymacs-lisp (make-vector new-size nil)) + (while (> counter 0) + (setq counter (1- counter)) + (if (< counter old-size) + (aset pymacs-lisp counter (aref previous counter)) + (setq pymacs-freed-list (cons counter pymacs-freed-list)))))) + (let ((index (car pymacs-freed-list))) + (setq pymacs-freed-list (cdr pymacs-freed-list)) + (aset pymacs-lisp index expression) + index)) + +(defun pymacs-free-lisp (indices) + ;; This function is triggered from Python side for Lisp handles which lost + ;; their last reference. These references should be cut on the Lisp side as + ;; well, or else, the objects will never be garbage-collected. + (while indices + (let ((index (car indices))) + (aset pymacs-lisp index nil) + (setq pymacs-freed-list (cons index pymacs-freed-list) + indices (cdr indices))))) + +(defun pymacs-print-for-apply (function arguments) + ;; This function prints a Python expression calling FUNCTION, which is a + ;; string naming a Python function, or a Python reference, over all its + ;; ARGUMENTS, which are Lisp expressions. + (let ((separator "") + argument) + (if (eq (car-safe function) 'pymacs-python) + (princ (format "python[%d]" (cdr function))) + (princ function)) + (princ "(") + (while arguments + (setq argument (car arguments) + arguments (cdr arguments)) + (princ separator) + (setq separator ", ") + (pymacs-print-for-eval argument)) + (princ ")"))) + +(defun pymacs-print-for-eval (expression) + ;; This function prints a Python expression out of a Lisp EXPRESSION. + (let (done) + (cond ((not expression) + (princ "None") + (setq done t)) + ((eq expression t) + (princ "True") + (setq done t)) + ((numberp expression) + (princ expression) + (setq done t)) + ((stringp expression) + (when (or pymacs-forget-mutability + (not pymacs-mutable-strings)) + (let* ((multibyte (pymacs-multibyte-string-p expression)) + (text (if multibyte + (encode-coding-string expression 'utf-8) + (copy-sequence expression)))) + (set-text-properties 0 (length text) nil text) + (princ (mapconcat 'identity + (split-string (prin1-to-string text) "\n") + "\\n")) + (when (and multibyte + (not (equal (find-charset-string text) '(ascii)))) + (princ ".decode('UTF-8')"))) + (setq done t))) + ((symbolp expression) + (let ((name (symbol-name expression))) + ;; The symbol can only be transmitted when in the main oblist. + (when (eq expression (intern-soft name)) + (princ "lisp[") + (prin1 name) + (princ "]") + (setq done t)))) + ((vectorp expression) + (when pymacs-forget-mutability + (let ((limit (length expression)) + (counter 0)) + (princ "(") + (while (< counter limit) + (unless (zerop counter) + (princ ", ")) + (pymacs-print-for-eval (aref expression counter)) + (setq counter (1+ counter))) + (when (= limit 1) + (princ ",")) + (princ ")") + (setq done t)))) + ((eq (car-safe expression) 'pymacs-python) + (princ "python[") + (princ (cdr expression)) + (princ "]") + (setq done t)) + ((pymacs-proper-list-p expression) + (when pymacs-forget-mutability + (princ "[") + (pymacs-print-for-eval (car expression)) + (while (setq expression (cdr expression)) + (princ ", ") + (pymacs-print-for-eval (car expression))) + (princ "]") + (setq done t)))) + (unless done + (let ((class (cond ((vectorp expression) "Vector") + ((and pymacs-use-hash-tables + (hash-table-p expression)) + "Table") + ((bufferp expression) "Buffer") + ((pymacs-proper-list-p expression) "List") + (t "Lisp")))) + (princ class) + (princ "(") + (princ (pymacs-allocate-lisp expression)) + (princ ")"))))) + +;;; Communication protocol. + +(defvar pymacs-transit-buffer nil + "Communication buffer between Emacs and Python.") + +;; The principle behind the communication protocol is that it is easier to +;; generate than parse, and that each language already has its own parser. +;; So, the Emacs side generates Python text for the Python side to interpret, +;; while the Python side generates Lisp text for the Lisp side to interpret. +;; About nothing but expressions are transmitted, which are evaluated on +;; arrival. The pseudo `reply' function is meant to signal the final result +;; of a series of exchanges following a request, while the pseudo `error' +;; function is meant to explain why an exchange could not have been completed. + +;; The protocol itself is rather simple, and contains human readable text +;; only. A message starts at the beginning of a line in the communication +;; buffer, either with `>' for the Lisp to Python direction, or `<' for the +;; Python to Lisp direction. This is followed by a decimal number giving the +;; length of the message text, a TAB character, and the message text itself. +;; Message direction alternates systematically between messages, it never +;; occurs that two successive messages are sent in the same direction. The +;; first message is received from the Python side, it is `(version VERSION)'. + +(defun pymacs-start-services () + ;; This function gets called automatically, as needed. + (let ((buffer (get-buffer-create "*Pymacs*"))) + (with-current-buffer buffer + (buffer-disable-undo) + (set-buffer-multibyte nil) + (set-buffer-file-coding-system 'raw-text) + (save-match-data + ;; Launch the Pymacs helper. + (let ((process + (apply 'start-process "pymacs" buffer + (let ((python (getenv "PYMACS_PYTHON"))) + (if (or (null python) (equal python "")) + "python" + python)) + "-c" (concat "import sys;" + " from Pymacs.pymacs import main;" + " main(*sys.argv[1:])") + (mapcar 'expand-file-name pymacs-load-path)))) + (cond ((fboundp 'set-process-query-on-exit-flag) + (set-process-query-on-exit-flag process nil)) + ((fboundp 'process-kill-without-query-process) + (process-kill-without-query process))) + ;; Receive the synchronising reply. + (while (progn + (goto-char (point-min)) + (not (re-search-forward "<\\([0-9]+\\)\t" nil t))) + (unless (accept-process-output process pymacs-timeout-at-start) + (pymacs-report-error + "Pymacs helper did not start within %d seconds" + pymacs-timeout-at-start))) + (let ((marker (process-mark process)) + (limit-position (+ (match-end 0) + (string-to-number (match-string 1))))) + (while (< (marker-position marker) limit-position) + (unless (accept-process-output process pymacs-timeout-at-start) + (pymacs-report-error + "Pymacs helper probably was interrupted at start"))))) + ;; Check that synchronisation occurred. + (goto-char (match-end 0)) + (let ((reply (read (current-buffer)))) + (if (and (pymacs-proper-list-p reply) + (= (length reply) 2) + (eq (car reply) 'version)) + (unless (string-equal (cadr reply) "0.23") + (pymacs-report-error + "Pymacs Lisp version is 0.23, Python is %s" + (cadr reply))) + (pymacs-report-error "Pymacs got an invalid initial reply"))))) + (when pymacs-use-hash-tables + (if pymacs-weak-hash + ;; A previous Pymacs session occurred in *this* Emacs session. Some + ;; IDs may hang around, which do not correspond to anything on the + ;; Python side. Python should not recycle such IDs for new objects. + (when pymacs-used-ids + (let ((pymacs-transit-buffer buffer) + (pymacs-forget-mutability t)) + (pymacs-apply "zombie_python" pymacs-used-ids))) + (setq pymacs-weak-hash (make-hash-table :weakness 'value))) + (if (boundp 'post-gc-hook) + (add-hook 'post-gc-hook 'pymacs-schedule-gc) + (setq pymacs-gc-timer (run-at-time 20 20 'pymacs-schedule-gc)))) + ;; If nothing failed, only then declare that Pymacs has started! + (setq pymacs-transit-buffer buffer))) + +(defun pymacs-terminate-services () + ;; This function is mainly provided for documentation purposes. + (interactive) + (garbage-collect) + (pymacs-garbage-collect) + (when (or (not pymacs-used-ids) + (yes-or-no-p "\ +Killing the Pymacs helper might create zombie objects. Kill? ")) + (cond ((boundp 'post-gc-hook) + (remove-hook 'post-gc-hook 'pymacs-schedule-gc)) + ((timerp pymacs-gc-timer) + (cancel-timer pymacs-gc-timer))) + (when pymacs-transit-buffer + (kill-buffer pymacs-transit-buffer)) + (setq pymacs-gc-running nil + pymacs-gc-timer nil + pymacs-transit-buffer nil + pymacs-lisp nil + pymacs-freed-list nil))) + +(defun pymacs-serve-until-reply (action inserter) + ;; This function builds a Python request by printing ACTION and + ;; evaluating INSERTER, which itself prints an argument. It then + ;; sends the request to the Pymacs helper, and serves all + ;; sub-requests coming from the Python side, until either a reply or + ;; an error is finally received. + (unless (and pymacs-transit-buffer + (buffer-name pymacs-transit-buffer) + (get-buffer-process pymacs-transit-buffer)) + (pymacs-start-services)) + (when pymacs-gc-wanted + (pymacs-garbage-collect)) + (let ((inhibit-quit t) + done value) + (while (not done) + (let ((form (pymacs-round-trip action inserter))) + (setq action (car form)) + (when (eq action 'free) + (pymacs-free-lisp (cadr form)) + (setq form (cddr form) + action (car form))) + (let* ((pair (pymacs-interruptible-eval (cadr form))) + (success (cdr pair))) + (setq value (car pair)) + (cond ((eq action 'eval) + (if success + (setq action "return" + inserter `(pymacs-print-for-eval ',value)) + (setq action "raise" + inserter `(let ((pymacs-forget-mutability t)) + (pymacs-print-for-eval ,value))))) + ((eq action 'expand) + (if success + (setq action "return" + inserter `(let ((pymacs-forget-mutability t)) + (pymacs-print-for-eval ,value))) + (setq action "raise" + inserter `(let ((pymacs-forget-mutability t)) + (pymacs-print-for-eval ,value))))) + ((eq action 'return) + (if success + (setq done t) + (pymacs-report-error "%s" value))) + ((eq action 'raise) + (if success + (pymacs-report-error "Python: %s" value) + (pymacs-report-error "%s" value))) + (t (pymacs-report-error "Protocol error: %s" form)))))) + value)) + +(defun pymacs-round-trip (action inserter) + ;; This function produces a Python request by printing and + ;; evaluating INSERTER, which itself prints an argument. It sends + ;; the request to the Pymacs helper, awaits for any kind of reply, + ;; and returns it. + (with-current-buffer pymacs-transit-buffer + ;; Possibly trim the beginning of the transit buffer. + (cond ((not pymacs-trace-transit) + (erase-buffer)) + ((consp pymacs-trace-transit) + (when (> (buffer-size) (cdr pymacs-trace-transit)) + (let ((cut (- (buffer-size) (car pymacs-trace-transit)))) + (when (> cut 0) + (save-excursion + (goto-char cut) + (unless (memq (preceding-char) '(0 ?\n)) + (forward-line 1)) + (delete-region (point-min) (point)))))))) + ;; Send the request, wait for a reply, and process it. + (let* ((process (get-buffer-process pymacs-transit-buffer)) + (status (process-status process)) + (marker (process-mark process)) + (moving (= (point) marker)) + send-position reply-position reply) + (save-excursion + (save-match-data + ;; Encode request. + (setq send-position (marker-position marker)) + (let ((standard-output marker)) + (princ action) + (princ " ") + (eval inserter)) + (goto-char marker) + (unless (= (preceding-char) ?\n) + (princ "\n" marker)) + ;; Send request text. + (goto-char send-position) + (insert (format ">%d\t" (- marker send-position))) + (setq reply-position (marker-position marker)) + (process-send-region process send-position marker) + ;; Receive reply text. + (while (and (eq status 'run) + (progn + (goto-char reply-position) + (not (re-search-forward "<\\([0-9]+\\)\t" nil t)))) + (unless (accept-process-output process pymacs-timeout-at-reply) + (setq status (process-status process)))) + (when (eq status 'run) + (let ((limit-position (+ (match-end 0) + (string-to-number (match-string 1))))) + (while (and (eq status 'run) + (< (marker-position marker) limit-position)) + (unless (accept-process-output process pymacs-timeout-at-line) + (setq status (process-status process)))))) + ;; Decode reply. + (if (not (eq status 'run)) + (pymacs-report-error "Pymacs helper status is `%S'" status) + (goto-char (match-end 0)) + (setq reply (read (current-buffer)))))) + (when (and moving (not pymacs-trace-transit)) + (goto-char marker)) + reply))) + +(defun pymacs-interruptible-eval (expression) + ;; This function produces a pair (VALUE . SUCCESS) for EXPRESSION. + ;; A cautious evaluation of EXPRESSION is attempted, and any + ;; error while evaluating is caught, including Emacs quit (C-g). + ;; Any Emacs quit also gets forward as a SIGINT to the Pymacs handler. + ;; With SUCCESS being true, VALUE is the expression value. + ;; With SUCCESS being false, VALUE is an interruption diagnostic. + (condition-case info + (cons (let ((inhibit-quit nil)) (eval expression)) t) + (quit (setq quit-flag t) + (interrupt-process pymacs-transit-buffer) + (cons "*Interrupted!*" nil)) + (error (cons (prin1-to-string info) nil)))) + +(defun pymacs-proper-list-p (expression) + ;; Tell if a list is proper, id est, that it is `nil' or ends with `nil'. + (cond ((not expression)) + ((consp expression) (not (cdr (last expression)))))) + +(provide 'pymacs)
new file mode 100644 --- /dev/null +++ b/.elisp/show-wspace.el @@ -0,0 +1,228 @@ +;;; show-wspace.el --- Highlight whitespace of various kinds. +;; +;; Filename: show-wspace.el +;; Description: Highlight whitespace of various kinds. +;; Author: Peter Steiner <unistein@isbe.ch>, Drew Adams +;; Maintainer: Drew Adams +;; Copyright (C) 2000-2008, Drew Adams, all rights reserved. +;; Created: Wed Jun 21 08:54:53 2000 +;; Version: 21.0 +;; Last-Updated: Tue Jan 01 13:59:36 2008 (-28800 Pacific Standard Time) +;; By: dradams +;; Update #: 264 +;; URL: http://www.emacswiki.org/cgi-bin/wiki/show-wspace.el +;; Keywords: highlight, whitespace +;; Compatibility: GNU Emacs 20.x, GNU Emacs 21.x, GNU Emacs 22.x +;; +;; Features that might be required by this library: +;; +;; None +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Commentary: +;; +;; Highlight whitespace of various kinds. +;; +;; To use this library: +;; +;; Add this to your initialization file (~/.emacs or ~/_emacs): +;; +;; (require 'show-wspace) ; Load this library. +;; +;; Then you can use commands `toggle-*' (see below) to turn the +;; various kinds of whitespace highlighting on and off in Font-Lock +;; mode. +;; +;; If you want to always use a particular kind of whitespace +;; highlighting, by default, then add the corresponding +;; `show-ws-highlight-*' function (see below) to the hook +;; `font-lock-mode-hook'. Then, whenever Font-Lock mode is turned on, +;; whitespace highlighting will also be turned on. +;; +;; For example, you can turn on tab highlighting by default by adding +;; function `show-ws-highlight-tabs' to `font-lock-mode-hook' in your +;; .emacs file, as follows: +;; +;; (add-hook 'font-lock-mode-hook 'show-ws-highlight-tabs) +;; +;; +;; Faces defined here: +;; +;; `show-ws-hard-space', `show-ws-tab', `show-ws-trailing-whitespace'. +;; +;; Commands defined here: +;; +;; `show-ws-toggle-show-hard-spaces', `show-ws-toggle-show-tabs', +;; `show-ws-toggle-show-trailing-whitespace', +;; `toggle-show-hard-spaces-show-ws' (alias), +;; `toggle-show-tabs-show-ws' (alias), +;; `toggle-show-trailing-whitespace-show-ws' (alias). +;; +;; Non-interactive functions defined here: +;; +;; `show-ws-highlight-hard-spaces', `show-ws-highlight-tabs', +;; `show-ws-highlight-trailing-whitespace'. +;; +;; Internal variables defined here: +;; +;; `show-ws-highlight-hard-spaces-p', `show-ws-highlight-tabs-p', +;; `show-ws-highlight-trailing-whitespace-p'. +;; +;; Drew Adams wrote the `toggle-*' commands and `*-p' variables. +;; +;; Peter Steiner wrote the original code that did the equivalent of +;; the `show-ws-highlight-*' commands here in his `hilite-trail.el'. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Change log: +;; +;; 2007/09/25 dadams +;; Renamed to use prefix show-ws-. Thx to Cyril Brulebois. +;; 2006/11/11 dadams +;; Corrected doc strings. Clarified: hard space is non-breaking space, \240. +;; Included hard space in highlight-trailing-whitespace. +;; 2006/04/06 dadams +;; highlight-*: Use font-lock-add-keywords. Thanks to Karl Chen. +;; 2006/02/20 dadams +;; Mentioned in Commentary how to use non-interactively. +;; 2006/01/07 dadams +;; Added :link for sending bug report. +;; 2006/01/06 dadams +;; Added defgroup and use it. +;; 2005/12/30 dadams +;; Removed require of def-face-const.el. +;; Renamed faces, without "-face". +;; 2005/01/25 dadams +;; Removed ###autoload for defvars. +;; 2004/06/10 dadams +;; Fixed minor bug in highlight-* functions. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth +;; Floor, Boston, MA 02110-1301, USA. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; Code: + +(and (< emacs-major-version 20) (eval-when-compile (require 'cl))) ;; when, push + +;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup Show-Whitespace nil + "Highlight whitespace of various kinds." + :prefix "show-ws-" + :group 'convenience :group 'matching + :link `(url-link :tag "Send Bug Report" + ,(concat "mailto:" "drew.adams" "@" "oracle" ".com?subject=\ +show-wspace.el bug: \ +&body=Describe bug here, starting with `emacs -q'. \ +Don't forget to mention your Emacs and library versions.")) + :link '(url-link :tag "Other Libraries by Drew" + "http://www.emacswiki.org/cgi-bin/wiki/DrewsElispLibraries") + :link '(url-link :tag "Download" + "http://www.emacswiki.org/cgi-bin/wiki/show-wspace.el") + :link '(url-link :tag "Description" + "http://www.emacswiki.org/cgi-bin/wiki/ShowWhiteSpace#ShowWspace") + :link '(emacs-commentary-link :tag "Commentary" "show-wspace") + ) + +(defface show-ws-tab '((t (:background "LemonChiffon"))) + "*Face for highlighting tab characters (`C-i') in Font-Lock mode." + :group 'Show-Whitespace :group 'font-lock :group 'faces) + +(defface show-ws-trailing-whitespace '((t (:background "Gold"))) + "*Face for highlighting whitespace at line ends in Font-Lock mode." + :group 'Show-Whitespace :group 'font-lock :group 'faces) + +(defface show-ws-hard-space '((t (:background "PaleGreen"))) + "*Face for highlighting non-breaking spaces (`\240')in Font-Lock mode." + :group 'Show-Whitespace :group 'font-lock :group 'faces) + + +(defvar show-ws-highlight-tabs-p nil + "Non-nil means font-lock mode highlights TAB characters (`C-i').") + +(defvar show-ws-highlight-trailing-whitespace-p nil + "Non-nil means font-lock mode highlights whitespace at line ends.") + +(defvar show-ws-highlight-hard-spaces-p nil + "Non-nil means font-lock mode highlights non-breaking spaces (`\240').") + +;;;###autoload +(defalias 'toggle-show-tabs-show-ws 'show-ws-toggle-show-tabs) +;;;###autoload +(defun show-ws-toggle-show-tabs () + "Toggle highlighting of TABs, using face `show-ws-tab'." + (interactive) + (if show-ws-highlight-tabs-p + (remove-hook 'font-lock-mode-hook 'show-ws-highlight-tabs) + (add-hook 'font-lock-mode-hook 'show-ws-highlight-tabs)) + (setq show-ws-highlight-tabs-p (not show-ws-highlight-tabs-p)) + (font-lock-mode) (font-lock-mode) + (message "TAB highlighting is now %s." (if show-ws-highlight-tabs-p "ON" "OFF"))) + +;;;###autoload +(defalias 'toggle-show-hard-spaces-show-ws 'show-ws-toggle-show-hard-spaces) +;;;###autoload +(defun show-ws-toggle-show-hard-spaces () + "Toggle highlighting of non-breaking space characters (`\240'). +Uses face `show-ws-hard-space'." + (interactive) + (if show-ws-highlight-hard-spaces-p + (remove-hook 'font-lock-mode-hook 'show-ws-highlight-hard-spaces) + (add-hook 'font-lock-mode-hook 'show-ws-highlight-hard-spaces)) + (setq show-ws-highlight-hard-spaces-p (not show-ws-highlight-hard-spaces-p)) + (font-lock-mode) (font-lock-mode) + (message "Hard (non-breaking) space highlighting is now %s." + (if show-ws-highlight-hard-spaces-p "ON" "OFF"))) + +;;;###autoload +(defalias 'toggle-show-trailing-whitespace-show-ws + 'show-ws-toggle-show-trailing-whitespace) +;;;###autoload +(defun show-ws-toggle-show-trailing-whitespace () + "Toggle highlighting of trailing whitespace. +Uses face `show-ws-trailing-whitespace'." + (interactive) + (if show-ws-highlight-trailing-whitespace-p + (remove-hook 'font-lock-mode-hook 'show-ws-highlight-trailing-whitespace) + (add-hook 'font-lock-mode-hook 'show-ws-highlight-trailing-whitespace)) + (setq show-ws-highlight-trailing-whitespace-p + (not show-ws-highlight-trailing-whitespace-p)) + (font-lock-mode) (font-lock-mode) + (message "Trailing whitespace highlighting is now %s." + (if show-ws-highlight-trailing-whitespace-p "ON" "OFF"))) + +(defun show-ws-highlight-tabs () + "Highlight tab characters (`C-i')." + (font-lock-add-keywords nil '(("[\t]+" (0 'show-ws-tab t))))) +(defun show-ws-highlight-hard-spaces () + "Highlight hard (non-breaking) space characters (`\240')." + (font-lock-add-keywords nil '(("[\240]+" (0 'show-ws-hard-space t))))) +(defun show-ws-highlight-trailing-whitespace () + "Highlight whitespace characters at line ends." + (font-lock-add-keywords + nil '(("[\240\040\t]+$" (0 'show-ws-trailing-whitespace t))))) + +;;;;;;;;;;;;;;;;;;;;;;; + +(provide 'show-wspace) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; show-wspace.el ends here
new file mode 100644 --- /dev/null +++ b/.emacs @@ -0,0 +1,88 @@ +;; Augie Fackler's .emacs file +; Places I've stolen from: +; Karl Fogel: http://svn.red-bean.com/repos/kfogel/trunk +; Dave Anderson: + +(setq load-path (cons (expand-file-name "~/.elisp") load-path)) +; Better buffer switching and file loading (load first in case we need the +; * Messages * buffer) +(require 'ido) +(ido-mode t) + +(require 'show-wspace) +(require 'doctest-mode) + +; Start the server so that emacsclient will work +; TODO: is there a way to *not* start a server if one was already running? +(server-start) + +; All lines should end in a newline +(setq require-final-newline t) + +; disable tabs +(setq tab-width 4) +(setq-default indent-tabs-mode nil) + +(defun af-python-mode-hook () + ; highlight tabs in Python + (make-variable-buffer-local 'font-lock-mode-hook) + (add-hook 'font-lock-mode-hook 'show-ws-highlight-tabs) + (make-variable-buffer-local 'python-indent) + (if (string-match "melange" buffer-file-name) + (set-variable 'python-indent 2)) +) +(add-hook 'python-mode-hook 'af-python-mode-hook) + +;;pymacs +(setenv "PYTHONPATH" (concat (getenv "HOME") "/unixSoft/lib/python")) +(autoload 'pymacs-apply "pymacs") +(autoload 'pymacs-call "pymacs") +(autoload 'pymacs-eval "pymacs" nil t) +(autoload 'pymacs-exec "pymacs" nil t) +(autoload 'pymacs-load "pymacs" nil t) +(eval-after-load "pymacs" + '(add-to-list 'pymacs-load-path "~/unixSoft/lib/python")) +;(pymacs-load "ropemacs" "rope-") +;(setq ropemacs-enable-autoimport t) + +; text-mode tries to use M-s for something other than my save shortcut. +; That's evil. Stop it from doing that. +(add-hook 'text-mode-hook '(lambda () + (define-key text-mode-map "\M-s" + 'save-buffer))) + +; Cleanup whitespace before saves. +(add-hook 'before-save-hook '(lambda () + (whitespace-cleanup))) + +; Disable that startup screen +(setq inhibit-startup-message t) + +; Basically everything I do is in version control, stop saving backup files +(setq make-backup-files nil) + +; Set some pretty colors that are gentler on my eyes +(setq default-frame-alist + '((width . 80) + (cursor-color . "white") + (cursor-type . box) + (foreground-color . "white") + (background-color . "black") + ) + ) + +;; Desktop mode to remember buffers +(load "desktop") +(setq desktop-enable t) + +;; Automatically revert unedited files that change on the underlying +;; system. +(global-auto-revert-mode) + +;; Key Bindings +; M-backspace kills the current buffer +(global-set-key [(meta backspace)] 'kill-this-buffer) +; Save early and often, with only one keystroke +(global-set-key [(meta s)] 'save-buffer) +; Typing tab is for lesser editors, make hitting return do that +(global-set-key "\C-m" 'newline-and-indent)
new file mode 100644 --- /dev/null +++ b/.hgrc @@ -0,0 +1,46 @@ +[extensions] +hgext.color= +hgext.hgk= +hgext.record= +hgext.convert= +hgext.mq = +hgext.parentrevspec= +hgext.transplant= +hgext.rebase= +hgsubversion= +hgext.zeroconf= +hgext.patchbomb= +hgext.graphlog= + +[ui] +username = Augie Fackler <durin42@gmail.com> +style = compact + +[defaults] +qimport = --git +qrefresh = --git +annotate = -nu +heads = --style default +serve = -v +log = -l 6 +glog = -l 6 +diff = -p + +[diff] +git=1 + +[web] +style = gitweb +port=7000 +allow_archive = bz2 zip + +[paths] +hgcrew = http://hg.intevation.org/mercurial/crew +hgbookmarks = http://www.bitbucket.org/segv/hgbookmarks +hgsubv = https://durin42@bitbucket.org/durin42/hgsubversion + +[hooks] +# Prevent "hg pull" if MQ patches are applied. +#prechangegroup.mq-no-pull = ! hg qtop > /dev/null 2>&1 +# Prevent "hg push" if MQ patches are applied. +#preoutgoing.mq-no-push = ! hg qtop > /dev/null 2>&1
new file mode 100644 --- /dev/null +++ b/.ipython/ipythonrc @@ -0,0 +1,632 @@ +# -*- Mode: Shell-Script -*- Not really, but shows comments correctly +# $Id: ipythonrc 2156 2007-03-19 02:32:19Z fperez $ + +#*************************************************************************** +# +# Configuration file for IPython -- ipythonrc format +# +# =========================================================== +# Deprecation note: you should look into modifying ipy_user_conf.py (located +# in ~/.ipython or ~/_ipython, depending on your platform) instead, it's a +# more flexible and robust (and better supported!) configuration +# method. +# =========================================================== +# +# The format of this file is simply one of 'key value' lines. +# Lines containing only whitespace at the beginning and then a # are ignored +# as comments. But comments can NOT be put on lines with data. + +# The meaning and use of each key are explained below. + +#--------------------------------------------------------------------------- +# Section: included files + +# Put one or more *config* files (with the syntax of this file) you want to +# include. For keys with a unique value the outermost file has precedence. For +# keys with multiple values, they all get assembled into a list which then +# gets loaded by IPython. + +# In this file, all lists of things should simply be space-separated. + +# This allows you to build hierarchies of files which recursively load +# lower-level services. If this is your main ~/.ipython/ipythonrc file, you +# should only keep here basic things you always want available. Then you can +# include it in every other special-purpose config file you create. +include + +#--------------------------------------------------------------------------- +# Section: startup setup + +# These are mostly things which parallel a command line option of the same +# name. + +# Keys in this section should only appear once. If any key from this section +# is encountered more than once, the last value remains, all earlier ones get +# discarded. + + +# Automatic calling of callable objects. If set to 1 or 2, callable objects +# are automatically called when invoked at the command line, even if you don't +# type parentheses. IPython adds the parentheses for you. For example: + +#In [1]: str 45 +#------> str(45) +#Out[1]: '45' + +# IPython reprints your line with '---->' indicating that it added +# parentheses. While this option is very convenient for interactive use, it +# may occasionally cause problems with objects which have side-effects if +# called unexpectedly. + +# The valid values for autocall are: + +# autocall 0 -> disabled (you can toggle it at runtime with the %autocall magic) + +# autocall 1 -> active, but do not apply if there are no arguments on the line. + +# In this mode, you get: + +#In [1]: callable +#Out[1]: <built-in function callable> + +#In [2]: callable 'hello' +#------> callable('hello') +#Out[2]: False + +# 2 -> Active always. Even if no arguments are present, the callable object +# is called: + +#In [4]: callable +#------> callable() + +# Note that even with autocall off, you can still use '/' at the start of a +# line to treat the first argument on the command line as a function and add +# parentheses to it: + +#In [8]: /str 43 +#------> str(43) +#Out[8]: '43' + +autocall 1 + +# Auto-edit syntax errors. When you use the %edit magic in ipython to edit +# source code (see the 'editor' variable below), it is possible that you save +# a file with syntax errors in it. If this variable is true, IPython will ask +# you whether to re-open the editor immediately to correct such an error. + +autoedit_syntax 0 + +# Auto-indent. IPython can recognize lines ending in ':' and indent the next +# line, while also un-indenting automatically after 'raise' or 'return'. + +# This feature uses the readline library, so it will honor your ~/.inputrc +# configuration (or whatever file your INPUTRC variable points to). Adding +# the following lines to your .inputrc file can make indent/unindenting more +# convenient (M-i indents, M-u unindents): + +# $if Python +# "\M-i": " " +# "\M-u": "\d\d\d\d" +# $endif + +# The feature is potentially a bit dangerous, because it can cause problems +# with pasting of indented code (the pasted code gets re-indented on each +# line). But it's a huge time-saver when working interactively. The magic +# function %autoindent allows you to toggle it on/off at runtime. + +autoindent 1 + +# Auto-magic. This gives you access to all the magic functions without having +# to prepend them with an % sign. If you define a variable with the same name +# as a magic function (say who=1), you will need to access the magic function +# with % (%who in this example). However, if later you delete your variable +# (del who), you'll recover the automagic calling form. + +# Considering that many magic functions provide a lot of shell-like +# functionality, automagic gives you something close to a full Python+system +# shell environment (and you can extend it further if you want). + +automagic 1 + +# Size of the output cache. After this many entries are stored, the cache will +# get flushed. Depending on the size of your intermediate calculations, you +# may have memory problems if you make it too big, since keeping things in the +# cache prevents Python from reclaiming the memory for old results. Experiment +# with a value that works well for you. + +# If you choose cache_size 0 IPython will revert to python's regular >>> +# unnumbered prompt. You will still have _, __ and ___ for your last three +# results, but that will be it. No dynamic _1, _2, etc. will be created. If +# you are running on a slow machine or with very limited memory, this may +# help. + +cache_size 1000 + +# Classic mode: Setting 'classic 1' you lose many of IPython niceties, +# but that's your choice! Classic 1 -> same as IPython -classic. +# Note that this is _not_ the normal python interpreter, it's simply +# IPython emulating most of the classic interpreter's behavior. +classic 0 + +# colors - Coloring option for prompts and traceback printouts. + +# Currently available schemes: NoColor, Linux, LightBG. + +# This option allows coloring the prompts and traceback printouts. This +# requires a terminal which can properly handle color escape sequences. If you +# are having problems with this, use the NoColor scheme (uses no color escapes +# at all). + +# The Linux option works well in linux console type environments: dark +# background with light fonts. + +# LightBG is similar to Linux but swaps dark/light colors to be more readable +# in light background terminals. + +# keep uncommented only the one you want: +colors Linux +#colors LightBG +#colors NoColor + +######################## +# Note to Windows users +# +# Color and readline support is avaialble to Windows users via Gary Bishop's +# readline library. You can find Gary's tools at +# http://sourceforge.net/projects/uncpythontools. +# Note that his readline module requires in turn the ctypes library, available +# at http://starship.python.net/crew/theller/ctypes. +######################## + +# color_info: IPython can display information about objects via a set of +# functions, and optionally can use colors for this, syntax highlighting +# source code and various other elements. This information is passed through a +# pager (it defaults to 'less' if $PAGER is not set). + +# If your pager has problems, try to setting it to properly handle escapes +# (see the less manpage for detail), or disable this option. The magic +# function %color_info allows you to toggle this interactively for testing. + +color_info 1 + +# confirm_exit: set to 1 if you want IPython to confirm when you try to exit +# with an EOF (Control-d in Unix, Control-Z/Enter in Windows). Note that using +# the magic functions %Exit or %Quit you can force a direct exit, bypassing +# any confirmation. + +confirm_exit 0 + +# Use deep_reload() as a substitute for reload() by default. deep_reload() is +# still available as dreload() and appears as a builtin. + +deep_reload 0 + +# Which editor to use with the %edit command. If you leave this at 0, IPython +# will honor your EDITOR environment variable. Since this editor is invoked on +# the fly by ipython and is meant for editing small code snippets, you may +# want to use a small, lightweight editor here. + +# For Emacs users, setting up your Emacs server properly as described in the +# manual is a good idea. An alternative is to use jed, a very light editor +# with much of the feel of Emacs (though not as powerful for heavy-duty work). + +editor 0 + +# log 1 -> same as ipython -log. This automatically logs to ./ipython.log +log 0 + +# Same as ipython -Logfile YourLogfileName. +# Don't use with log 1 (use one or the other) +logfile '' + +# banner 0 -> same as ipython -nobanner +banner 0 + +# messages 0 -> same as ipython -nomessages +messages 1 + +# Automatically call the pdb debugger after every uncaught exception. If you +# are used to debugging using pdb, this puts you automatically inside of it +# after any call (either in IPython or in code called by it) which triggers an +# exception which goes uncaught. +pdb 0 + +# Enable the pprint module for printing. pprint tends to give a more readable +# display (than print) for complex nested data structures. +pprint 1 + +# Prompt strings + +# Most bash-like escapes can be used to customize IPython's prompts, as well as +# a few additional ones which are IPython-specific. All valid prompt escapes +# are described in detail in the Customization section of the IPython HTML/PDF +# manual. + +# Use \# to represent the current prompt number, and quote them to protect +# spaces. +prompt_in1 'In [\#]: ' + +# \D is replaced by as many dots as there are digits in the +# current value of \#. +prompt_in2 ' .\D.: ' + +prompt_out 'Out[\#]: ' + +# Select whether to left-pad the output prompts to match the length of the +# input ones. This allows you for example to use a simple '>' as an output +# prompt, and yet have the output line up with the input. If set to false, +# the output prompts will be unpadded (flush left). +prompts_pad_left 1 + +# Pylab support: when ipython is started with the -pylab switch, by default it +# executes 'from matplotlib.pylab import *'. Set this variable to false if you +# want to disable this behavior. + +# For details on pylab, see the matplotlib website: +# http://matplotlib.sf.net +pylab_import_all 1 + + +# quick 1 -> same as ipython -quick +quick 0 + +# Use the readline library (1) or not (0). Most users will want this on, but +# if you experience strange problems with line management (mainly when using +# IPython inside Emacs buffers) you may try disabling it. Not having it on +# prevents you from getting command history with the arrow keys, searching and +# name completion using TAB. + +readline 1 + +# Screen Length: number of lines of your screen. This is used to control +# printing of very long strings. Strings longer than this number of lines will +# be paged with the less command instead of directly printed. + +# The default value for this is 0, which means IPython will auto-detect your +# screen size every time it needs to print. If for some reason this isn't +# working well (it needs curses support), specify it yourself. Otherwise don't +# change the default. + +screen_length 0 + +# Prompt separators for input and output. +# Use \n for newline explicitly, without quotes. +# Use 0 (like at the cmd line) to turn off a given separator. + +# The structure of prompt printing is: +# (SeparateIn)Input.... +# (SeparateOut)Output... +# (SeparateOut2), # that is, no newline is printed after Out2 +# By choosing these you can organize your output any way you want. + +separate_in \n +separate_out 0 +separate_out2 0 + +# 'nosep 1' is a shorthand for '-SeparateIn 0 -SeparateOut 0 -SeparateOut2 0'. +# Simply removes all input/output separators, overriding the choices above. +nosep 0 + +# Wildcard searches - IPython has a system for searching names using +# shell-like wildcards; type %psearch? for details. This variables sets +# whether by default such searches should be case sensitive or not. You can +# always override the default at the system command line or the IPython +# prompt. + +wildcards_case_sensitive 1 + +# Object information: at what level of detail to display the string form of an +# object. If set to 0, ipython will compute the string form of any object X, +# by calling str(X), when X? is typed. If set to 1, str(X) will only be +# computed when X?? is given, and if set to 2 or higher, it will never be +# computed (there is no X??? level of detail). This is mostly of use to +# people who frequently manipulate objects whose string representation is +# extremely expensive to compute. + +object_info_string_level 0 + +# xmode - Exception reporting mode. + +# Valid modes: Plain, Context and Verbose. + +# Plain: similar to python's normal traceback printing. + +# Context: prints 5 lines of context source code around each line in the +# traceback. + +# Verbose: similar to Context, but additionally prints the variables currently +# visible where the exception happened (shortening their strings if too +# long). This can potentially be very slow, if you happen to have a huge data +# structure whose string representation is complex to compute. Your computer +# may appear to freeze for a while with cpu usage at 100%. If this occurs, you +# can cancel the traceback with Ctrl-C (maybe hitting it more than once). + +#xmode Plain +xmode Context +#xmode Verbose + +# multi_line_specials: if true, allow magics, aliases and shell escapes (via +# !cmd) to be used in multi-line input (like for loops). For example, if you +# have this active, the following is valid in IPython: +# +#In [17]: for i in range(3): +# ....: mkdir $i +# ....: !touch $i/hello +# ....: ls -l $i + +multi_line_specials 1 + + +# System calls: When IPython makes system calls (e.g. via special syntax like +# !cmd or !!cmd, or magics like %sc or %sx), it can print the command it is +# executing to standard output, prefixed by a header string. + +system_header "IPython system call: " + +system_verbose 1 + +# wxversion: request a specific wxPython version (used for -wthread) + +# Set this to the value of wxPython you want to use, but note that this +# feature requires you to have the wxversion Python module to work. If you +# don't have the wxversion module (try 'import wxversion' at the prompt to +# check) or simply want to leave the system to pick up the default, leave this +# variable at 0. + +wxversion 0 + +#--------------------------------------------------------------------------- +# Section: Readline configuration (readline is not available for MS-Windows) + +# This is done via the following options: + +# (i) readline_parse_and_bind: this option can appear as many times as you +# want, each time defining a string to be executed via a +# readline.parse_and_bind() command. The syntax for valid commands of this +# kind can be found by reading the documentation for the GNU readline library, +# as these commands are of the kind which readline accepts in its +# configuration file. + +# The TAB key can be used to complete names at the command line in one of two +# ways: 'complete' and 'menu-complete'. The difference is that 'complete' only +# completes as much as possible while 'menu-complete' cycles through all +# possible completions. Leave the one you prefer uncommented. + +readline_parse_and_bind tab: complete +#readline_parse_and_bind tab: menu-complete + +# This binds Control-l to printing the list of all possible completions when +# there is more than one (what 'complete' does when hitting TAB twice, or at +# the first TAB if show-all-if-ambiguous is on) +readline_parse_and_bind "\C-l": possible-completions + +# This forces readline to automatically print the above list when tab +# completion is set to 'complete'. You can still get this list manually by +# using the key bound to 'possible-completions' (Control-l by default) or by +# hitting TAB twice. Turning this on makes the printing happen at the first +# TAB. +readline_parse_and_bind set show-all-if-ambiguous on + +# If you have TAB set to complete names, you can rebind any key (Control-o by +# default) to insert a true TAB character. +readline_parse_and_bind "\C-o": tab-insert + +# These commands allow you to indent/unindent easily, with the 4-space +# convention of the Python coding standards. Since IPython's internal +# auto-indent system also uses 4 spaces, you should not change the number of +# spaces in the code below. +readline_parse_and_bind "\M-i": " " +readline_parse_and_bind "\M-o": "\d\d\d\d" +readline_parse_and_bind "\M-I": "\d\d\d\d" + +# Bindings for incremental searches in the history. These searches use the +# string typed so far on the command line and search anything in the previous +# input history containing them. +readline_parse_and_bind "\C-r": reverse-search-history +readline_parse_and_bind "\C-s": forward-search-history + +# Bindings for completing the current line in the history of previous +# commands. This allows you to recall any previous command by typing its first +# few letters and hitting Control-p, bypassing all intermediate commands which +# may be in the history (much faster than hitting up-arrow 50 times!) +readline_parse_and_bind "\C-p": history-search-backward +readline_parse_and_bind "\C-n": history-search-forward + +# I also like to have the same functionality on the plain arrow keys. If you'd +# rather have the arrows use all the history (and not just match what you've +# typed so far), comment out or delete the next two lines. +readline_parse_and_bind "\e[A": history-search-backward +readline_parse_and_bind "\e[B": history-search-forward + +# These are typically on by default under *nix, but not win32. +readline_parse_and_bind "\C-k": kill-line +readline_parse_and_bind "\C-u": unix-line-discard + +# (ii) readline_remove_delims: a string of characters to be removed from the +# default word-delimiters list used by readline, so that completions may be +# performed on strings which contain them. + +readline_remove_delims -/~ + +# (iii) readline_merge_completions: whether to merge the result of all +# possible completions or not. If true, IPython will complete filenames, +# python names and aliases and return all possible completions. If you set it +# to false, each completer is used at a time, and only if it doesn't return +# any completions is the next one used. + +# The default order is: [python_matches, file_matches, alias_matches] + +readline_merge_completions 1 + +# (iv) readline_omit__names: normally hitting <tab> after a '.' in a name +# will complete all attributes of an object, including all the special methods +# whose names start with single or double underscores (like __getitem__ or +# __class__). + +# This variable allows you to control this completion behavior: + +# readline_omit__names 1 -> completion will omit showing any names starting +# with two __, but it will still show names starting with one _. + +# readline_omit__names 2 -> completion will omit all names beginning with one +# _ (which obviously means filtering out the double __ ones). + +# Even when this option is set, you can still see those names by explicitly +# typing a _ after the period and hitting <tab>: 'name._<tab>' will always +# complete attribute names starting with '_'. + +# This option is off by default so that new users see all attributes of any +# objects they are dealing with. + +readline_omit__names 0 + +#--------------------------------------------------------------------------- +# Section: modules to be loaded with 'import ...' + +# List, separated by spaces, the names of the modules you want to import + +# Example: +# import_mod sys os +# will produce internally the statements +# import sys +# import os + +# Each import is executed in its own try/except block, so if one module +# fails to load the others will still be ok. + +import_mod + +#--------------------------------------------------------------------------- +# Section: modules to import some functions from: 'from ... import ...' + +# List, one per line, the modules for which you want only to import some +# functions. Give the module name first and then the name of functions to be +# imported from that module. + +# Example: + +# import_some IPython.genutils timing timings +# will produce internally the statement +# from IPython.genutils import timing, timings + +# timing() and timings() are two IPython utilities for timing the execution of +# your own functions, which you may find useful. Just commment out the above +# line if you want to test them. + +# If you have more than one modules_some line, each gets its own try/except +# block (like modules, see above). + +import_some + +#--------------------------------------------------------------------------- +# Section: modules to import all from : 'from ... import *' + +# List (same syntax as import_mod above) those modules for which you want to +# import all functions. Remember, this is a potentially dangerous thing to do, +# since it is very easy to overwrite names of things you need. Use with +# caution. + +# Example: +# import_all sys os +# will produce internally the statements +# from sys import * +# from os import * + +# As before, each will be called in a separate try/except block. + +import_all + +#--------------------------------------------------------------------------- +# Section: Python code to execute. + +# Put here code to be explicitly executed (keep it simple!) +# Put one line of python code per line. All whitespace is removed (this is a +# feature, not a bug), so don't get fancy building loops here. +# This is just for quick convenient creation of things you want available. + +# Example: +# execute x = 1 +# execute print 'hello world'; y = z = 'a' +# will produce internally +# x = 1 +# print 'hello world'; y = z = 'a' +# and each *line* (not each statement, we don't do python syntax parsing) is +# executed in its own try/except block. + +execute + +# Note for the adventurous: you can use this to define your own names for the +# magic functions, by playing some namespace tricks: + +# execute __IPYTHON__.magic_pf = __IPYTHON__.magic_profile + +# defines %pf as a new name for %profile. + +#--------------------------------------------------------------------------- +# Section: Pyhton files to load and execute. + +# Put here the full names of files you want executed with execfile(file). If +# you want complicated initialization, just write whatever you want in a +# regular python file and load it from here. + +# Filenames defined here (which *must* include the extension) are searched for +# through all of sys.path. Since IPython adds your .ipython directory to +# sys.path, they can also be placed in your .ipython dir and will be +# found. Otherwise (if you want to execute things not in .ipyton nor in +# sys.path) give a full path (you can use ~, it gets expanded) + +# Example: +# execfile file1.py ~/file2.py +# will generate +# execfile('file1.py') +# execfile('_path_to_your_home/file2.py') + +# As before, each file gets its own try/except block. + +execfile + +# If you are feeling adventurous, you can even add functionality to IPython +# through here. IPython works through a global variable called __ip which +# exists at the time when these files are read. If you know what you are doing +# (read the source) you can add functions to __ip in files loaded here. + +# The file example-magic.py contains a simple but correct example. Try it: + +# execfile example-magic.py + +# Look at the examples in IPython/iplib.py for more details on how these magic +# functions need to process their arguments. + +#--------------------------------------------------------------------------- +# Section: aliases for system shell commands + +# Here you can define your own names for system commands. The syntax is +# similar to that of the builtin %alias function: + +# alias alias_name command_string + +# The resulting aliases are auto-generated magic functions (hence usable as +# %alias_name) + +# For example: + +# alias myls ls -la + +# will define 'myls' as an alias for executing the system command 'ls -la'. +# This allows you to customize IPython's environment to have the same aliases +# you are accustomed to from your own shell. + +# You can also define aliases with parameters using %s specifiers (one per +# parameter): + +# alias parts echo first %s second %s + +# will give you in IPython: +# >>> %parts A B +# first A second B + +# Use one 'alias' statement per alias you wish to define. + +# alias + +#************************* end of file <ipythonrc> ************************
new file mode 100644 --- /dev/null +++ b/.pdbrc.py @@ -0,0 +1,28 @@ +import readline +import pdb + +# make 'l' an alias to 'longlist' +pdb.Pdb.do_l = pdb.Pdb.do_longlist +pdb.Pdb.do_st = pdb.Pdb.do_sticky + +class Config(pdb.DefaultConfig): + + def __init__(self): + readline.parse_and_bind('set convert-meta on') + readline.parse_and_bind('Meta-/: complete') + + try: + from pygments.formatters import terminal + except ImportError: + pass + else: + self.colorscheme = terminal.TERMINAL_COLORS.copy() + self.colorscheme.update({ + terminal.Keyword: ('darkred', 'red'), + terminal.Number: ('darkyellow', 'yellow'), + terminal.String: ('brown', 'green'), + terminal.Name.Function: ('darkgreen', 'blue'), + terminal.Name.Namespace: ('teal', 'turquoise'), + }) + +
new file mode 100644 --- /dev/null +++ b/.python/startup.py @@ -0,0 +1,35 @@ +# Add auto-completion and a stored history file of commands to your Python +# interactive interpreter. Requires Python 2.0+, readline. Autocomplete is +# bound to the Esc key by default (you can change it - see readline docs). +# +# Store the file in ~/.pystartup, and set an environment variable to point +# to it: "export PYTHONSTARTUP=/max/home/itamar/.pystartup" in bash. +# +# Note that PYTHONSTARTUP does *not* expand "~", so you have to put in the +# full path to your home directory. + +import atexit +import os +import readline +import rlcompleter + +# TODO(augie) I'd like to replace this with an environment variable +historyPath = os.path.expanduser("~/.python/python_history") + +def save_history(historyPath=historyPath): + import readline + readline.write_history_file(historyPath) + +if os.path.exists(historyPath): + readline.read_history_file(historyPath) + +atexit.register(save_history) + +# Clean up after ourselves so we don't pollute the namespace +del os, atexit, readline, rlcompleter, save_history, historyPath +try: + import IPython + def ipy(): + IPython.Shell.start().mainloop() +except ImportError: + pass
new file mode 100644 --- /dev/null +++ b/.screenrc @@ -0,0 +1,11 @@ +startup_message off +caption always "%w" +defscrollback 2000 +vbell off +escape "^Ww" +bell_msg "^G" + + +#hardstatus on +#hardstatus alwayslastline +#hardstatus string "%{.bW}%-w%{.rW}%n %t%{-}%+w %=%{..G} %H %{..Y} %m/%d %C%a "
new file mode 100644 --- /dev/null +++ b/.vim/colors/gothic.vim @@ -0,0 +1,42 @@ +" Vim color file +" Maintainer: Stefano deFlorian - \Goth\ <stefano@junglebit.net> +" Last Change: 2003 Dec 9 +" Light - Dark :-) +" optimized for TFT panels + +set background=dark +hi clear +if exists("syntax_on") + syntax reset +endif +"colorscheme default +let g:colors_name = "gothic" + +" hardcoded colors : + +" GUI +highlight Normal guifg=#efefef guibg=#000000 +highlight Cursor guifg=#000000 guibg=#efefef gui=NONE +highlight Search guifg=#ffff60 guibg=#0000ff gui=NONE +highlight Visual guifg=Grey25 gui=NONE +highlight Special guifg=Orange +highlight Comment guifg=#3030ff +highlight StatusLine guifg=blue guibg=white +highlight Statement guifg=#ffff60 gui=NONE +highlight PreProc guifg=#a0e0a0 +highlight Identifier guifg=#00ffff +highlight Constant guifg=#a0a0a0 +highlight Type guifg=#a0a0ff gui=NONE + +" Console +highlight Normal ctermfg=LightGrey ctermbg=Black +highlight Cursor ctermfg=Black ctermbg=LightGrey cterm=NONE +highlight Search ctermfg=Yellow ctermbg=Blue cterm=NONE +highlight Visual cterm=reverse +highlight Special ctermfg=Brown +highlight Comment ctermfg=Blue +highlight StatusLine ctermfg=blue ctermbg=white +highlight Identifier ctermfg=Cyan +highlight Statement ctermfg=Yellow cterm=NONE +highlight Constant ctermfg=Grey cterm=NONE +highlight Type ctermfg=LightBlue cterm=NONE
new file mode 100644 --- /dev/null +++ b/.vim/ftplugin/python.vim @@ -0,0 +1,8 @@ +setlocal tabstop=4 +setlocal softtabstop=4 +setlocal shiftwidth=4 +setlocal textwidth=80 +setlocal smarttab +setlocal expandtab +setlocal smartindent +
new file mode 100644 --- /dev/null +++ b/.vim/ftplugin/python_fn.vim @@ -0,0 +1,446 @@ +" -*- vim -*- +" FILE: python.vim +" LAST MODIFICATION: 2008-05-17 6:29pm +" (C) Copyright 2001-2005 Mikael Berthe <bmikael@lists.lilotux.net> +" Maintained by Jon Franklin <jvfranklin@gmail.com> +" Version: 1.12 + +" USAGE: +" +" Save this file to $VIMFILES/ftplugin/python.vim. You can have multiple +" python ftplugins by creating $VIMFILES/ftplugin/python and saving your +" ftplugins in that directory. If saving this to the global ftplugin +" directory, this is the recommended method, since vim ships with an +" ftplugin/python.vim file already. +" You can set the global variable "g:py_select_leading_comments" to 0 +" if you don't want to select comments preceding a declaration (these +" are usually the description of the function/class). +" You can set the global variable "g:py_select_trailing_comments" to 0 +" if you don't want to select comments at the end of a function/class. +" If these variables are not defined, both leading and trailing comments +" are selected. +" Example: (in your .vimrc) "let g:py_select_leading_comments = 0" +" You may want to take a look at the 'shiftwidth' option for the +" shift commands... +" +" REQUIREMENTS: +" vim (>= 7) +" +" Shortcuts: +" ]t -- Jump to beginning of block +" ]e -- Jump to end of block +" ]v -- Select (Visual Line Mode) block +" ]< -- Shift block to left +" ]> -- Shift block to right +" ]# -- Comment selection +" ]u -- Uncomment selection +" ]c -- Select current/previous class +" ]d -- Select current/previous function +" ]<up> -- Jump to previous line with the same/lower indentation +" ]<down> -- Jump to next line with the same/lower indentation + +" Only do this when not done yet for this buffer +if exists("b:loaded_py_ftplugin") + finish +endif +let b:loaded_py_ftplugin = 1 + +map ]t :PBoB<CR> +vmap ]t :<C-U>PBOB<CR>m'gv`` +map ]e :PEoB<CR> +vmap ]e :<C-U>PEoB<CR>m'gv`` + +map ]v ]tV]e +map ]< ]tV]e< +vmap ]< < +map ]> ]tV]e> +vmap ]> > + +map ]# :call PythonCommentSelection()<CR> +vmap ]# :call PythonCommentSelection()<CR> +map ]u :call PythonUncommentSelection()<CR> +vmap ]u :call PythonUncommentSelection()<CR> + +map ]c :call PythonSelectObject("class")<CR> +map ]d :call PythonSelectObject("function")<CR> + +map ]<up> :call PythonNextLine(-1)<CR> +map ]<down> :call PythonNextLine(1)<CR> +" You may prefer use <s-up> and <s-down>... :-) + +" jump to previous class +map ]J :call PythonDec("class", -1)<CR> +vmap ]J :call PythonDec("class", -1)<CR> + +" jump to next class +map ]j :call PythonDec("class", 1)<CR> +vmap ]j :call PythonDec("class", 1)<CR> + +" jump to previous function +map ]F :call PythonDec("function", -1)<CR> +vmap ]F :call PythonDec("function", -1)<CR> + +" jump to next function +map ]f :call PythonDec("function", 1)<CR> +vmap ]f :call PythonDec("function", 1)<CR> + + + +" Menu entries +nmenu <silent> &Python.Update\ IM-Python\ Menu + \:call UpdateMenu()<CR> +nmenu &Python.-Sep1- : +nmenu <silent> &Python.Beginning\ of\ Block<Tab>[t + \]t +nmenu <silent> &Python.End\ of\ Block<Tab>]e + \]e +nmenu &Python.-Sep2- : +nmenu <silent> &Python.Shift\ Block\ Left<Tab>]< + \]< +vmenu <silent> &Python.Shift\ Block\ Left<Tab>]< + \]< +nmenu <silent> &Python.Shift\ Block\ Right<Tab>]> + \]> +vmenu <silent> &Python.Shift\ Block\ Right<Tab>]> + \]> +nmenu &Python.-Sep3- : +vmenu <silent> &Python.Comment\ Selection<Tab>]# + \]# +nmenu <silent> &Python.Comment\ Selection<Tab>]# + \]# +vmenu <silent> &Python.Uncomment\ Selection<Tab>]u + \]u +nmenu <silent> &Python.Uncomment\ Selection<Tab>]u + \]u +nmenu &Python.-Sep4- : +nmenu <silent> &Python.Previous\ Class<Tab>]J + \]J +nmenu <silent> &Python.Next\ Class<Tab>]j + \]j +nmenu <silent> &Python.Previous\ Function<Tab>]F + \]F +nmenu <silent> &Python.Next\ Function<Tab>]f + \]f +nmenu &Python.-Sep5- : +nmenu <silent> &Python.Select\ Block<Tab>]v + \]v +nmenu <silent> &Python.Select\ Function<Tab>]d + \]d +nmenu <silent> &Python.Select\ Class<Tab>]c + \]c +nmenu &Python.-Sep6- : +nmenu <silent> &Python.Previous\ Line\ wrt\ indent<Tab>]<up> + \]<up> +nmenu <silent> &Python.Next\ Line\ wrt\ indent<Tab>]<down> + \]<down> + +:com! PBoB execute "normal ".PythonBoB(line('.'), -1, 1)."G" +:com! PEoB execute "normal ".PythonBoB(line('.'), 1, 1)."G" +:com! UpdateMenu call UpdateMenu() + + +" Go to a block boundary (-1: previous, 1: next) +" If force_sel_comments is true, 'g:py_select_trailing_comments' is ignored +function! PythonBoB(line, direction, force_sel_comments) + let ln = a:line + let ind = indent(ln) + let mark = ln + let indent_valid = strlen(getline(ln)) + let ln = ln + a:direction + if (a:direction == 1) && (!a:force_sel_comments) && + \ exists("g:py_select_trailing_comments") && + \ (!g:py_select_trailing_comments) + let sel_comments = 0 + else + let sel_comments = 1 + endif + + while((ln >= 1) && (ln <= line('$'))) + if (sel_comments) || (match(getline(ln), "^\\s*#") == -1) + if (!indent_valid) + let indent_valid = strlen(getline(ln)) + let ind = indent(ln) + let mark = ln + else + if (strlen(getline(ln))) + if (indent(ln) < ind) + break + endif + let mark = ln + endif + endif + endif + let ln = ln + a:direction + endwhile + + return mark +endfunction + + +" Go to previous (-1) or next (1) class/function definition +function! PythonDec(obj, direction) + if (a:obj == "class") + let objregexp = "^\\s*class\\s\\+[a-zA-Z0-9_]\\+" + \ . "\\s*\\((\\([a-zA-Z0-9_,. \\t\\n]\\)*)\\)\\=\\s*:" + else + let objregexp = "^\\s*def\\s\\+[a-zA-Z0-9_]\\+\\s*(\\_[^:#]*)\\s*:" + endif + let flag = "W" + if (a:direction == -1) + let flag = flag."b" + endif + let res = search(objregexp, flag) +endfunction + + +" Comment out selected lines +" commentString is inserted in non-empty lines, and should be aligned with +" the block +function! PythonCommentSelection() range + let commentString = "#" + let cl = a:firstline + let ind = 1000 " I hope nobody use so long lines! :) + + " Look for smallest indent + while (cl <= a:lastline) + if strlen(getline(cl)) + let cind = indent(cl) + let ind = ((ind < cind) ? ind : cind) + endif + let cl = cl + 1 + endwhile + if (ind == 1000) + let ind = 1 + else + let ind = ind + 1 + endif + + let cl = a:firstline + execute ":".cl + " Insert commentString in each non-empty line, in column ind + while (cl <= a:lastline) + if strlen(getline(cl)) + execute "normal ".ind."|i".commentString + endif + execute "normal \<Down>" + let cl = cl + 1 + endwhile +endfunction + +" Uncomment selected lines +function! PythonUncommentSelection() range + " commentString could be different than the one from CommentSelection() + " For example, this could be "# \\=" + let commentString = "#" + let cl = a:firstline + while (cl <= a:lastline) + let ul = substitute(getline(cl), + \"\\(\\s*\\)".commentString."\\(.*\\)$", "\\1\\2", "") + call setline(cl, ul) + let cl = cl + 1 + endwhile +endfunction + + +" Select an object ("class"/"function") +function! PythonSelectObject(obj) + " Go to the object declaration + normal $ + call PythonDec(a:obj, -1) + let beg = line('.') + + if !exists("g:py_select_leading_comments") || (g:py_select_leading_comments) + let decind = indent(beg) + let cl = beg + while (cl>1) + let cl = cl - 1 + if (indent(cl) == decind) && (getline(cl)[decind] == "#") + let beg = cl + else + break + endif + endwhile + endif + + if (a:obj == "class") + let eod = "\\(^\\s*class\\s\\+[a-zA-Z0-9_]\\+\\s*" + \ . "\\((\\([a-zA-Z0-9_,. \\t\\n]\\)*)\\)\\=\\s*\\)\\@<=:" + else + let eod = "\\(^\\s*def\\s\\+[a-zA-Z0-9_]\\+\\s*(\\_[^:#]*)\\s*\\)\\@<=:" + endif + " Look for the end of the declaration (not always the same line!) + call search(eod, "") + + " Is it a one-line definition? + if match(getline('.'), "^\\s*\\(#.*\\)\\=$", col('.')) == -1 + let cl = line('.') + execute ":".beg + execute "normal V".cl."G" + else + " Select the whole block + execute "normal \<Down>" + let cl = line('.') + execute ":".beg + execute "normal V".PythonBoB(cl, 1, 0)."G" + endif +endfunction + + +" Jump to the next line with the same (or lower) indentation +" Useful for moving between "if" and "else", for example. +function! PythonNextLine(direction) + let ln = line('.') + let ind = indent(ln) + let indent_valid = strlen(getline(ln)) + let ln = ln + a:direction + + while((ln >= 1) && (ln <= line('$'))) + if (!indent_valid) && strlen(getline(ln)) + break + else + if (strlen(getline(ln))) + if (indent(ln) <= ind) + break + endif + endif + endif + let ln = ln + a:direction + endwhile + + execute "normal ".ln."G" +endfunction + +function! UpdateMenu() + " delete menu if it already exists, then rebuild it. + " this is necessary in case you've got multiple buffers open + " a future enhancement to this would be to make the menu aware of + " all buffers currently open, and group classes and functions by buffer + if exists("g:menuran") + aunmenu IM-Python + endif + let restore_fe = &foldenable + set nofoldenable + " preserve disposition of window and cursor + let cline=line('.') + let ccol=col('.') - 1 + norm H + let hline=line('.') + " create the menu + call MenuBuilder() + " restore disposition of window and cursor + exe "norm ".hline."Gzt" + let dnscroll=cline-hline + exe "norm ".dnscroll."j".ccol."l" + let &foldenable = restore_fe +endfunction + +function! MenuBuilder() + norm gg0 + let currentclass = -1 + let classlist = [] + let parentclass = "" + while line(".") < line("$") + " search for a class or function + if match ( getline("."), '^\s*class\s\+[_a-zA-Z].*:\|^\s*def\s\+[_a-zA-Z].*:' ) != -1 + norm ^ + let linenum = line('.') + let indentcol = col('.') + norm "nye + let classordef=@n + norm w"nywge + let objname=@n + let parentclass = FindParentClass(classlist, indentcol) + if classordef == "class" + call AddClass(objname, linenum, parentclass) + else " this is a function + call AddFunction(objname, linenum, parentclass) + endif + " We actually created a menu, so lets set the global variable + let g:menuran=1 + call RebuildClassList(classlist, [objname, indentcol], classordef) + endif " line matched + norm j + endwhile +endfunction + +" classlist contains the list of nested classes we are in. +" in most cases it will be empty or contain a single class +" but where a class is nested within another, it will contain 2 or more +" this function adds or removes classes from the list based on indentation +function! RebuildClassList(classlist, newclass, classordef) + let i = len(a:classlist) - 1 + while i > -1 + if a:newclass[1] <= a:classlist[i][1] + call remove(a:classlist, i) + endif + let i = i - 1 + endwhile + if a:classordef == "class" + call add(a:classlist, a:newclass) + endif +endfunction + +" we found a class or function, determine its parent class based on +" indentation and what's contained in classlist +function! FindParentClass(classlist, indentcol) + let i = 0 + let parentclass = "" + while i < len(a:classlist) + if a:indentcol <= a:classlist[i][1] + break + else + if len(parentclass) == 0 + let parentclass = a:classlist[i][0] + else + let parentclass = parentclass.'\.'.a:classlist[i][0] + endif + endif + let i = i + 1 + endwhile + return parentclass +endfunction + +" add a class to the menu +function! AddClass(classname, lineno, parentclass) + if len(a:parentclass) > 0 + let classstring = a:parentclass.'\.'.a:classname + else + let classstring = a:classname + endif + exe 'menu IM-Python.classes.'.classstring.' :call <SID>JumpToAndUnfold('.a:lineno.')<CR>' +endfunction + +" add a function to the menu, grouped by member class +function! AddFunction(functionname, lineno, parentclass) + if len(a:parentclass) > 0 + let funcstring = a:parentclass.'.'.a:functionname + else + let funcstring = a:functionname + endif + exe 'menu IM-Python.functions.'.funcstring.' :call <SID>JumpToAndUnfold('.a:lineno.')<CR>' +endfunction + + +function! s:JumpToAndUnfold(line) + " Go to the right line + execute 'normal '.a:line.'gg' + " Check to see if we are in a fold + let lvl = foldlevel(a:line) + if lvl != 0 + " and if so, then expand the fold out, other wise, ignore this part. + execute 'normal 15zo' + endif +endfunction + +"" This one will work only on vim 6.2 because of the try/catch expressions. +" function! s:JumpToAndUnfoldWithExceptions(line) +" try +" execute 'normal '.a:line.'gg15zo' +" catch /^Vim\((\a\+)\)\=:E490:/ +" " Do nothing, just consume the error +" endtry +"endfunction + + +" vim:set et sts=2 sw=2: +
new file mode 100644 --- /dev/null +++ b/.vim/indent/htmldjango.vim @@ -0,0 +1,12 @@ +" Vim indent file +" Language: Django HTML template +" Maintainer: Dave Hodder <dmh@dmh.org.uk> +" Last Change: 2007 Jan 25 + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif + +" Use HTML formatting rules. +runtime! indent/html.vim
new file mode 100644 --- /dev/null +++ b/.vim/indent/python.vim @@ -0,0 +1,197 @@ +" Python indent file +" Language: Python +" Maintainer: Eric Mc Sween <em@tomcom.de> +" Original Author: David Bustos <bustos@caltech.edu> +" Last Change: 2004 Jun 07 + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 + +setlocal expandtab +setlocal nolisp +setlocal autoindent +setlocal indentexpr=GetPythonIndent(v:lnum) +setlocal indentkeys=!^F,o,O,<:>,0),0],0},=elif,=except,0# + + +let s:maxoff = 50 + +" Find backwards the closest open parenthesis/bracket/brace. +function! s:SearchParensPair() + let line = line('.') + let col = col('.') + + " Skip strings and comments and don't look too far + let skip = "line('.') < " . (line - s:maxoff) . " ? dummy :" . + \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? ' . + \ '"string\\|comment"' + + " Search for parentheses + call cursor(line, col) + let parlnum = searchpair('(', '', ')', 'bW', skip) + let parcol = col('.') + + " Search for brackets + call cursor(line, col) + let par2lnum = searchpair('\[', '', '\]', 'bW', skip) + let par2col = col('.') + + " Search for braces + call cursor(line, col) + let par3lnum = searchpair('{', '', '}', 'bW', skip) + let par3col = col('.') + + " Get the closest match + if par2lnum > parlnum || (par2lnum == parlnum && par2col > parcol) + let parlnum = par2lnum + let parcol = par2col + endif + if par3lnum > parlnum || (par3lnum == parlnum && par3col > parcol) + let parlnum = par3lnum + let parcol = par3col + endif + + " Put the cursor on the match + if parlnum > 0 + call cursor(parlnum, parcol) + endif + return parlnum +endfunction + +" Find the start of a multi-line statement +function! s:StatementStart(lnum) + let lnum = a:lnum + while 1 + if getline(lnum - 1) =~ '\\$' + let lnum = lnum - 1 + else + call cursor(lnum, 1) + let maybe_lnum = s:SearchParensPair() + if maybe_lnum < 1 + return lnum + else + let lnum = maybe_lnum + endif + endif + endwhile +endfunction + +" Find the block starter that matches the current line +function! s:BlockStarter(lnum, block_start_re) + let lnum = a:lnum + let maxindent = 10000 " whatever + while lnum > 1 + let lnum = prevnonblank(lnum - 1) + if indent(lnum) < maxindent + if getline(lnum) =~ a:block_start_re + return lnum + else + let maxindent = indent(lnum) + " It's not worth going further if we reached the top level + if maxindent == 0 + return -1 + endif + endif + endif + endwhile + return -1 +endfunction + +function! GetPythonIndent(lnum) + + " First line has indent 0 + if a:lnum == 1 + return 0 + endif + + " If we can find an open parenthesis/bracket/brace, line up with it. + call cursor(a:lnum, 1) + let parlnum = s:SearchParensPair() + if parlnum > 0 + let parcol = col('.') + let closing_paren = match(getline(a:lnum), '^\s*[])}]') != -1 + if match(getline(parlnum), '[([{]\s*$', parcol - 1) != -1 + if closing_paren + return indent(parlnum) + else + return indent(parlnum) + &shiftwidth + endif + else + if closing_paren + return parcol - 1 + else + return parcol + endif + endif + endif + + " Examine this line + let thisline = getline(a:lnum) + let thisindent = indent(a:lnum) + + " If the line starts with 'elif' or 'else', line up with 'if' or 'elif' + if thisline =~ '^\s*\(elif\|else\)\>' + let bslnum = s:BlockStarter(a:lnum, '^\s*\(if\|elif\)\>') + if bslnum > 0 + return indent(bslnum) + else + return -1 + endif + endif + + " If the line starts with 'except' or 'finally', line up with 'try' + " or 'except' + if thisline =~ '^\s*\(except\|finally\)\>' + let bslnum = s:BlockStarter(a:lnum, '^\s*\(try\|except\)\>') + if bslnum > 0 + return indent(bslnum) + else + return -1 + endif + endif + + " Examine previous line + let plnum = a:lnum - 1 + let pline = getline(plnum) + let sslnum = s:StatementStart(plnum) + + " If the previous line is blank, keep the same indentation + if pline =~ '^\s*$' + return -1 + endif + + " If this line is explicitly joined, try to find an indentation that looks + " good. + if pline =~ '\\$' + let compound_statement = '^\s*\(if\|while\|for\s.*\sin\|except\)\s*' + let maybe_indent = matchend(getline(sslnum), compound_statement) + if maybe_indent != -1 + return maybe_indent + else + return indent(sslnum) + &sw * 2 + endif + endif + + " If the previous line ended with a colon, indent relative to + " statement start. + if pline =~ ':\s*$' + return indent(sslnum) + &sw + endif + + " If the previous line was a stop-execution statement or a pass + if getline(sslnum) =~ '^\s*\(break\|continue\|raise\|return\|pass\)\>' + " See if the user has already dedented + if indent(a:lnum) > indent(sslnum) - &sw + " If not, recommend one dedent + return indent(sslnum) - &sw + endif + " Otherwise, trust the user + return -1 + endif + + " In all other cases, line up with the start of the previous statement. + return indent(sslnum) +endfunction
new file mode 100644 --- /dev/null +++ b/.vim/plugin/fuzzyfinder.vim @@ -0,0 +1,1676 @@ +"============================================================================= +" fuzzyfinder.vim : Fuzzy/Partial pattern explorer for +" buffer/file/MRU/command/favorite/tag/etc. +"============================================================================= +" +" Author: Takeshi NISHIDA <ns9tks@DELETE-ME.gmail.com> +" Version: 2.13, for Vim 7.1 +" Licence: MIT Licence +" URL: http://www.vim.org/scripts/script.php?script_id=1984 +" +" GetLatestVimScripts: 1984 1 :AutoInstall: fuzzyfinder.vim +" +"============================================================================= +" DOCUMENT: {{{1 +" Japanese: http://vim.g.hatena.ne.jp/keyword/fuzzyfinder.vim +" +"----------------------------------------------------------------------------- +" Description: +" Fuzzyfinder provides convenient ways to quickly reach the buffer/file you +" want. Fuzzyfinder finds matching files/buffers with a fuzzy/partial +" pattern to which it converted the entered pattern. +" +" E.g.: entered pattern -> fuzzy pattern / partial pattern +" abc -> *a*b*c* / *abc* +" a?c -> *a?c* / *a?c* +" dir/file -> dir/*f*i*l*e* / dir/*file* +" d*r/file -> d*r/*f*i*l*e* / d*r/*file* +" ../**/s -> ../**/*s* / ../**/*s* +" +" (** allows searching a directory tree.) +" +" You will be happy when: +" "./OhLongLongLongLongLongFile.txt" +" "./AhLongLongLongLongLongName.txt" +" "./AhLongLongLongLongLongFile.txt" <- you want :O +" Type "AF" and "AhLongLongLongLongLongFile.txt" will be select. :D +" +" Fuzzyfinder has some modes: +" - Buffer mode +" - File mode +" - Directory mode (yet another :cd command) +" - MRU-file mode (most recently used files) +" - MRU-command mode (most recently used command-lines) +" - Favorite-file mode +" - Tag mode (yet another :tag command) +" - Tagged-file mode (files which are included in current tags) +" +" Fuzzyfinder supports the multibyte. +" +"----------------------------------------------------------------------------- +" Installation: +" Drop this file in your plugin directory. +" +"----------------------------------------------------------------------------- +" Usage: +" Starting Fuzzyfinder: +" You can start Fuzzyfinder by the following commands: +" +" :FuzzyFinderBuffer - launchs buffer-mode Fuzzyfinder. +" :FuzzyFinderFile - launchs file-mode Fuzzyfinder. +" :FuzzyFinderDir - launchs directory-mode Fuzzyfinder. +" :FuzzyFinderMruFile - launchs MRU-file-mode Fuzzyfinder. +" :FuzzyFinderMruCmd - launchs MRU-command-mode Fuzzyfinder. +" :FuzzyFinderFavFile - launchs favorite-file-mode Fuzzyfinder. +" :FuzzyFinderTag - launchs tag-mode Fuzzyfinder. +" :FuzzyFinderTaggedFile - launchs tagged-file-mode Fuzzyfinder. +" +" It is recommended to map these commands. These commands can take initial +" text as a command argument. The text will be entered after Fuzzyfinder +" launched. If a command was executed with a ! modifier (e.g. +" :FuzzyFinderTag!), it enables the partial matching instead of the fuzzy +" matching. +" +" +" In Fuzzyfinder: +" The entered pattern is converted to the fuzzy pattern and buffers/files +" which match the pattern is shown in a completion menu. +" +" A completion menu is shown when you type at the end of the line and the +" length of entered pattern is more than setting value. By default, it is +" shown at the beginning. +" +" If too many items (200, by default) were matched, the completion is +" aborted to reduce nonresponse. +" +" If an item were matched with entered pattern exactly, it is shown first. +" The item whose file name has longer prefix matching is placed upper. +" Also, an item which matched more sequentially is placed upper. The item +" whose index were matched with a number suffixed with entered pattern is +" placed lower. the first item in the completion menu will be selected +" automatically. +" +" You can open a selected item in various ways: +" <CR> - opens in a previous window. +" <C-j> - opens in a split window. +" <C-k> - opens in a vertical-split window. +" <C-]> - opens in a new tab page. +" In MRU-command mode, <CR> executes a selected command and others just +" put it into a command-line. These key mappings are customizable. +" +" To cancel and return to previous window, leave Insert mode. +" +" To Switch the mode without leaving Insert mode, use <C-l> or <C-o>. +" This key mapping is customizable. +" +" If you want to temporarily change whether or not to ignore case, use +" <C-t>. This key mapping is customizable. +" +" To Hide The Completion Temporarily Menu In Fuzzyfinder: +" You can close it by <C-e> and reopen it by <C-x><C-u>. +" +" About Highlighting: +" Fuzzyfinder highlights the buffer with "Error" group when the completion +" item was not found or the completion process was aborted. +" +" About Alternative Approach For Tag Jump: +" Following mappings are replacements for :tag and <C-]>: +" +" nnoremap <silent> <C-f><C-t> :FuzzyFinderTag!<CR> +" nnoremap <silent> <C-]> :FuzzyFinderTag! <C-r>=expand('<cword>')<CR><CR> +" +" In the tag mode, it is recommended to use partial matching instead of +" fuzzy matching. +" +" About Tagged File Mode: +" The files which are included in the current tags are the ones which are +" related to the current working environment. So this mode is a pseudo +" project mode. +" +" About Usage Of Command Argument: +" As an example, if you want to launch file-mode Fuzzyfinder with the full +" path of current directory, map like below: +" +" nnoremap <C-p> :FuzzyFinderFile <C-r>=fnamemodify(getcwd(), ':p')<CR><CR> +" +" Instead, if you want the directory of current buffer and not current +" directory: +" +" nnoremap <C-p> :FuzzyFinderFile <C-r>=expand('%:~:.')[:-1-len(expand('%:~:.:t'))]<CR><CR> +" +" About Abbreviations And Multiple Search: +" You can use abbreviations and multiple search in each mode. For example, +" set as below: +" +" let g:FuzzyFinderOptions.Base.abbrev_map = { +" \ "^WORK" : [ +" \ "~/project/**/src/", +" \ ".vim/plugin/", +" \ ], +" \ } +" +" And type "WORKtxt" in file-mode Fuzzyfinder, then it searches by +" following patterns: +" +" "~/project/**/src/*t*x*t*" +" ".vim/plugin/*t*x*t*" +" +" Adding Favorite Files: +" You can add a favorite file by the following commands: +" +" :FuzzyFinderAddFavFile {filename} +" +" If you do not specify the filename, current file name is used. +" +" About Information File: +" Fuzzyfinder writes information of the MRU, favorite, etc to the file by +" default (~/.vimfuzzyfinder). + +" :FuzzyFinderEditInfo command is helpful in editing your information +" file. This command reads the information file in new unnamed buffer. +" Write the buffer and the information file will be updated. +" +" About Cache: +" Once a cache was created, It is not updated automatically to improve +" response by default. To update it, use :FuzzyFinderRemoveCache command. +" +" About Migemo: +" Migemo is a search method for Japanese language. +" +"----------------------------------------------------------------------------- +" Options: +" You can set options via g:FuzzyFinderOptions which is a dictionary. See +" the folded section named "GLOBAL OPTIONS:" for details. To easily set +" options for customization, put necessary entries from GLOBAL OPTIONS into +" your vimrc file and edit those values. +" +"----------------------------------------------------------------------------- +" Setting Example: +" let g:FuzzyFinderOptions = { 'Base':{}, 'Buffer':{}, 'File':{}, 'Dir':{}, 'MruFile':{}, 'MruCmd':{}, 'FavFile':{}, 'Tag':{}, 'TaggedFile':{}} +" let g:FuzzyFinderOptions.Base.ignore_case = 1 +" let g:FuzzyFinderOptions.Base.abbrev_map = { +" \ '\C^VR' : [ +" \ '$VIMRUNTIME/**', +" \ '~/.vim/**', +" \ '$VIM/.vim/**', +" \ '$VIM/vimfiles/**', +" \ ], +" \ } +" let g:FuzzyFinderOptions.MruFile.max_item = 200 +" let g:FuzzyFinderOptions.MruCmd.max_item = 200 +" nnoremap <silent> <C-n> :FuzzyFinderBuffer<CR> +" nnoremap <silent> <C-m> :FuzzyFinderFile <C-r>=expand('%:~:.')[:-1-len(expand('%:~:.:t'))]<CR><CR> +" nnoremap <silent> <C-j> :FuzzyFinderMruFile<CR> +" nnoremap <silent> <C-k> :FuzzyFinderMruCmd<CR> +" nnoremap <silent> <C-p> :FuzzyFinderDir <C-r>=expand('%:p:~')[:-1-len(expand('%:p:~:t'))]<CR><CR> +" nnoremap <silent> <C-f><C-d> :FuzzyFinderDir<CR> +" nnoremap <silent> <C-f><C-f> :FuzzyFinderFavFile<CR> +" nnoremap <silent> <C-f><C-t> :FuzzyFinderTag!<CR> +" nnoremap <silent> <C-f><C-g> :FuzzyFinderTaggedFile<CR> +" noremap <silent> g] :FuzzyFinderTag! <C-r>=expand('<cword>')<CR><CR> +" nnoremap <silent> <C-f>F :FuzzyFinderAddFavFile<CR> +" nnoremap <silent> <C-f><C-e> :FuzzyFinderEditInfo<CR> +" +"----------------------------------------------------------------------------- +" Special Thanks: +" Vincent Wang +" Ingo Karkat +" Nikolay Golubev +" Brian Doyle +" id:secondlife +" Matt Tolton +" +"----------------------------------------------------------------------------- +" ChangeLog: +" 2.13: +" - Fixed a bug that a directory disappeared when a file in that directroy +" was being opened in File/Mru-File mode. +" +" 2.12: +" - Changed to be able to show completion items in the order of recently +" used in Buffer mode. +" - Added g:FuzzyFinderOptions.Buffer.mru_order option. +" +" 2.11: +" - Changed that a dot sequence of entered pattern is expanded to parent +" directroies in File/Dir mode. +" E.g.: "foo/...bar" -> "foo/../../bar" +" - Fixed a bug that a prompt string was excessively inserted. +" +" 2.10: +" - Changed not to show a current buffer in a completion menu. +" - Fixed a bug that a filename to open was not been escaped. +" - Added 'prompt' option. +" - Added 'prompt_highlight' option. +" - Removed g:FuzzyFinderOptions.MruFile.no_special_buffer option. +" +" 2.9: +" - Enhanced <BS> behavior in Fuzzyfinder and added 'smart_bs' option. +" - Fixed a bug that entered pattern was not been escaped. +" - Fixed not to insert "zv" with "c/pattern<CR>" command in Normal mode. +" - Avoid the slow down problem caused by filereadable() check for the MRU +" information in BufEnter/BufWritePost. +" +" 2.8.1: +" - Fixed a bug caused by the non-escaped buffer name "[Fuzzyfinder]". +" - Fixed a command to open in a new tab page in Buffer mode. +" 2.8: +" - Added 'trim_length' option. +" - Added 'switch_order' option. +" - Fixed a bug that entered command did not become the newest in the +" history. +" - Fixed a bug that folds could not open with <CR> in a command-line when +" searching. +" - Removed 'excluded_indicator' option. Now a completion list in Buffer +" mode is the same as a result of :buffers. +" +" 2.7: +" - Changed to find an item whose index is matched with the number +" suffixed with entered pattern. +" - Fixed the cache bug after changing current directroy in File mode. +" +" 2.6.2: +" - Fixed not to miss changes in options when updates the MRU information. +" +" 2.6.1: +" - Fixed a bug related to floating-point support. +" - Added support for GetLatestVimScripts. +" +" 2.6: +" - Revived MRU-command mode. The problem with a command-line abbreviation +" was solved. +" - Changed the specification of the information file. +" - Added :FuzzyFinderEditInfo command. + +" 2.5.1: +" - Fixed to be able to match "foo/./bar" by "foo/**/bar" in File mode. +" - Fixed to be able to open a space-containing file in File mode. +" - Fixed to honor the current working directory properly in File mode. +" +" 2.5: +" - Fixed the bug that a wrong initial text is entered after switching to a +" next mode. +" - Fixed the bug that it does not return to previous window after leaving +" Fuzzyfinder one. +" +" 2.4: +" - Fixed the bug that Fuzzyfinder fails to open a file caused by auto-cd +" plugin/script. +" +" 2.3: +" - Added a key mapping to open items in a new tab page and +" g:FuzzyFinderOptions.Base.key_open_tab opton. +" - Changed to show Fuzzyfinder window above last window even if +" 'splitbelow' was set. +" - Changed to set nocursorline and nocursorcolumn in Fuzzyfinder. +" - Fixed not to push up a buffer number unlimitedly. +" +" 2.2: +" - Added new feature, which is the partial matching. +" - Fixed the bug that an error occurs when "'" was entered. +" +" 2.1: +" - Restructured the option system AGAIN. Sorry :p +" - Changed to inherit a typed text when switching a mode without leaving +" Insert mode. +" - Changed commands which launch explorers to be able to take a argument +" for initial text. +" - Changed to complete file names by relative path and not full path in +" the buffer/mru-file/tagged-file mode. +" - Changed to highlight a typed text when the completion item was not +" found or the completion process was aborted. +" - Changed to create caches for each tag file and not working directory +" in the tag/tagged-file mode. +" - Fixed the bug that the buffer mode couldn't open a unnamed buffer. +" - Added 'matching_limit' option. +" - Removed 'max_match' option. Use 'matching_limit' option instead. +" - Removed 'initial_text' option. Use command argument instead. +" - Removed the MRU-command mode. +" +" 2.0: +" - Added the tag mode. +" - Added the tagged-file mode. +" - Added :FuzzyFinderRemoveCache command. +" - Restructured the option system. many options are changed names or +" default values of some options. +" - Changed to hold and reuse caches of completion lists by default. +" - Changed to set filetype 'fuzzyfinder'. +" - Disabled the MRU-command mode by default because there are problems. +" - Removed FuzzyFinderAddMode command. +" +" 1.5: +" - Added the directory mode. +" - Fixed the bug that it caused an error when switch a mode in Insert +" mode. +" - Changed g:FuzzyFinder_KeySwitchMode type to a list. +" +" 1.4: +" - Changed the specification of the information file. +" - Added the MRU-commands mode. +" - Renamed :FuzzyFinderAddFavorite command to :FuzzyFinderAddFavFile. +" - Renamed g:FuzzyFinder_MruModeVars option to +" g:FuzzyFinder_MruFileModeVars. +" - Renamed g:FuzzyFinder_FavoriteModeVars option to +" g:FuzzyFinder_FavFileModeVars. +" - Changed to show registered time of each item in MRU/favorite mode. +" - Added 'timeFormat' option for MRU/favorite modes. +" +" 1.3: +" - Fixed a handling of multi-byte characters. +" +" 1.2: +" - Added support for Migemo. (Migemo is Japanese search method.) +" +" 1.1: +" - Added the favorite mode. +" - Added new features, which are abbreviations and multiple search. +" - Added 'abbrevMap' option for each mode. +" - Added g:FuzzyFinder_MruModeVars['ignoreSpecialBuffers'] option. +" - Fixed the bug that it did not work correctly when a user have mapped +" <C-p> or <Down>. +" +" 1.0: +" - Added the MRU mode. +" - Added commands to add and use original mode. +" - Improved the sorting algorithm for completion items. +" - Added 'initialInput' option to automatically insert a text at the +" beginning of a mode. +" - Changed that 'excludedPath' option works for the entire path. +" - Renamed some options. +" - Changed default values of some options. +" - Packed the mode-specific options to dictionaries. +" - Removed some options. +" +" 0.6: +" - Fixed some bugs. + +" 0.5: +" - Improved response by aborting processing too many items. +" - Changed to be able to open a buffer/file not only in previous window +" but also in new window. +" - Fixed a bug that recursive searching with '**' does not work. +" - Added g:FuzzyFinder_CompletionItemLimit option. +" - Added g:FuzzyFinder_KeyOpen option. +" +" 0.4: +" - Improved response of the input. +" - Improved the sorting algorithm for completion items. It is based on +" the matching level. 1st is perfect matching, 2nd is prefix matching, +" and 3rd is fuzzy matching. +" - Added g:FuzzyFinder_ExcludePattern option. +" - Removed g:FuzzyFinder_WildIgnore option. +" - Removed g:FuzzyFinder_EchoPattern option. +" - Removed g:FuzzyFinder_PathSeparator option. +" - Changed the default value of g:FuzzyFinder_MinLengthFile from 1 to 0. +" +" 0.3: +" - Added g:FuzzyFinder_IgnoreCase option. +" - Added g:FuzzyFinder_KeyToggleIgnoreCase option. +" - Added g:FuzzyFinder_EchoPattern option. +" - Changed the open command in a buffer mode from ":edit" to ":buffer" to +" avoid being reset cursor position. +" - Changed the default value of g:FuzzyFinder_KeyToggleMode from +" <C-Space> to <F12> because <C-Space> does not work on some CUI +" environments. +" - Changed to avoid being loaded by Vim before 7.0. +" - Fixed a bug with making a fuzzy pattern which has '\'. +" +" 0.2: +" - A bug it does not work on Linux is fixed. +" +" 0.1: +" - First release. +" +" }}}1 +"============================================================================= +" INCLUDE GUARD: {{{1 +if exists('loaded_fuzzyfinder') || v:version < 701 + finish +endif +let loaded_fuzzyfinder = 1 + +" }}}1 +"============================================================================= +" FUNCTION: {{{1 +"----------------------------------------------------------------------------- +" LIST FUNCTIONS: + +function! s:Unique(in) + let sorted = sort(a:in) + if len(sorted) < 2 + return sorted + endif + let last = remove(sorted, 0) + let result = [last] + for item in sorted + if item != last + call add(result, item) + let last = item + endif + endfor + return result +endfunction + +" [ [0], [1,2], [3] ] -> [ 0, 1, 2, 3 ] +function! s:Concat(in) + let result = [] + for l in a:in + let result += l + endfor + return result +endfunction + +" [ [ 0, 1 ], [ 2, 3, 4 ], ] -> [ [0,2], [0,3], [0,4], [1,2], [1,3], [1,4] ] +function! s:CartesianProduct(lists) + if empty(a:lists) + return [] + endif + "let result = map((a:lists[0]), '[v:val]') + let result = [ [] ] + for l in a:lists + let temp = [] + for r in result + let temp += map(copy(l), 'add(copy(r), v:val)') + endfor + let result = temp + endfor + return result +endfunction + +" copy + filter + limit +function! s:FilterEx(in, expr, limit) + if a:limit <= 0 + return filter(copy(a:in), a:expr) + endif + let result = [] + let stride = a:limit * 3 / 2 " x1.5 + for i in range(0, len(a:in) - 1, stride) + let result += filter(a:in[i : i + stride - 1], a:expr) + if len(result) >= a:limit + return remove(result, 0, a:limit - 1) + endif + endfor + return result +endfunction + +" +function! s:FilterMatching(entries, key, pattern, index, limit) + return s:FilterEx(a:entries, 'v:val[''' . a:key . '''] =~ ' . string(a:pattern) . ' || v:val.index == ' . a:index, a:limit) +endfunction + +function! s:ExtendIndexToEach(in, offset) + for i in range(len(a:in)) + let a:in[i].index = i + a:offset + endfor + return a:in +endfunction + +function! s:UpdateMruList(mrulist, new_item, key, max_item, excluded) + let result = copy(a:mrulist) + let result = filter(result,'v:val[a:key] != a:new_item[a:key]') + let result = insert(result, a:new_item) + let result = filter(result, 'v:val[a:key] !~ a:excluded') + return result[0 : a:max_item - 1] +endfunction + +"----------------------------------------------------------------------------- +" STRING FUNCTIONS: + +" trims a:str and add a:mark if a length of a:str is more than a:len +function! s:TrimLast(str, len) + if a:len <= 0 || len(a:str) <= a:len + return a:str + endif + return a:str[:(a:len - len(s:ABBR_TRIM_MARK) - 1)] . s:ABBR_TRIM_MARK +endfunction + +" takes suffix numer. if no digits, returns -1 +function! s:SuffixNumber(str) + let s = matchstr(a:str, '\d\+$') + return (len(s) ? str2nr(s) : -1) +endfunction + +function! s:ConvertWildcardToRegexp(expr) + let re = escape(a:expr, '\') + for [pat, sub] in [ [ '*', '\\.\\*' ], [ '?', '\\.' ], [ '[', '\\[' ], ] + let re = substitute(re, pat, sub, 'g') + endfor + return '\V' . re +endfunction + +" "foo/bar/hoge" -> { head: "foo/bar/", tail: "hoge" } +function! s:SplitPath(path) + let dir = matchstr(a:path, '^.*[/\\]') + return { + \ 'head' : dir, + \ 'tail' : a:path[strlen(dir):] + \ } +endfunction + +function! s:EscapeFilename(fn) + return escape(a:fn, " \t\n*?[{`$%#'\"|!<") +endfunction + +" "foo/.../bar/...hoge" -> "foo/.../bar/../../hoge" +function! s:ExpandTailDotSequenceToParentDir(base) + return substitute(a:base, '^\(.*[/\\]\)\?\zs\.\(\.\+\)\ze[^/\\]*$', + \ '\=repeat(".." . s:PATH_SEPARATOR, len(submatch(2)))', '') +endfunction + +"----------------------------------------------------------------------------- +" FUNCTIONS FOR COMPLETION ITEM: + +function! s:FormatCompletionItem(expr, number, abbr, trim_len, time, base_pattern, evals_path_tail) + if a:evals_path_tail + let rate = s:EvaluateMatchingRate(s:SplitPath(matchstr(a:expr, '^.*[^/\\]')).tail, + \ s:SplitPath(a:base_pattern).tail) + else + let rate = s:EvaluateMatchingRate(a:expr, a:base_pattern) + endif + return { + \ 'word' : a:expr, + \ 'abbr' : s:TrimLast((a:number >= 0 ? printf('%2d: ', a:number) : '') . a:abbr, a:trim_len), + \ 'menu' : printf('%s[%s]', (len(a:time) ? a:time . ' ' : ''), s:MakeRateStar(rate, 5)), + \ 'ranks' : [-rate, (a:number >= 0 ? a:number : a:expr)] + \ } +endfunction + +function! s:EvaluateMatchingRate(expr, pattern) + if a:expr == a:pattern + return s:MATCHING_RATE_BASE + endif + let rate = 0 + let rate_increment = (s:MATCHING_RATE_BASE * 9) / (len(a:pattern) * 10) " zero divide ok + let matched = 1 + let i_pattern = 0 + for i_expr in range(len(a:expr)) + if a:expr[i_expr] == a:pattern[i_pattern] + let rate += rate_increment + let matched = 1 + let i_pattern += 1 + if i_pattern >= len(a:pattern) + break + endif + elseif matched + let rate_increment = rate_increment / 2 + let matched = 0 + endif + endfor + return rate +endfunction + +function! s:MakeRateStar(rate, base) + let len = (a:base * a:rate) / s:MATCHING_RATE_BASE + return repeat('*', len) . repeat('.', a:base - len) +endfunction + +"----------------------------------------------------------------------------- +" MISC FUNCTIONS: + +function! s:IsAvailableMode(mode) + return exists('a:mode.mode_available') && a:mode.mode_available +endfunction + +function! s:GetAvailableModes() + return filter(values(g:FuzzyFinderMode), 's:IsAvailableMode(v:val)') +endfunction + +function! s:GetSortedAvailableModes() + let modes = filter(items(g:FuzzyFinderMode), 's:IsAvailableMode(v:val[1])') + let modes = map(modes, 'extend(v:val[1], { "ranks" : [v:val[1].switch_order, v:val[0]] })') + return sort(modes, 's:CompareRanks') +endfunction + +function! s:GetSidPrefix() + return matchstr(expand('<sfile>'), '<SNR>\d\+_') +endfunction + +function! s:OnCmdCR() + for m in s:GetAvailableModes() + call m.extend_options() + call m.on_command_pre(getcmdtype() . getcmdline()) + endfor + " lets last entry become the newest in the history + if getcmdtype() =~ '[:/=@]' + call histadd(getcmdtype(), getcmdline()) + endif + + " this is not mapped again (:help recursive_mapping) + return "\<CR>" +endfunction + +function! s:ExpandAbbrevMap(base, abbrev_map) + let result = [a:base] + + " expand + for [pattern, sub_list] in items(a:abbrev_map) + let exprs = result + let result = [] + for expr in exprs + let result += map(copy(sub_list), 'substitute(expr, pattern, v:val, "g")') + endfor + endfor + + return s:Unique(result) +endfunction + +" "**" is expanded to ["**", "."]. E.g.: "foo/**/bar" -> [ "foo/./bar", "foo/**/bar" ] +function! s:ExpandEx(dir) + if a:dir !~ '\S' + return [''] + endif + + " [ ["foo/"], ["**/", "./" ], ["bar/"] ] + let lists = [] + for i in split(a:dir, '[/\\]\zs') + let m = matchlist(i, '^\*\{2,}\([/\\]*\)$') + call add(lists, (empty(m) ? [i] : [i, '.' . m[1]])) + endfor + + " expand wlidcards + return split(join(map(s:CartesianProduct(lists), 'expand(join(v:val, ""))'), "\n"), "\n") +endfunction + +function! s:EnumExpandedDirsEntries(dir, excluded) + let dirs = s:ExpandEx(a:dir) + let entries = s:Concat(map(copy(dirs), 'split(glob(v:val . ".*"), "\n") + ' . + \ 'split(glob(v:val . "*" ), "\n")')) + if len(dirs) <= 1 + call map(entries, 'extend(s:SplitPath(v:val), { "suffix" : (isdirectory(v:val) ? s:PATH_SEPARATOR : ""), "head" : a:dir })') + else + call map(entries, 'extend(s:SplitPath(v:val), { "suffix" : (isdirectory(v:val) ? s:PATH_SEPARATOR : "") })') + endif + if len(a:excluded) + call filter(entries, '(v:val.head . v:val.tail . v:val.suffix) !~ a:excluded') + endif + return entries +endfunction + +function! s:GetTagList(tagfile) + return map(readfile(a:tagfile), 'matchstr(v:val, ''^[^!\t][^\t]*'')') +endfunction + +function! s:GetTaggedFileList(tagfile) + execute 'cd ' . fnamemodify(a:tagfile, ':h') + let result = map(readfile(a:tagfile), 'fnamemodify(matchstr(v:val, ''^[^!\t][^\t]*\t\zs[^\t]\+''), '':p:~'')') + cd - + return result +endfunction + +function! s:HighlightPrompt(prompt, highlight) + syntax clear + execute printf('syntax match %s /^\V%s/', a:highlight, escape(a:prompt, '\')) +endfunction + +function! s:HighlightError() + syntax clear + syntax match Error /^.*$/ +endfunction + +function! s:CompareTimeDescending(i1, i2) + return a:i1.time == a:i2.time ? 0 : a:i1.time > a:i2.time ? -1 : +1 +endfunction + +function! s:CompareRanks(i1, i2) + if exists('a:i1.ranks') && exists('a:i2.ranks') + for i in range(min([len(a:i1.ranks), len(a:i2.ranks)])) + if a:i1.ranks[i] > a:i2.ranks[i] + return +1 + elseif a:i1.ranks[i] < a:i2.ranks[i] + return -1 + endif + endfor + endif + return 0 +endfunction + +function! s:GetCurrentTagFiles() + return sort(filter(map(tagfiles(), 'fnamemodify(v:val, '':p'')'), 'filereadable(v:val)')) +endfunction + +" }}}1 +"============================================================================= +" OBJECT: {{{1 +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode = { 'Base' : {} } + +function! g:FuzzyFinderMode.Base.launch(initial_text, partial_matching, prev_bufnr, tag_files) + " initializes this object + call self.extend_options() + let self.partial_matching = a:partial_matching + let self.prev_bufnr = a:prev_bufnr + let self.tag_files = a:tag_files " to get local value of current buffer + let self.last_col = -1 + call s:InfoFileManager.load() + if !s:IsAvailableMode(self) + echo 'This mode is not available: ' . self.to_str() + return + endif + + call s:WindowManager.activate(self.make_complete_func('CompleteFunc')) + call s:OptionManager.set('completeopt', 'menuone') + call s:OptionManager.set('ignorecase', self.ignore_case) + + " local autocommands + augroup FuzzyfinderLocal + autocmd! + execute 'autocmd CursorMovedI <buffer> call ' . self.to_str('on_cursor_moved_i()') + execute 'autocmd InsertLeave <buffer> nested call ' . self.to_str('on_insert_leave()' ) + augroup END + + " local mapping + for [lhs, rhs] in [ + \ [ self.key_open , self.to_str('on_cr(0, 0)' ) ], + \ [ self.key_open_split , self.to_str('on_cr(1, 0)' ) ], + \ [ self.key_open_vsplit, self.to_str('on_cr(2, 0)' ) ], + \ [ self.key_open_tab , self.to_str('on_cr(3, 0)' ) ], + \ [ '<BS>' , self.to_str('on_bs()' ) ], + \ [ '<C-h>' , self.to_str('on_bs()' ) ], + \ [ self.key_next_mode , self.to_str('on_switch_mode(+1)' ) ], + \ [ self.key_prev_mode , self.to_str('on_switch_mode(-1)' ) ], + \ [ self.key_ignore_case, self.to_str('on_switch_ignore_case()') ], + \ ] + " hacks to be able to use feedkeys(). + execute printf('inoremap <buffer> <silent> %s <C-r>=%s ? "" : ""<CR>', lhs, rhs) + endfor + + call self.on_mode_enter() + + " Starts Insert mode and makes CursorMovedI event now. Command prompt is + " needed to forces a completion menu to update every typing. + call setline(1, self.prompt . a:initial_text) + call feedkeys("A", 'n') " startinsert! does not work in InsertLeave handler +endfunction + +function! g:FuzzyFinderMode.Base.on_cursor_moved_i() + let ln = getline('.') + let cl = col('.') + if !self.exists_prompt(ln) + " if command prompt is removed + "call setline('.', self.prompt . ln) + call setline('.', self.restore_prompt(ln)) + call feedkeys(repeat("\<Right>", len(getline('.')) - len(ln)), 'n') + elseif cl <= len(self.prompt) + " if the cursor is moved before command prompt + call feedkeys(repeat("\<Right>", len(self.prompt) - cl + 1), 'n') + elseif cl > strlen(ln) && cl != self.last_col + " if the cursor is placed on the end of the line and has been actually moved. + let self.last_col = cl + call feedkeys("\<C-x>\<C-u>", 'n') + endif +endfunction + +function! g:FuzzyFinderMode.Base.on_insert_leave() + let text = getline('.') + call self.on_mode_leave() + call self.empty_cache_if_existed(0) + call s:OptionManager.restore_all() + call s:WindowManager.deactivate() + + " switchs to next mode, or finishes fuzzyfinder. + if exists('s:reserved_switch_mode') + let m = self.next_mode(s:reserved_switch_mode < 0) + call m.launch(self.remove_prompt(text), self.partial_matching, self.prev_bufnr, self.tag_files) + unlet s:reserved_switch_mode + else + if exists('s:reserved_command') + call feedkeys(self.on_open(s:reserved_command[0], s:reserved_command[1]), 'n') + unlet s:reserved_command + endif + endif +endfunction + +function! g:FuzzyFinderMode.Base.on_buf_enter() +endfunction + +function! g:FuzzyFinderMode.Base.on_buf_write_post() +endfunction + +function! g:FuzzyFinderMode.Base.on_command_pre(cmd) +endfunction + +function! g:FuzzyFinderMode.Base.on_cr(index, check_dir) + if pumvisible() + call feedkeys(printf("\<C-y>\<C-r>=%s(%d, 1) ? '' : ''\<CR>", self.to_str('on_cr'), a:index), 'n') + elseif !a:check_dir || getline('.') !~ '[/\\]$' + let s:reserved_command = [self.remove_prompt(getline('.')), a:index] + call feedkeys("\<Esc>", 'n') + endif +endfunction + +function! g:FuzzyFinderMode.Base.on_bs() + let bs_count = 1 + if self.smart_bs && col('.') > 2 && getline('.')[col('.') - 2] =~ '[/\\]' + let bs_count = len(matchstr(getline('.')[:col('.') - 3], '[^/\\]*$')) + 1 + endif + call feedkeys((pumvisible() ? "\<C-e>" : "") . repeat("\<BS>", bs_count), 'n') +endfunction + +function! g:FuzzyFinderMode.Base.on_mode_enter() +endfunction + +function! g:FuzzyFinderMode.Base.on_mode_leave() +endfunction + +function! g:FuzzyFinderMode.Base.on_open(expr, mode) + return [ + \ ':edit ', + \ ':split ', + \ ':vsplit ', + \ ':tabedit ', + \ ][a:mode] . s:EscapeFilename(a:expr) . "\<CR>" +endfunction + +function! g:FuzzyFinderMode.Base.on_switch_mode(next_prev) + let s:reserved_switch_mode = a:next_prev + call feedkeys("\<Esc>", 'n') +endfunction + +function! g:FuzzyFinderMode.Base.on_switch_ignore_case() + let &ignorecase = !&ignorecase + echo "ignorecase = " . &ignorecase + let self.last_col = -1 + call self.on_cursor_moved_i() +endfunction + +" export string list +function! g:FuzzyFinderMode.Base.serialize_info() + let header = self.to_key() . "\t" + return map(copy(self.info), 'header . string(v:val)') +endfunction + +" import related items from string list +function! g:FuzzyFinderMode.Base.deserialize_info(lines) + let header = self.to_key() . "\t" + let self.info = map(filter(copy(a:lines), 'v:val[: len(header) - 1] ==# header'), + \ 'eval(v:val[len(header) :])') +endfunction + +function! g:FuzzyFinderMode.Base.complete(findstart, base) + if a:findstart + return 0 + elseif !self.exists_prompt(a:base) || len(self.remove_prompt(a:base)) < self.min_length + return [] + endif + call s:HighlightPrompt(self.prompt, self.prompt_highlight) + " FIXME: ExpandAbbrevMap duplicates index + let result = [] + for expanded_base in s:ExpandAbbrevMap(self.remove_prompt(a:base), self.abbrev_map) + let result += self.on_complete(expanded_base) + endfor + call sort(result, 's:CompareRanks') + if empty(result) + call s:HighlightError() + else + call feedkeys("\<C-p>\<Down>", 'n') + endif + return result +endfunction + +" This function is set to 'completefunc' which doesn't accept dictionary-functions. +function! g:FuzzyFinderMode.Base.make_complete_func(name) + execute printf("function! s:%s(findstart, base)\n" . + \ " return %s.complete(a:findstart, a:base)\n" . + \ "endfunction", a:name, self.to_str()) + return s:GetSidPrefix() . a:name +endfunction + +" fuzzy : 'str' -> {'base':'str', 'wi':'*s*t*r*', 're':'\V\.\*s\.\*t\.\*r\.\*'} +" partial: 'str' -> {'base':'str', 'wi':'*str*', 're':'\V\.\*str\.\*'} +function! g:FuzzyFinderMode.Base.make_pattern(base) + if self.partial_matching + let wi = (a:base !~ '^[*?]' ? '*' : '') . a:base . + \ (a:base =~ '[^*?]$' ? '*' : '') + let re = s:ConvertWildcardToRegexp(wi) + return { 'base': a:base, 'wi':wi, 're': re } + else + let wi = '' + for char in split(a:base, '\zs') + if wi !~ '[*?]$' && char !~ '[*?]' + let wi .= '*'. char + else + let wi .= char + endif + endfor + + if wi !~ '[*?]$' + let wi .= '*' + endif + + let re = s:ConvertWildcardToRegexp(wi) + + if self.migemo_support && a:base !~ '[^\x01-\x7e]' + let re .= '\|\m.*' . substitute(migemo(a:base), '\\_s\*', '.*', 'g') . '.*' + endif + + return { 'base': a:base, 'wi':wi, 're': re } + endif +endfunction + +" glob with caching-feature, etc. +function! g:FuzzyFinderMode.Base.glob_ex(dir, file, excluded, index, matching_limit) + let key = fnamemodify(a:dir, ':p') + call extend(self, { 'cache' : {} }, 'keep') + if !exists('self.cache[key]') + echo 'Caching file list...' + let self.cache[key] = s:EnumExpandedDirsEntries(key, a:excluded) + call s:ExtendIndexToEach(self.cache[key], 1) + endif + echo 'Filtering file list...' + "return map(s:FilterEx(self.cache[key], 'v:val.tail =~ ' . string(a:file), a:matching_limit), + return map(s:FilterMatching(self.cache[key], 'tail', a:file, a:index, a:matching_limit), + \ '{ "index" : v:val.index, "path" : (v:val.head == key ? a:dir : v:val.head) . v:val.tail . v:val.suffix }') +endfunction + +function! g:FuzzyFinderMode.Base.glob_dir_ex(dir, file, excluded, index, matching_limit) + let key = fnamemodify(a:dir, ':p') + call extend(self, { 'cache' : {} }, 'keep') + if !exists('self.cache[key]') + echo 'Caching file list...' + let self.cache[key] = filter(s:EnumExpandedDirsEntries(key, a:excluded), 'len(v:val.suffix)') + call insert(self.cache[key], { 'head' : key, 'tail' : '..', 'suffix' : s:PATH_SEPARATOR }) + call insert(self.cache[key], { 'head' : key, 'tail' : '.' , 'suffix' : '' }) + call s:ExtendIndexToEach(self.cache[key], 1) + endif + echo 'Filtering file list...' + "return map(s:FilterEx(self.cache[key], 'v:val.tail =~ ' . string(a:file), a:matching_limit), + return map(s:FilterMatching(self.cache[key], 'tail', a:file, a:index, a:matching_limit), + \ '{ "index" : v:val.index, "path" : (v:val.head == key ? a:dir : v:val.head) . v:val.tail . v:val.suffix }') +endfunction + +function! g:FuzzyFinderMode.Base.empty_cache_if_existed(force) + if exists('self.cache') && (a:force || !exists('self.lasting_cache') || !self.lasting_cache) + unlet self.cache + "let self.cache = (type(self.cache) == type({}) ? {} : + " \ type(self.cache) == type([]) ? [] : + " \ type(self.cache) == type('') ? '' : 0) + endif +endfunction + +function! g:FuzzyFinderMode.Base.to_key() + return filter(keys(g:FuzzyFinderMode), 'g:FuzzyFinderMode[v:val] is self')[0] +endfunction + +" returns 'g:FuzzyFinderMode.{key}{.argument}' +function! g:FuzzyFinderMode.Base.to_str(...) + return 'g:FuzzyFinderMode.' . self.to_key() . (a:0 > 0 ? '.' . a:1 : '') +endfunction + +" takes in g:FuzzyFinderOptions +function! g:FuzzyFinderMode.Base.extend_options() + let n = filter(keys(g:FuzzyFinderMode), 'g:FuzzyFinderMode[v:val] is self')[0] + call extend(self, g:FuzzyFinderOptions.Base, 'force') + call extend(self, g:FuzzyFinderOptions[self.to_key()], 'force') +endfunction + +function! g:FuzzyFinderMode.Base.next_mode(rev) + let modes = (a:rev ? s:GetSortedAvailableModes() : reverse(s:GetSortedAvailableModes())) + let m_last = modes[-1] + for m in modes + if m is self + break + endif + let m_last = m + endfor + return m_last + " vim crashed using map() +endfunction + +function! g:FuzzyFinderMode.Base.exists_prompt(in) + return strlen(a:in) >= strlen(self.prompt) && a:in[:strlen(self.prompt) -1] ==# self.prompt +endfunction + +function! g:FuzzyFinderMode.Base.remove_prompt(in) + return a:in[(self.exists_prompt(a:in) ? strlen(self.prompt) : 0):] +endfunction + +function! g:FuzzyFinderMode.Base.restore_prompt(in) + let i = 0 + while i < len(self.prompt) && i < len(a:in) && self.prompt[i] ==# a:in[i] + let i += 1 + endwhile + return self.prompt . a:in[i : ] +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.Buffer = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.Buffer.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = s:FilterMatching(self.cache, 'path', patterns.re, s:SuffixNumber(patterns.base), 0) + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, v:val.time, a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.Buffer.on_open(expr, mode) + " attempts to convert the path to the number for handling unnamed buffer + return printf([ + \ ':%sbuffer', + \ ':%ssbuffer', + \ ':vertical :%ssbuffer', + \ ':tab :%ssbuffer', + \ ][a:mode] . "\<CR>", filter(self.cache, 'v:val.path == a:expr')[0].buf_nr) +endfunction + +function! g:FuzzyFinderMode.Buffer.on_mode_enter() + let self.cache = map(filter(range(1, bufnr('$')), 'buflisted(v:val) && v:val != self.prev_bufnr'), + \ 'self.make_item(v:val)') + if self.mru_order + call s:ExtendIndexToEach(sort(self.cache, 's:CompareTimeDescending'), 1) + endif +endfunction + +function! g:FuzzyFinderMode.Buffer.on_buf_enter() + call self.update_buf_times() +endfunction + +function! g:FuzzyFinderMode.Buffer.on_buf_write_post() + call self.update_buf_times() +endfunction + +function! g:FuzzyFinderMode.Buffer.update_buf_times() + if !exists('self.buf_times') + let self.buf_times = {} + endif + let self.buf_times[bufnr('%')] = localtime() +endfunction + +function! g:FuzzyFinderMode.Buffer.make_item(nr) + return { + \ 'index' : a:nr, + \ 'buf_nr' : a:nr, + \ 'path' : empty(bufname(a:nr)) ? '[No Name]' : fnamemodify(bufname(a:nr), ':~:.'), + \ 'time' : (exists('self.buf_times[a:nr]') ? strftime(self.time_format, self.buf_times[a:nr]) : ''), + \ } +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.File = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.File.on_complete(base) + let base = s:ExpandTailDotSequenceToParentDir(a:base) + let patterns = map(s:SplitPath(base), 'self.make_pattern(v:val)') + let result = self.glob_ex(patterns.head.base, patterns.tail.re, self.excluded_path, s:SuffixNumber(patterns.tail.base), self.matching_limit) + let result = filter(result, 'bufnr("^" . v:val.path . "$") != self.prev_bufnr') + if len(result) >= self.matching_limit + call s:HighlightError() + endif + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, "", base, 1)') +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.Dir = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.Dir.on_complete(base) + let base = s:ExpandTailDotSequenceToParentDir(a:base) + let patterns = map(s:SplitPath(base), 'self.make_pattern(v:val)') + let result = self.glob_dir_ex(patterns.head.base, patterns.tail.re, self.excluded_path, s:SuffixNumber(patterns.tail.base), 0) + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, "", base, 1)') +endfunction + +function! g:FuzzyFinderMode.Dir.on_open(expr, mode) + return ':cd ' . escape(a:expr, ' ') . [ + \ "\<CR>", + \ "", + \ "", + \ "", + \ ][a:mode] +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.MruFile = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.MruFile.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = s:FilterMatching(self.cache, 'path', patterns.re, s:SuffixNumber(patterns.base), 0) + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, v:val.time, a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.MruFile.on_mode_enter() + let self.cache = copy(self.info) + let self.cache = filter(self.cache, 'bufnr("^" . v:val.path . "$") != self.prev_bufnr') + let self.cache = filter(self.cache, 'filereadable(v:val.path)') + let self.cache = map(self.cache, '{ "path" : fnamemodify(v:val.path, ":~:."), "time" : strftime(self.time_format, v:val.time) }') + let self.cache = s:ExtendIndexToEach(self.cache, 1) +endfunction + +function! g:FuzzyFinderMode.MruFile.on_buf_enter() + call self.update_info() +endfunction + +function! g:FuzzyFinderMode.MruFile.on_buf_write_post() + call self.update_info() +endfunction + +function! g:FuzzyFinderMode.MruFile.update_info() + "if !empty(&buftype) || !filereadable(expand('%')) + if !empty(&buftype) + return + endif + call s:InfoFileManager.load() + let self.info = s:UpdateMruList(self.info, { 'path' : expand('%:p'), 'time' : localtime() }, + \ 'path', self.max_item, self.excluded_path) + call s:InfoFileManager.save() +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.MruCmd = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.MruCmd.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = s:FilterMatching(self.cache, 'command', patterns.re, s:SuffixNumber(patterns.base), 0) + return map(result, 's:FormatCompletionItem(v:val.command, v:val.index, v:val.command, self.trim_length, v:val.time, a:base, 0)') +endfunction + +function! g:FuzzyFinderMode.MruCmd.on_open(expr, mode) + redraw + " use feedkeys to remap <CR> + return a:expr . [ + \ "\<C-r>=feedkeys(\"\\<CR>\", 'm')?'':''\<CR>", + \ "", + \ "", + \ "", + \ ][a:mode] +endfunction + +function! g:FuzzyFinderMode.MruCmd.on_mode_enter() + let self.cache = s:ExtendIndexToEach(map(copy(self.info), + \ '{ "command" : v:val.command, "time" : strftime(self.time_format, v:val.time) }'), 1) +endfunction + +function! g:FuzzyFinderMode.MruCmd.on_command_pre(cmd) + call self.update_info(a:cmd) +endfunction + +function! g:FuzzyFinderMode.MruCmd.update_info(cmd) + call s:InfoFileManager.load() + let self.info = s:UpdateMruList(self.info, { 'command' : a:cmd, 'time' : localtime() }, + \ 'command', self.max_item, self.excluded_command) + call s:InfoFileManager.save() +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.FavFile = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.FavFile.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = s:FilterMatching(self.cache, 'path', patterns.re, s:SuffixNumber(patterns.base), 0) + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, v:val.time, a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.FavFile.on_mode_enter() + let self.cache = copy(self.info) + let self.cache = filter(self.cache, 'bufnr("^" . v:val.path . "$") != self.prev_bufnr') + let self.cache = map(self.cache, '{ "path" : fnamemodify(v:val.path, ":~:."), "time" : strftime(self.time_format, v:val.time) }') + let self.cache = s:ExtendIndexToEach(self.cache, 1) +endfunction + +function! g:FuzzyFinderMode.FavFile.add(in_file, adds) + call s:InfoFileManager.load() + + let file = fnamemodify((empty(a:in_file) ? expand('%') : a:in_file), ':p:~') + + call filter(self.info, 'v:val.path != file') + if a:adds + call add(self.info, { 'path' : file, 'time' : localtime() }) + endif + + call s:InfoFileManager.save() +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.Tag = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.Tag.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = self.find_tag(patterns.re, self.matching_limit) + if len(result) >= self.matching_limit + call s:HighlightError() + endif + return map(result, 's:FormatCompletionItem(v:val, -1, v:val, self.trim_length, "", a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.Tag.on_open(expr, mode) + return [ + \ ':tjump ', + \ ':stjump ', + \ ':vertical :stjump ', + \ ':tab :stjump ', + \ ][a:mode] . a:expr . "\<CR>" +endfunction + +function! g:FuzzyFinderMode.Tag.find_tag(pattern, matching_limit) + if !len(self.tag_files) + return [] + endif + + let key = join(self.tag_files, "\n") + + " cache not created or tags file updated? + call extend(self, { 'cache' : {} }, 'keep') + if !exists('self.cache[key]') || max(map(copy(self.tag_files), 'getftime(v:val) >= self.cache[key].time')) + echo 'Caching tag list...' + let self.cache[key] = { + \ 'time' : localtime(), + \ 'data' : s:Unique(s:Concat(map(copy(self.tag_files), 's:GetTagList(v:val)'))), + \ } + endif + + echo 'Filtering tag list...' + return s:FilterEx(self.cache[key].data, 'v:val =~ ' . string(a:pattern), a:matching_limit) +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.TaggedFile = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.TaggedFile.on_complete(base) + let patterns = self.make_pattern(a:base) + echo 'Making tagged file list...' + let result = self.find_tagged_file(patterns.re, self.matching_limit) + if len(result) >= self.matching_limit + call s:HighlightError() + endif + return map(result, 's:FormatCompletionItem(v:val, -1, v:val, self.trim_length, "", a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.TaggedFile.find_tagged_file(pattern, matching_limit) + if !len(self.tag_files) + return [] + endif + + let key = join(self.tag_files, "\n") + + " cache not created or tags file updated? + call extend(self, { 'cache' : {} }, 'keep') + if !exists('self.cache[key]') || max(map(copy(self.tag_files), 'getftime(v:val) >= self.cache[key].time')) + echo 'Caching tagged-file list...' + let self.cache[key] = { + \ 'time' : localtime(), + \ 'data' : s:Unique(s:Concat(map(copy(self.tag_files), 's:GetTaggedFileList(v:val)'))), + \ } + endif + + echo 'Filtering tagged-file list...' + return s:FilterEx(map(self.cache[key].data, 'fnamemodify(v:val, '':.'')'), + \ 'v:val =~ ' . string(a:pattern), + \ a:matching_limit) + +endfunction + +"----------------------------------------------------------------------------- +" sets or restores temporary options +let s:OptionManager = { 'originals' : {} } + +function! s:OptionManager.set(name, value) + call extend(self.originals, { a:name : eval('&' . a:name) }, 'keep') + execute printf('let &%s = a:value', a:name) +endfunction + +function! s:OptionManager.restore_all() + for [name, value] in items(self.originals) + execute printf('let &%s = value', name) + endfor + let self.originals = {} +endfunction + +"----------------------------------------------------------------------------- +" manages buffer/window for fuzzyfinder +let s:WindowManager = { 'buf_nr' : -1 } + +function! s:WindowManager.activate(complete_func) + let self.prev_winnr = winnr() + let cwd = getcwd() + + if !bufexists(self.buf_nr) + leftabove 1new + file `='[Fuzzyfinder]'` + let self.buf_nr = bufnr('%') + elseif bufwinnr(self.buf_nr) == -1 + leftabove 1split + execute self.buf_nr . 'buffer' + delete _ + elseif bufwinnr(self.buf_nr) != bufwinnr('%') + execute bufwinnr(self.buf_nr) . 'wincmd w' + endif + + " countermeasure for auto-cd script + execute ':lcd ' . cwd + + setlocal filetype=fuzzyfinder + setlocal bufhidden=delete + setlocal buftype=nofile + setlocal noswapfile + setlocal nobuflisted + setlocal modifiable + setlocal nocursorline " for highlighting + setlocal nocursorcolumn " for highlighting + let &l:completefunc = a:complete_func + + redraw " for 'lazyredraw' + + " suspend autocomplpop.vim + if exists(':AutoComplPopLock') + :AutoComplPopLock + endif +endfunction + +function! s:WindowManager.deactivate() + " resume autocomplpop.vim + if exists(':AutoComplPopUnlock') + :AutoComplPopUnlock + endif + + close + execute self.prev_winnr . 'wincmd w' +endfunction + +"----------------------------------------------------------------------------- +let s:InfoFileManager = { 'originals' : {} } + +function! s:InfoFileManager.load() + for m in s:GetAvailableModes() + let m.info = [] + endfor + + try + let lines = readfile(expand(self.get_info_file())) + catch /.*/ + return + endtry + + " compatibility check + if !count(lines, self.get_info_version_line()) + call self.warn_old_info() + let g:FuzzyFinderOptions.Base.info_file = '' + return + endif + + for m in s:GetAvailableModes() + call m.deserialize_info(lines) + endfor +endfunction + +function! s:InfoFileManager.save() + let lines = [ self.get_info_version_line() ] + for m in s:GetAvailableModes() + let lines += m.serialize_info() + endfor + + try + call writefile(lines, expand(self.get_info_file())) + catch /.*/ + endtry +endfunction + +function! s:InfoFileManager.edit() + + new + file `='[FuzzyfinderInfo]'` + let self.bufnr = bufnr('%') + + setlocal filetype=vim + setlocal bufhidden=delete + setlocal buftype=acwrite + setlocal noswapfile + + augroup FuzzyfinderInfo + autocmd! + autocmd BufWriteCmd <buffer> call s:InfoFileManager.on_buf_write_cmd() + augroup END + + execute '0read ' . expand(self.get_info_file()) + setlocal nomodified + +endfunction + +function! s:InfoFileManager.on_buf_write_cmd() + for m in s:GetAvailableModes() + call m.deserialize_info(getline(1, '$')) + endfor + call self.save() + setlocal nomodified + execute printf('%dbdelete! ', self.bufnr) + echo "Information file updated" +endfunction + +function! s:InfoFileManager.get_info_version_line() + return "VERSION\t206" +endfunction + +function! s:InfoFileManager.get_info_file() + return g:FuzzyFinderOptions.Base.info_file +endfunction + +function! s:InfoFileManager.warn_old_info() + echohl WarningMsg + echo printf("==================================================\n" . + \ " Your Fuzzyfinder information file is no longer \n" . + \ " supported. Please remove \n" . + \ " %-48s\n" . + \ "==================================================\n" , + \ '"' . expand(self.get_info_file()) . '".') + echohl None +endfunction + +" }}}1 +"============================================================================= +" GLOBAL OPTIONS: {{{1 +" stores user-defined g:FuzzyFinderOptions ------------------------------ {{{2 +let user_options = (exists('g:FuzzyFinderOptions') ? g:FuzzyFinderOptions : {}) +" }}}2 + +" Initializes g:FuzzyFinderOptions. +let g:FuzzyFinderOptions = { 'Base':{}, 'Buffer':{}, 'File':{}, 'Dir':{}, 'MruFile':{}, 'MruCmd':{}, 'FavFile':{}, 'Tag':{}, 'TaggedFile':{}} +"----------------------------------------------------------------------------- +" [All Mode] This is mapped to select completion item or finish input and +" open a buffer/file in previous window. +let g:FuzzyFinderOptions.Base.key_open = '<CR>' +" [All Mode] This is mapped to select completion item or finish input and +" open a buffer/file in split new window +let g:FuzzyFinderOptions.Base.key_open_split = '<C-j>' +" [All Mode] This is mapped to select completion item or finish input and +" open a buffer/file in vertical-split new window. +let g:FuzzyFinderOptions.Base.key_open_vsplit = '<C-k>' +" [All Mode] This is mapped to select completion item or finish input and +" open a buffer/file in a new tab page. +let g:FuzzyFinderOptions.Base.key_open_tab = '<C-]>' +" [All Mode] This is mapped to switch to the next mode. +let g:FuzzyFinderOptions.Base.key_next_mode = '<C-l>' +" [All Mode] This is mapped to switch to the previous mode. +let g:FuzzyFinderOptions.Base.key_prev_mode = '<C-o>' +" [All Mode] This is mapped to temporarily switch whether or not to ignore +" case. +let g:FuzzyFinderOptions.Base.key_ignore_case = '<C-t>' +" [All Mode] This is the file name to write information of the MRU, etc. If +" "" was set, Fuzzyfinder does not write to the file. +let g:FuzzyFinderOptions.Base.info_file = '~/.vimfuzzyfinder' +" [All Mode] Fuzzyfinder does not start a completion if a length of entered +" text is less than this. +let g:FuzzyFinderOptions.Base.min_length = 0 +" [All Mode] This is a dictionary. Each value must be a list. All matchs of a +" key in entered text is expanded with the value. +let g:FuzzyFinderOptions.Base.abbrev_map = {} +" [All Mode] Fuzzyfinder ignores case in search patterns if non-zero is set. +let g:FuzzyFinderOptions.Base.ignore_case = 1 +" [All Mode] This is a string to format time string. See :help strftime() for +" details. +let g:FuzzyFinderOptions.Base.time_format = '(%x %H:%M:%S)' +" [All Mode] If a length of completion item is more than this, it is trimmed +" when shown in completion menu. +let g:FuzzyFinderOptions.Base.trim_length = 80 +" [All Mode] Fuzzyfinder does not remove caches of completion lists at the end +" of explorer to reuse at the next time if non-zero was set. +let g:FuzzyFinderOptions.Base.lasting_cache = 1 +" [All Mode] Fuzzyfinder uses Migemo if non-zero is set. +let g:FuzzyFinderOptions.Base.migemo_support = 0 +"----------------------------------------------------------------------------- +" [Buffer Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.Buffer.mode_available = 1 +" [Buffer Mode] The prompt string. +let g:FuzzyFinderOptions.Buffer.prompt = '>Buffer>' +" [Buffer Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.Buffer.prompt_highlight = 'Question' +" [Buffer Mode] Pressing <BS> after a path separator deletes one directory +" name if non-zero is set. +let g:FuzzyFinderOptions.Buffer.smart_bs = 1 +" [Buffer Mode] The completion items is sorted in the order of recently used +" if non-zero is set. +let g:FuzzyFinderOptions.Buffer.mru_order = 1 +" [Buffer Mode] This is used to sort modes for switching to the next/previous +" mode. +let g:FuzzyFinderOptions.Buffer.switch_order = 10 +"----------------------------------------------------------------------------- +" [File Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.File.mode_available = 1 +" [File Mode] The prompt string. +let g:FuzzyFinderOptions.File.prompt = '>File>' +" [File Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.File.prompt_highlight = 'Question' +" [File Mode] Pressing <BS> after a path separator deletes one directory name +" if non-zero is set. +let g:FuzzyFinderOptions.File.smart_bs = 1 +" [File Mode] This is used to sort modes for switching to the next/previous +" mode. +let g:FuzzyFinderOptions.File.switch_order = 20 +" [File Mode] The items matching this are excluded from the completion list. +let g:FuzzyFinderOptions.File.excluded_path = '\v\~$|\.o$|\.exe$|\.bak$|\.swp$|((^|[/\\])\.[/\\]$)' +" [File Mode] If a number of matched items was over this, the completion +" process is aborted. +let g:FuzzyFinderOptions.File.matching_limit = 200 +"----------------------------------------------------------------------------- +" [Directory Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.Dir.mode_available = 1 +" [Directory Mode] The prompt string. +let g:FuzzyFinderOptions.Dir.prompt = '>Dir>' +" [Directory Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.Dir.prompt_highlight = 'Question' +" [Directory Mode] Pressing <BS> after a path separator deletes one directory +" name if non-zero is set. +let g:FuzzyFinderOptions.Dir.smart_bs = 1 +" [Directory Mode] This is used to sort modes for switching to the +" next/previous mode. +let g:FuzzyFinderOptions.Dir.switch_order = 30 +" [Directory Mode] The items matching this are excluded from the completion +" list. +let g:FuzzyFinderOptions.Dir.excluded_path = '\v(^|[/\\])\.{1,2}[/\\]$' +"----------------------------------------------------------------------------- +" [Mru-File Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.MruFile.mode_available = 1 +" [Mru-File Mode] The prompt string. +let g:FuzzyFinderOptions.MruFile.prompt = '>MruFile>' +" [Mru-File Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.MruFile.prompt_highlight = 'Question' +" [Mru-File Mode] Pressing <BS> after a path separator deletes one directory +" name if non-zero is set. +let g:FuzzyFinderOptions.MruFile.smart_bs = 1 +" [Mru-File Mode] This is used to sort modes for switching to the +" next/previous mode. +let g:FuzzyFinderOptions.MruFile.switch_order = 40 +" [Mru-File Mode] The items matching this are excluded from the completion +" list. +let g:FuzzyFinderOptions.MruFile.excluded_path = '\v\~$|\.bak$|\.swp$' +" [Mru-File Mode] This is an upper limit of MRU items to be stored. +let g:FuzzyFinderOptions.MruFile.max_item = 99 +"----------------------------------------------------------------------------- +" [Mru-Cmd Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.MruCmd.mode_available = 1 +" [Mru-Cmd Mode] The prompt string. +let g:FuzzyFinderOptions.MruCmd.prompt = '>MruCmd>' +" [Mru-Cmd Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.MruCmd.prompt_highlight = 'Question' +" [Mru-Cmd Mode] Pressing <BS> after a path separator deletes one directory +" name if non-zero is set. +let g:FuzzyFinderOptions.MruCmd.smart_bs = 0 +" [Mru-Cmd Mode] This is used to sort modes for switching to the next/previous +" mode. +let g:FuzzyFinderOptions.MruCmd.switch_order = 50 +" [Mru-Cmd Mode] The items matching this are excluded from the completion +" list. +let g:FuzzyFinderOptions.MruCmd.excluded_command = '^$' +" [Mru-Cmd Mode] This is an upper limit of MRU items to be stored. +let g:FuzzyFinderOptions.MruCmd.max_item = 99 +"----------------------------------------------------------------------------- +" [Favorite-File Mode] This disables all functions of this mode if zero was +" set. +let g:FuzzyFinderOptions.FavFile.mode_available = 1 +" [Favorite-File Mode] The prompt string. +let g:FuzzyFinderOptions.FavFile.prompt = '>FavFile>' +" [Favorite-File Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.FavFile.prompt_highlight = 'Question' +" [Favorite-File Mode] Pressing <BS> after a path separator deletes one +" directory name if non-zero is set. +let g:FuzzyFinderOptions.FavFile.smart_bs = 1 +" [Favorite-File Mode] This is used to sort modes for switching to the +" next/previous mode. +let g:FuzzyFinderOptions.FavFile.switch_order = 60 +"----------------------------------------------------------------------------- +" [Tag Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.Tag.mode_available = 1 +" [Tag Mode] The prompt string. +let g:FuzzyFinderOptions.Tag.prompt = '>Tag>' +" [Tag Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.Tag.prompt_highlight = 'Question' +" [Tag Mode] Pressing <BS> after a path separator deletes one directory name +" if non-zero is set. +let g:FuzzyFinderOptions.Tag.smart_bs = 0 +" [Tag Mode] This is used to sort modes for switching to the next/previous +" mode. +let g:FuzzyFinderOptions.Tag.switch_order = 70 +" [Tag Mode] The items matching this are excluded from the completion list. +let g:FuzzyFinderOptions.Tag.excluded_path = '\v\~$|\.bak$|\.swp$' +" [Tag Mode] If a number of matched items was over this, the completion +" process is aborted. +let g:FuzzyFinderOptions.Tag.matching_limit = 200 +"----------------------------------------------------------------------------- +" [Tagged-File Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.TaggedFile.mode_available = 1 +" [Tagged-File Mode] The prompt string. +let g:FuzzyFinderOptions.TaggedFile.prompt = '>TaggedFile>' +" [Tagged-File Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.TaggedFile.prompt_highlight = 'Question' +" [Tagged-File Mode] Pressing <BS> after a path separator deletes one +" directory name if non-zero is set. +let g:FuzzyFinderOptions.TaggedFile.smart_bs = 0 +" [Tagged-File Mode] This is used to sort modes for switching to the +" next/previous mode. +let g:FuzzyFinderOptions.TaggedFile.switch_order = 80 +" [Tagged-File Mode] If a number of matched items was over this, the +" completion process is aborted. +let g:FuzzyFinderOptions.TaggedFile.matching_limit = 200 + +" overwrites default values of g:FuzzyFinderOptions with user-defined values - {{{2 +call map(user_options, 'extend(g:FuzzyFinderOptions[v:key], v:val, ''force'')') +call map(copy(g:FuzzyFinderMode), 'v:val.extend_options()') +" }}}2 + +" }}}1 +"============================================================================= +" COMMANDS/AUTOCOMMANDS/MAPPINGS/ETC.: {{{1 + +let s:PATH_SEPARATOR = (has('win32') || has('win64') ? '\' : '/') +let s:MATCHING_RATE_BASE = 10000000 +let s:ABBR_TRIM_MARK = '...' + +augroup FuzzyfinderGlobal + autocmd! + autocmd BufEnter * for m in s:GetAvailableModes() | call m.extend_options() | call m.on_buf_enter() | endfor + autocmd BufWritePost * for m in s:GetAvailableModes() | call m.extend_options() | call m.on_buf_write_post() | endfor +augroup END + +" cnoremap has a problem, which doesn't expand cabbrev. +cmap <silent> <expr> <CR> <SID>OnCmdCR() + +command! -bang -narg=? -complete=buffer FuzzyFinderBuffer call g:FuzzyFinderMode.Buffer.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderFile call g:FuzzyFinderMode.File.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=dir FuzzyFinderDir call g:FuzzyFinderMode.Dir.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderMruFile call g:FuzzyFinderMode.MruFile.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderMruCmd call g:FuzzyFinderMode.MruCmd.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderFavFile call g:FuzzyFinderMode.FavFile.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=tag FuzzyFinderTag call g:FuzzyFinderMode.Tag.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderTaggedFile call g:FuzzyFinderMode.TaggedFile.launch(<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderEditInfo call s:InfoFileManager.edit() +command! -bang -narg=? -complete=file FuzzyFinderAddFavFile call g:FuzzyFinderMode.FavFile.add(<q-args>, 1) +command! -bang -narg=0 FuzzyFinderRemoveCache for m in s:GetAvailableModes() | call m.empty_cache_if_existed(1) | endfor + +" }}}1 +"============================================================================= +" vim: set fdm=marker:
new file mode 100644 --- /dev/null +++ b/.vim/plugin/fuzzyfinder_textmate.vim @@ -0,0 +1,150 @@ +if has("ruby") + +" ==================================================================================== +" COPIED FROM FUZZYFINDER.VIM {{{ +" since they can't be called from outside fuzzyfinder.vim +" ==================================================================================== +function! s:GetCurrentTagFiles() + return sort(filter(map(tagfiles(), 'fnamemodify(v:val, '':p'')'), 'filereadable(v:val)')) +endfunction + +function! s:HighlightPrompt(prompt, highlight) + syntax clear + execute printf('syntax match %s /^\V%s/', a:highlight, escape(a:prompt, '\')) +endfunction + +function! s:HighlightError() + syntax clear + syntax match Error /^.*$/ +endfunction +" ------------------------------------------------------------------------------------ +" }}} +" ==================================================================================== + +command! -bang -narg=? -complete=file FuzzyFinderTextMate call FuzzyFinderTextMateLauncher(<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) + +function! InstantiateTextMateMode() "{{{ +ruby << RUBY + begin + require "#{ENV['HOME']}/.vim/ruby/fuzzy_file_finder" + rescue LoadError + begin + require 'rubygems' + begin + gem 'fuzzy_file_finder' + rescue Gem::LoadError + gem 'jamis-fuzzy_file_finder' + end + rescue LoadError + end + + require 'fuzzy_file_finder' + end +RUBY + + " Configuration option: g:fuzzy_roots + " Specifies roots in which the FuzzyFinder will search. + if !exists('g:fuzzy_roots') + let g:fuzzy_roots = ['.'] + endif + + " Configuration option: g:fuzzy_ceiling + " Specifies the maximum number of files that FuzzyFinder allows to be searched + if !exists('g:fuzzy_ceiling') + let g:fuzzy_ceiling = 10000 + endif + + " Configuration option: g:fuzzy_ignore + " A semi-colon delimited list of file glob patterns to ignore + if !exists('g:fuzzy_ignore') + let g:fuzzy_ignore = "" + endif + + " Configuration option: g:fuzzy_matching_limit + " The maximum number of matches to return at a time. Defaults to 200, via the + " g:FuzzyFinderMode.TextMate.matching_limit variable, but using a global variable + " makes it easier to set this value. + +ruby << RUBY + def finder + @finder ||= begin + roots = VIM.evaluate("g:fuzzy_roots").split("\n") + ceiling = VIM.evaluate("g:fuzzy_ceiling").to_i + ignore = VIM.evaluate("g:fuzzy_ignore").split(/;/) + FuzzyFileFinder.new(roots, ceiling, ignore) + end + end +RUBY + + let g:FuzzyFinderMode.TextMate = copy(g:FuzzyFinderMode.Base) + + " ================================================================================ + " This function is copied almost whole-sale from fuzzyfinder.vim. Ideally, I could + " used the on_complete callback to more cleanly add the new behavior, but the + " TextMate-style completion broke a few of fuzzyfinder.vim's assumptions, and the + " only way to patch that up was to override Base.complete...which required me to + " copy-and-paste much of the original implementation. + " + " Ugly. But effective. + " ================================================================================ + function! g:FuzzyFinderMode.TextMate.complete(findstart, base) + if a:findstart + return 0 + elseif !self.exists_prompt(a:base) || len(self.remove_prompt(a:base)) < self.min_length + return [] + endif + call s:HighlightPrompt(self.prompt, self.prompt_highlight) + + let result = [] + + if exists('g:fuzzy_matching_limit') + let l:limit = g:fuzzy_matching_limit + else + let l:limit = self.matching_limit + endif + + ruby << RUBY + text = VIM.evaluate('self.remove_prompt(a:base)') + limit = VIM.evaluate('l:limit').to_i + + matches = finder.find(text, limit) + matches.sort_by { |a| [-a[:score], a[:path]] }.each_with_index do |match, index| + word = match[:path] + abbr = "%2d: %s" % [index+1, match[:abbr]] + menu = "[%5d]" % [match[:score] * 10000] + VIM.evaluate("add(result, { 'word' : #{word.inspect}, 'abbr' : #{abbr.inspect}, 'menu' : #{menu.inspect} })") + end +RUBY + + if empty(result) || len(result) >= self.matching_limit + call s:HighlightError() + endif + + if !empty(result) + call feedkeys("\<C-p>\<Down>", 'n') + endif + + return result + endfunction + + function! FuzzyFinderTextMateLauncher(initial_text, partial_matching, prev_bufnr, tag_files) + call g:FuzzyFinderMode.TextMate.launch(a:initial_text, a:partial_matching, a:prev_bufnr, a:tag_files) + endfunction + + let g:FuzzyFinderOptions.TextMate = copy(g:FuzzyFinderOptions.File) +endfunction "}}} + +if !exists('loaded_fuzzyfinder') "{{{ + function! FuzzyFinderTextMateLauncher(initial_text, partial_matching, prev_bufnr, tag_files) + call InstantiateTextMateMode() + function! FuzzyFinderTextMateLauncher(initial_text, partial_matching, prev_bufnr, tag_files) + call g:FuzzyFinderMode.TextMate.launch(a:initial_text, a:partial_matching, a:prev_bufnr, a:tag_files) + endfunction + call g:FuzzyFinderMode.TextMate.launch(a:initial_text, a:partial_matching, a:prev_bufnr, a:tag_files) + endfunction + finish +end "}}} + +call InstantiateTextMateMode() + +endif
new file mode 100644 --- /dev/null +++ b/.vim/plugin/fuzzyfinder_textmate_README @@ -0,0 +1,47 @@ +FuzzyFinder: TextMate +-------------------------- +This is an extension to Takeshi NISHIDA's excellent Fuzzyfinder VIM script[1]. It adds a TextMate mode, for completing file names in a similar fashion to how TextMate[2] does file completion in its cmd-T window. + +This extension is partially written in Ruby[3], and it depends on a separate Ruby module (fuzzy_file_finder[4]), so you'll need Vim to be compiled with Ruby support. + + +Usage +------------------------ +Usage is almost identical to the Fuzzyfinder script, so you should check out the documentation[1] for that (which is very good). The only difference is that to start TextMate mode, you just invoke the :FuzzyFinderTextMate command. It is recommended to map that command to something more agile, e.g.: + + map <leader>t :FuzzyFinderTextMate<CR> + +Once in TextMate mode, completion is done against the entire directory tree at once, using partial matches. Results are sorted by "score", which is a measure of how closely the file matches the text you entered. + +There are several different variables that you may set in your .vimrc file that can be used to tweak the finder's behavior: + +* g:fuzzy_roots - this defaults to the current directory, but may be set to an array of paths that you want the finder to search. +* g:fuzzy_ceiling - this defaults to 10,000, and is the maximum number of files that the finder will scan before it raises an error. To use the finder on larger trees, set this variable to a value larger than the number of files you expect to scan. +* g:fuzzy_ignore - this is a semi-colon delimited list of file glob patterns to ignore. +* g:fuzzy_matching_limit - this is the maximum number of items that will be matched, and defaults to 200. If the finder feels sluggish to you, you can reduce this to something smaller (100, or 50). + +Installation +----------------------- +First, make sure you have the Fuzzyfinder script[1]. Install that as per the instructions for that script. + +Then, you'll need to install the fuzzy_file_finder[4] Ruby module. The simplest way to do this is via Rubygems: + + gem install --source=http://gems.github.com jamis-fuzzy_file_finder + +Alternatively, you can take the lib/fuzzy_file_finder.rb file from that project and put it in ~/.vim/ruby. + +Lastly, you'll need to install this extension script. Take the fuzzyfinder_textmate.vim script and put it under ~/.vim/plugin. + + +License +----------------------- +Most of this script and its supporting documentation is made available in the PUBLIC DOMAIN. It may be used, modified, and redistributed, almost without exception. The only exceptions are those parts of the code that were copied from NISHIDA's original script; those parts are distributed under the MIT license, as indicated by NISHIDA's code. The parts that were copied are indicated thus by comments. + + +References +----------------------- +[1] http://www.vim.org/scripts/script.php?script_id=1984 +[2] http://www.macromates.com +[3] http://www.ruby-lang.org +[4] http://github.com/jamis/fuzzy_file_finder/tree/master +
new file mode 100644 --- /dev/null +++ b/.vim/plugin/supertab.vim @@ -0,0 +1,211 @@ +" Author: Gergely Kontra <kgergely@mcl.hu> +" You may direct issues regarding version 0.4+ to +" Eric Van Dewoestine (ervandew@yahoo.com). +" Version: 0.41 +" Description: +" Use your tab key to do all your completion in insert mode! +" The script remembers the last completion type, and applies that. +" Eg.: You want to enter /usr/local/lib/povray3/ +" You type (in insert mode): +" /u<C-x><C-f>/l<Tab><Tab><Tab>/p<Tab>/i<Tab> +" You can also manipulate the completion type used by changing g:complType +" variable. +" You can cycle forward and backward with the <Tab> and <S-Tab> keys +" (<S-Tab> will not work in the console version) +" Note: you must press <Tab> once to be able to cycle back +" History: +" 0.41 Fixed couple bugs introduced in last version (Eric Van Dewoestine). +" 0.4 Added the following functionality (Eric Van Dewoestine) +" - support for vim 7 omni, user, and spelling completion modes +" (should be backwards compatible with vim 6.x). +" - command :SuperTabHelp which opens a window with available +" completion types that the user can choose from. +" - variable g:SuperTabRetainCompletionType setting for determining if +" and for how long to retain completion type. +" 0.32 Corrected tab-insertion/completing decidion (thx to: Lorenz Wegener) +" 0.31 Added <S-Tab> for backward cycling. (req by: Peter Chun) +" 0.3 Back to the roots. Autocompletion is another story... +" Now the prompt appears, when showmode is on + +if !exists('complType') "Integration with other completion functions... + + " This variable determines if, and for how long, the current completion type + " is retained. The possible values include: + " 0 - The current completion type is only retained for the current completion. + " Once you have chosen a completion result or exited the completion + " mode, the default completion type is restored. + " 1 - The current completion type is saved for the duration of your vim + " session or until you enter a different completion mode. + " (SuperTab default). + " 2 - The current completion type is saved until you exit insert mode (via + " ESC). Once you exit insert mode the default completion type is + " restored. + if !exists("g:SuperTabRetainCompletionType") + let g:SuperTabRetainCompletionType = 1 + endif + + " This variable is used to set the default completion type. + " There is no need to escape this value as that will be done for you when + " the type is set. + " Ex. let g:SuperTabDefaultCompletionType = "<C-X><C-U>" + if !exists("g:SuperTabDefaultCompletionType") + let g:SuperTabDefaultCompletionType = "<C-P>" + endif + + " construct the help text. + let s:tabHelp = + \ "Hit <CR> or CTRL-] on the completion type you wish to swith to.\n" . + \ "Use :help ins-completion for more information.\n" . + \ "\n" . + \ "|<C-N>| - Keywords in 'complete' searching down.\n" . + \ "|<C-P>| - Keywords in 'complete' searching up (SuperTab default).\n" . + \ "|<C-X><C-L>| - Whole lines.\n" . + \ "|<C-X><C-N>| - Keywords in current file.\n" . + \ "|<C-X><C-K>| - Keywords in 'dictionary'.\n" . + \ "|<C-X><C-T>| - Keywords in 'thesaurus', thesaurus-style.\n" . + \ "|<C-X><C-I>| - Keywords in the current and included files.\n" . + \ "|<C-X><C-]>| - Tags.\n" . + \ "|<C-X><C-F>| - File names.\n" . + \ "|<C-X><C-D>| - Definitions or macros.\n" . + \ "|<C-X><C-V>| - Vim command-line." + if v:version >= 700 + let s:tabHelp = s:tabHelp . "\n" . + \ "|<C-X><C-U>| - User defined completion.\n" . + \ "|<C-X><C-O>| - Occult completion.\n" . + \ "|<C-X>s| - Spelling suggestions." + endif + + " set the available completion types and modes. + let s:types = + \ "\<C-E>\<C-Y>\<C-L>\<C-N>\<C-K>\<C-T>\<C-I>\<C-]>\<C-F>\<C-D>\<C-V>\<C-N>\<C-P>" + let s:modes = '/^E/^Y/^L/^N/^K/^T/^I/^]/^F/^D/^V/^P' + if v:version >= 700 + let s:types = s:types . "\<C-U>\<C-O>\<C-N>\<C-P>s" + let s:modes = s:modes . '/^U/^O/s' + endif + let s:types = s:types . "np" + let s:modes = s:modes . '/n/p' + + " Globally available function that user's can use to create mappings to + " quickly switch completion modes. Useful when a user wants to restore the + " default or switch to another mode without having to kick off a completion + " of that type or use SuperTabHelp. + " Example mapping to restore SuperTab default: + " nmap <F6> :call SetSuperTabCompletionType("<C-P>")<cr> + fu! SuperTabSetCompletionType (type) + exec "let g:complType = \"" . escape(a:type, '<') . "\"" + endf + + call SuperTabSetCompletionType(g:SuperTabDefaultCompletionType) + + im <C-X> <C-r>=CtrlXPP()<CR> + + " Setup mechanism to restore orignial completion type upon leaving insert + " mode if g:SuperTabDefaultCompletionType == 2 + if g:SuperTabRetainCompletionType == 2 + " pre vim 7, must map <esc> + if v:version < 700 + im <silent> <ESC> + \ <ESC>:call SuperTabSetCompletionType(g:SuperTabDefaultCompletionType)<cr> + + " since vim 7, we can use InsertLeave autocmd. + else + augroup supertab + autocmd InsertLeave * + \ call SuperTabSetCompletionType(g:SuperTabDefaultCompletionType) + augroup END + endif + endif + + fu! CtrlXPP() + if &smd + echo '' | echo '-- ^X++ mode (' . s:modes . ')' + endif + let complType=nr2char(getchar()) + if stridx(s:types, complType) != -1 + if stridx("\<C-E>\<C-Y>",complType)!=-1 " no memory, just scroll... + return "\<C-x>".complType + elseif stridx('np',complType)!=-1 + let complType=nr2char(char2nr(complType)-96) " char2nr('n')-char2nr("\<C-n") + else + let complType="\<C-x>".complType + endif + + if g:SuperTabRetainCompletionType + let g:complType = complType + endif + + return complType + else + echohl "Unknown mode" + return complType + endif + endf + + " From the doc |insert.txt| improved + im <Tab> <C-n> + inore <S-Tab> <C-p> + + " This way after hitting <Tab>, hitting it once more will go to next match + " (because in XIM mode <C-n> and <C-p> mappings are ignored) + " and wont start a brand new completion + " The side effect, that in the beginning of line <C-n> and <C-p> inserts a + " <Tab>, but I hope it may not be a problem... + ino <C-n> <C-R>=<SID>SuperTab('n')<CR> + ino <C-p> <C-R>=<SID>SuperTab('p')<CR> + + fu! <SID>SuperTab(command) + if (strpart(getline('.'),col('.')-2,1)=~'^\s\?$') + return "\<Tab>" + else + " exception: if in <c-p> mode, then <c-n> should move up the list, and + " <c-p> down the list. + if a:command == 'p' && g:complType == "\<C-P>" + return "\<C-N>" + endif + return g:complType + endif + endf + + fu! <SID>SuperTabHelp() + if bufwinnr("SuperTabHelp") == -1 + botright split SuperTabHelp + + setlocal noswapfile + setlocal buftype=nowrite + setlocal bufhidden=delete + + let saved = @" + let @" = s:tabHelp + silent put + call cursor(1,1) + silent 1,delete + call cursor(4,1) + let @" = saved + exec "resize " . line('$') + + syntax match Special "|.\{-}|" + + setlocal readonly + setlocal nomodifiable + + nmap <silent> <buffer> <cr> :call <SID>SetCompletionType()<cr> + nmap <silent> <buffer> <c-]> :call <SID>SetCompletionType()<cr> + else + exec bufwinnr("SuperTabHelp") . "winc w" + endif + endf + + fu! s:SetCompletionType () + let chosen = substitute(getline('.'), '.*|\(.*\)|.*', '\1', '') + if chosen != getline('.') + call SuperTabSetCompletionType(chosen) + close + winc p + endif + endf + + if !exists(":SuperTabHelp") + command SuperTabHelp :call <SID>SuperTabHelp() + endif +en
new file mode 100644 --- /dev/null +++ b/.vim/syntax/django.vim @@ -0,0 +1,93 @@ +" Vim syntax file +" Language: Django template +" Maintainer: Dave Hodder <dmh@dmh.org.uk> +" Last Change: 2007 Apr 21 + +" For version 5.x: Clear all syntax items +" For version 6.x: Quit when a syntax file was already loaded +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +syntax case match + +" Mark illegal characters +syn match djangoError "%}\|}}\|#}" + +" Django template built-in tags and parameters +" 'comment' doesn't appear here because it gets special treatment +syn keyword djangoStatement contained and as block endblock by cycle debug else +syn keyword djangoStatement contained extends filter endfilter firstof for +syn keyword djangoStatement contained endfor if endif ifchanged endifchanged +syn keyword djangoStatement contained ifequal endifequal ifnotequal +syn keyword djangoStatement contained endifnotequal in include load not now or +syn keyword djangoStatement contained parsed regroup reversed spaceless +syn keyword djangoStatement contained endspaceless ssi templatetag openblock +syn keyword djangoStatement contained closeblock openvariable closevariable +syn keyword djangoStatement contained openbrace closebrace opencomment +syn keyword djangoStatement contained closecomment widthratio url with endwith +syn keyword djangoStatement contained get_current_language trans noop blocktrans +syn keyword djangoStatement contained endblocktrans get_available_languages +syn keyword djangoStatement contained get_current_language_bidi plural + +" Django templete built-in filters +syn keyword djangoFilter contained add addslashes capfirst center cut date +syn keyword djangoFilter contained default default_if_none dictsort +syn keyword djangoFilter contained dictsortreversed divisibleby escape +syn keyword djangoFilter contained filesizeformat first fix_ampersands +syn keyword djangoFilter contained floatformat get_digit join length length_is +syn keyword djangoFilter contained linebreaks linebreaksbr linenumbers ljust +syn keyword djangoFilter contained lower make_list phone2numeric pluralize +syn keyword djangoFilter contained pprint random removetags rjust slice slugify +syn keyword djangoFilter contained stringformat striptags +syn keyword djangoFilter contained time timesince timeuntil title +syn keyword djangoFilter contained truncatewords unordered_list upper urlencode +syn keyword djangoFilter contained urlize urlizetrunc wordcount wordwrap yesno + +" Keywords to highlight within comments +syn keyword djangoTodo contained TODO FIXME XXX + +" Django template constants (always surrounded by double quotes) +syn region djangoArgument contained start=/"/ skip=/\\"/ end=/"/ + +" Mark illegal characters within tag and variables blocks +syn match djangoTagError contained "#}\|{{\|[^%]}}\|[<>!&#]" +syn match djangoVarError contained "#}\|{%\|%}\|[<>!&#%]" + +" Django template tag and variable blocks +syn region djangoTagBlock start="{%" end="%}" contains=djangoStatement,djangoFilter,djangoArgument,djangoTagError display +syn region djangoVarBlock start="{{" end="}}" contains=djangoFilter,djangoArgument,djangoVarError display + +" Django template 'comment' tag and comment block +syn region djangoComment start="{%\s*comment\s*%}" end="{%\s*endcomment\s*%}" contains=djangoTodo +syn region djangoComBlock start="{#" end="#}" contains=djangoTodo + +" Define the default highlighting. +" For version 5.7 and earlier: only when not done already +" For version 5.8 and later: only when an item doesn't have highlighting yet +if version >= 508 || !exists("did_django_syn_inits") + if version < 508 + let did_django_syn_inits = 1 + command -nargs=+ HiLink hi link <args> + else + command -nargs=+ HiLink hi def link <args> + endif + + HiLink djangoTagBlock PreProc + HiLink djangoVarBlock PreProc + HiLink djangoStatement Statement + HiLink djangoFilter Identifier + HiLink djangoArgument Constant + HiLink djangoTagError Error + HiLink djangoVarError Error + HiLink djangoError Error + HiLink djangoComment Comment + HiLink djangoComBlock Comment + HiLink djangoTodo Todo + + delcommand HiLink +endif + +let b:current_syntax = "django"
new file mode 100644 --- /dev/null +++ b/.vim/syntax/python.vim @@ -0,0 +1,352 @@ +" Vim syntax file +" Language: Python +" Maintainer: Dmitry Vasiliev <dima@hlabs.spb.ru> +" URL: http://www.hlabs.spb.ru/vim/python.vim +" Last Change: 2008-09-29 +" Filenames: *.py +" Version: 2.6.3 +" +" Based on python.vim (from Vim 6.1 distribution) +" by Neil Schemenauer <nas@python.ca> +" +" Thanks: +" +" Jeroen Ruigrok van der Werven +" for the idea to highlight erroneous operators +" Pedro Algarvio +" for the patch to enable spell checking only for the right spots +" (strings and comments) +" John Eikenberry +" for the patch fixing small typo + +" +" Options: +" +" For set option do: let OPTION_NAME = 1 +" For clear option do: let OPTION_NAME = 0 +" +" Option names: +" +" For highlight builtin functions: +" python_highlight_builtins +" +" For highlight standard exceptions: +" python_highlight_exceptions +" +" For highlight string formatting: +" python_highlight_string_formatting +" +" For highlight str.format syntax: +" python_highlight_string_format +" +" For highlight string.Template syntax: +" python_highlight_string_templates +" +" For highlight indentation errors: +" python_highlight_indent_errors +" +" For highlight trailing spaces: +" python_highlight_space_errors +" +" For highlight doc-tests: +" python_highlight_doctests +" +" If you want all Python highlightings above: +" python_highlight_all +" (This option not override previously set options) +" +" For fast machines: +" python_slow_sync +" +" For "print" builtin as function: +" python_print_as_function + +" For version 5.x: Clear all syntax items +" For version 6.x: Quit when a syntax file was already loaded +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +if exists("python_highlight_all") && python_highlight_all != 0 + " Not override previously set options + if !exists("python_highlight_builtins") + let python_highlight_builtins = 1 + endif + if !exists("python_highlight_exceptions") + let python_highlight_exceptions = 1 + endif + if !exists("python_highlight_string_formatting") + let python_highlight_string_formatting = 1 + endif + if !exists("python_highlight_string_format") + let python_highlight_string_format = 1 + endif + if !exists("python_highlight_string_templates") + let python_highlight_string_templates = 1 + endif + if !exists("python_highlight_indent_errors") + let python_highlight_indent_errors = 1 + endif + if !exists("python_highlight_space_errors") + let python_highlight_space_errors = 1 + endif + if !exists("python_highlight_doctests") + let python_highlight_doctests = 1 + endif +endif + +" Keywords +syn keyword pythonStatement break continue del +syn keyword pythonStatement exec return +syn keyword pythonStatement pass raise +syn keyword pythonStatement global assert +syn keyword pythonStatement lambda yield +syn keyword pythonStatement with +syn keyword pythonStatement def class nextgroup=pythonFunction skipwhite +syn match pythonFunction "[a-zA-Z_][a-zA-Z0-9_]*" display contained +syn keyword pythonRepeat for while +syn keyword pythonConditional if elif else +syn keyword pythonImport import from as +syn keyword pythonException try except finally +syn keyword pythonOperator and in is not or + +if !exists("python_print_as_function") || python_print_as_function == 0 + syn keyword pythonStatement print +endif + +" Decorators (new in Python 2.4) +syn match pythonDecorator "@" display nextgroup=pythonFunction skipwhite + +" Comments +syn match pythonComment "#.*$" display contains=pythonTodo,@Spell +syn match pythonRun "\%^#!.*$" +syn match pythonCoding "\%^.*\(\n.*\)\?#.*coding[:=]\s*[0-9A-Za-z-_.]\+.*$" +syn keyword pythonTodo TODO FIXME XXX contained + +" Errors +syn match pythonError "\<\d\+\D\+\>" display +syn match pythonError "[$?]" display +syn match pythonError "[&|]\{2,}" display +syn match pythonError "[=]\{3,}" display + +" TODO: Mixing spaces and tabs also may be used for pretty formatting multiline +" statements. For now I don't know how to work around this. +if exists("python_highlight_indent_errors") && python_highlight_indent_errors != 0 + syn match pythonIndentError "^\s*\( \t\|\t \)\s*\S"me=e-1 display +endif + +" Trailing space errors +if exists("python_highlight_space_errors") && python_highlight_space_errors != 0 + syn match pythonSpaceError "\s\+$" display +endif + +" Strings +syn region pythonString start=+[bB]\='+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell +syn region pythonString start=+[bB]\="+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell +syn region pythonString start=+[bB]\="""+ end=+"""+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest2,pythonSpaceError,@Spell +syn region pythonString start=+[bB]\='''+ end=+'''+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest,pythonSpaceError,@Spell + +syn match pythonEscape +\\[abfnrtv'"\\]+ display contained +syn match pythonEscape "\\\o\o\=\o\=" display contained +syn match pythonEscapeError "\\\o\{,2}[89]" display contained +syn match pythonEscape "\\x\x\{2}" display contained +syn match pythonEscapeError "\\x\x\=\X" display contained +syn match pythonEscape "\\$" + +" Unicode strings +syn region pythonUniString start=+[uU]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell +syn region pythonUniString start=+[uU]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell +syn region pythonUniString start=+[uU]"""+ end=+"""+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest2,pythonSpaceError,@Spell +syn region pythonUniString start=+[uU]'''+ end=+'''+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest,pythonSpaceError,@Spell + +syn match pythonUniEscape "\\u\x\{4}" display contained +syn match pythonUniEscapeError "\\u\x\{,3}\X" display contained +syn match pythonUniEscape "\\U\x\{8}" display contained +syn match pythonUniEscapeError "\\U\x\{,7}\X" display contained +syn match pythonUniEscape "\\N{[A-Z ]\+}" display contained +syn match pythonUniEscapeError "\\N{[^A-Z ]\+}" display contained + +" Raw strings +syn region pythonRawString start=+[rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,@Spell +syn region pythonRawString start=+[rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,@Spell +syn region pythonRawString start=+[rR]"""+ end=+"""+ keepend contains=pythonDocTest2,pythonSpaceError,@Spell +syn region pythonRawString start=+[rR]'''+ end=+'''+ keepend contains=pythonDocTest,pythonSpaceError,@Spell + +syn match pythonRawEscape +\\['"]+ display transparent contained + +" Unicode raw strings +syn region pythonUniRawString start=+[uU][rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell +syn region pythonUniRawString start=+[uU][rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell +syn region pythonUniRawString start=+[uU][rR]"""+ end=+"""+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest2,pythonSpaceError,@Spell +syn region pythonUniRawString start=+[uU][rR]'''+ end=+'''+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest,pythonSpaceError,@Spell + +syn match pythonUniRawEscape "\([^\\]\(\\\\\)*\)\@<=\\u\x\{4}" display contained +syn match pythonUniRawEscapeError "\([^\\]\(\\\\\)*\)\@<=\\u\x\{,3}\X" display contained + +if exists("python_highlight_string_formatting") && python_highlight_string_formatting != 0 + " String formatting + syn match pythonStrFormatting "%\(([^)]\+)\)\=[-#0 +]*\d*\(\.\d\+\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrFormatting "%[-#0 +]*\(\*\|\d\+\)\=\(\.\(\*\|\d\+\)\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString +endif + +if exists("python_highlight_string_format") && python_highlight_string_format != 0 + " str.format syntax + syn match pythonStrFormat "{{\|}}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrFormat "{\([a-zA-Z_][a-zA-Z0-9_]*\|\d\+\)\(\.[a-zA-Z_][a-zA-Z0-9_]*\|\[\(\d\+\|[^!:\}]\+\)\]\)*\(![rs]\)\=\(:\({\([a-zA-Z_][a-zA-Z0-9_]*\|\d\+\)}\|\([^}]\=[<>=^]\)\=[ +-]\=#\=0\=\d*\(\.\d\+\)\=[bcdeEfFgGnoxX%]\=\)\=\)\=}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString +endif + +if exists("python_highlight_string_templates") && python_highlight_string_templates != 0 + " String templates + syn match pythonStrTemplate "\$\$" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrTemplate "\${[a-zA-Z_][a-zA-Z0-9_]*}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrTemplate "\$[a-zA-Z_][a-zA-Z0-9_]*" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString +endif + +if exists("python_highlight_doctests") && python_highlight_doctests != 0 + " DocTests + syn region pythonDocTest start="^\s*>>>" end=+'''+he=s-1 end="^\s*$" contained + syn region pythonDocTest2 start="^\s*>>>" end=+"""+he=s-1 end="^\s*$" contained +endif + +" Numbers (ints, longs, floats, complex) +syn match pythonHexError "\<0[xX]\x*[g-zG-Z]\x*[lL]\=\>" display + +syn match pythonHexNumber "\<0[xX]\x\+[lL]\=\>" display +syn match pythonOctNumber "\<0[oO]\o\+[lL]\=\>" display +syn match pythonBinNumber "\<0[bB][01]\+[lL]\=\>" display + +syn match pythonNumber "\<\d\+[lLjJ]\=\>" display + +syn match pythonFloat "\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>" display +syn match pythonFloat "\<\d\+[eE][+-]\=\d\+[jJ]\=\>" display +syn match pythonFloat "\<\d\+\.\d*\([eE][+-]\=\d\+\)\=[jJ]\=" display + +syn match pythonOctError "\<0[oO]\=\o*[8-9]\d*[lL]\=\>" display +syn match pythonBinError "\<0[bB][01]*[2-9]\d*[lL]\=\>" display + +if exists("python_highlight_builtins") && python_highlight_builtins != 0 + " Builtin functions, types and objects + syn keyword pythonBuiltinObj True False Ellipsis None NotImplemented + syn keyword pythonBuiltinObj __debug__ __doc__ __file__ __name__ __package__ + + syn keyword pythonBuiltinFunc __import__ abs all any apply + syn keyword pythonBuiltinFunc basestring bin bool buffer bytearray bytes callable + syn keyword pythonBuiltinFunc chr classmethod cmp coerce compile complex + syn keyword pythonBuiltinFunc delattr dict dir divmod enumerate eval + syn keyword pythonBuiltinFunc execfile file filter float format frozenset getattr + syn keyword pythonBuiltinFunc globals hasattr hash help hex id + syn keyword pythonBuiltinFunc input int intern isinstance + syn keyword pythonBuiltinFunc issubclass iter len list locals long map max + syn keyword pythonBuiltinFunc min next object oct open ord + syn keyword pythonBuiltinFunc pow property range + syn keyword pythonBuiltinFunc raw_input reduce reload repr + syn keyword pythonBuiltinFunc reversed round set setattr + syn keyword pythonBuiltinFunc slice sorted staticmethod str sum super tuple + syn keyword pythonBuiltinFunc type unichr unicode vars xrange zip + + if exists("python_print_as_function") && python_print_as_function != 0 + syn keyword pythonBuiltinFunc print + endif +endif + +if exists("python_highlight_exceptions") && python_highlight_exceptions != 0 + " Builtin exceptions and warnings + syn keyword pythonExClass BaseException + syn keyword pythonExClass Exception StandardError ArithmeticError + syn keyword pythonExClass LookupError EnvironmentError + + syn keyword pythonExClass AssertionError AttributeError BufferError EOFError + syn keyword pythonExClass FloatingPointError GeneratorExit IOError + syn keyword pythonExClass ImportError IndexError KeyError + syn keyword pythonExClass KeyboardInterrupt MemoryError NameError + syn keyword pythonExClass NotImplementedError OSError OverflowError + syn keyword pythonExClass ReferenceError RuntimeError StopIteration + syn keyword pythonExClass SyntaxError IndentationError TabError + syn keyword pythonExClass SystemError SystemExit TypeError + syn keyword pythonExClass UnboundLocalError UnicodeError + syn keyword pythonExClass UnicodeEncodeError UnicodeDecodeError + syn keyword pythonExClass UnicodeTranslateError ValueError VMSError + syn keyword pythonExClass WindowsError ZeroDivisionError + + syn keyword pythonExClass Warning UserWarning BytesWarning DeprecationWarning + syn keyword pythonExClass PendingDepricationWarning SyntaxWarning + syn keyword pythonExClass RuntimeWarning FutureWarning + syn keyword pythonExClass ImportWarning UnicodeWarning +endif + +if exists("python_slow_sync") && python_slow_sync != 0 + syn sync minlines=2000 +else + " This is fast but code inside triple quoted strings screws it up. It + " is impossible to fix because the only way to know if you are inside a + " triple quoted string is to start from the beginning of the file. + syn sync match pythonSync grouphere NONE "):$" + syn sync maxlines=200 +endif + +if version >= 508 || !exists("did_python_syn_inits") + if version <= 508 + let did_python_syn_inits = 1 + command -nargs=+ HiLink hi link <args> + else + command -nargs=+ HiLink hi def link <args> + endif + + HiLink pythonStatement Statement + HiLink pythonImport Statement + HiLink pythonFunction Function + HiLink pythonConditional Conditional + HiLink pythonRepeat Repeat + HiLink pythonException Exception + HiLink pythonOperator Operator + + HiLink pythonDecorator Define + + HiLink pythonComment Comment + HiLink pythonCoding Special + HiLink pythonRun Special + HiLink pythonTodo Todo + + HiLink pythonError Error + HiLink pythonIndentError Error + HiLink pythonSpaceError Error + + HiLink pythonString String + HiLink pythonUniString String + HiLink pythonRawString String + HiLink pythonUniRawString String + + HiLink pythonEscape Special + HiLink pythonEscapeError Error + HiLink pythonUniEscape Special + HiLink pythonUniEscapeError Error + HiLink pythonUniRawEscape Special + HiLink pythonUniRawEscapeError Error + + HiLink pythonStrFormatting Special + HiLink pythonStrFormat Special + HiLink pythonStrTemplate Special + + HiLink pythonDocTest Special + HiLink pythonDocTest2 Special + + HiLink pythonNumber Number + HiLink pythonHexNumber Number + HiLink pythonOctNumber Number + HiLink pythonBinNumber Number + HiLink pythonFloat Float + HiLink pythonOctError Error + HiLink pythonHexError Error + HiLink pythonBinError Error + + HiLink pythonBuiltinObj Structure + HiLink pythonBuiltinFunc Function + + HiLink pythonExClass Structure + + delcommand HiLink +endif + +let b:current_syntax = "python"
new file mode 100644 --- /dev/null +++ b/.vimrc @@ -0,0 +1,122 @@ +if v:lang =~ "utf8$" || v:lang =~ "UTF-8$" + set fileencodings=utf-8,latin1 +endif + +"set number " Show line number +set nocompatible " Use Vim defaults (much better!) +set bs=2 " allow backspacing over everything in insert mode +set autoindent +"set nosmartindent +"set backup " keep a backup file +set viminfo='20,\"50 " read/write a .viminfo file, don't store more + " than 50 lines of registers +set history=50 " keep 50 lines of command line history +set ruler " show the cursor position all the time +set hidden " allow background buffers that aren't written out +set wildmode=list:longest,full " be more like my shell + + +set ignorecase " unsure +set smartcase + +set scrolloff=3 " keep 3 lines of context + +set incsearch " search as you type + + + +filetype plugin indent on +colorscheme gothic + + +" Only do this part when compiled with support for autocommands +if has("autocmd") + " python settings + autocmd BufRead *.py set smartindent cinwords=if,elif,else,for,while,try,except,finally,def,class + autocmd FileType python set tabstop=4|set softtabstop=4|set shiftwidth=4|set expandtab + autocmd FileType python setlocal noautoindent nosmartindent + "autocmd BufRead,BufNewFile *melange*/** call confirm(string([&filetype, expand('<amatch>')])) + autocmd BufRead,BufNewFile *melange*/*.py setlocal sw=2 ts=2 + + " In text files, always limit the width of text to 78 characters + autocmd BufRead *.txt set tw=78 + " When editing a file, always jump to the last cursor position + autocmd BufReadPost * if line("'\"") > 0 && line ("'\"") <= line("$") | exe "normal g'\"" | endif +endif + +if has("cscope") + set csprg=/usr/bin/cscope + set csto=0 + set cst + set nocsverb + " add any database in current directory + if filereadable("cscope.out") + cs add cscope.out + " else add database pointed to by environment + elseif $CSCOPE_DB != "" + cs add $CSCOPE_DB + endif + set csverb +endif + +" Switch syntax highlighting on, when the terminal has colors +" Also switch on highlighting the last used search pattern. +if &t_Co > 2 || has("gui_running") + syntax on + set hlsearch +endif + +" Highlight trailing whitespace +"au Syntax * syn match Error /\s\+$/ | syn match Error /^\s* \t\s*/ +" strip trailing whitespace on save +autocmd FileType c,cpp,java,php,python autocmd BufWritePre <buffer> :call setline(1,map(getline(1,"$"),'substitute(v:val,"\\s\\+$","","")')) + +" Highlight past column 80 +:highlight Over80ColLimit term=inverse,bold cterm=bold ctermbg=red ctermfg=white gui=bold guibg=red guifg=white +:syntax match Over80ColLimit /\%81v.*/ + +if &term=="xterm" + set t_Co=8 + set t_Sb=[4%dm + set t_Sf=[3%dm +endif + +function! Find(name) + let l:_name = substitute(a:name, "\\s", "*", "g") + let l:list=system("find . -iname '*".l:_name."*' -not -name \"*.pyc\" -and -not -name \"*.o\" -and -not -name \"*.i\" -and -not -name \"*.class\" -and -not -name \"*.swp\" | perl -ne 'print \"$.\\t$_\"'") + let l:num=strlen(substitute(l:list, "[^\n]", "", "g")) + if l:num < 1 + echo "'".a:name."' not found" + return + endif + if l:num != 1 + echo l:list + let l:input=input("Which ? (<enter>=nothing)\n") + if strlen(l:input)==0 + return + endif + if strlen(substitute(l:input, "[0-9]", "", "g"))>0 + echo "Not a number" + return + endif + if l:input<1 || l:input>l:num + echo "Out of range" + return + endif + let l:line=matchstr("\n".l:list, "\n".l:input."\t[^\n]*") + else + let l:line=l:list + endif + let l:line=substitute(l:line, "^[^\t]*\t./", "", "") + execute ":e ".l:line +endfunction +command! -nargs=1 Find :call Find("<args>") + +if has("ruby") + map ,f :FuzzyFinderTextMate<CR> + let g:fuzzy_ignore="*.pyc" + let g:fuzzy_matching_limit=50 +else + map ,f :Find +endif +
new file mode 100644 --- /dev/null +++ b/.zsh/00.path_manipulation.zsh @@ -0,0 +1,17 @@ +# Functions for manipulating $PATH. Split out so I can use them in .zshenv if I want. + +function insert_path_element() { + if echo "$PATH" | grep "$1:" >> /dev/null; then + echo $1 already in path + else + export PATH="$1:$PATH" + fi +} + +function remove_path_element() { + if echo "$PATH" | grep "$1:" >> /dev/null; then + export PATH="`echo $PATH | sed s%$1:%%`" + else + echo $1 not in PATH + fi +}
new file mode 100644 --- /dev/null +++ b/.zsh/01.aliases.zsh @@ -0,0 +1,10 @@ +#aliases and funtions to act like them +function less() { command less -M "$@" } +function ll() { command ls -lFh "$@" } +function ls() { command ls -F "$@" } +function la() { command ls -aF "$@" } +function lla() { command ls -laFh "$@" } +function screen() { command screen -U "$@" } +function ipy() { command ipython "$@" } +function memacs() { command open -a Emacs "$@" } +alias vi='vim' \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/.zsh/50.completions.zsh @@ -0,0 +1,82 @@ +## Completions +autoload -Uz compinit +compinit -C +## completions #### +autoload -U zstyle+ +## General completion technique +## complete as much you can .. +zstyle ':completion:*' completer _complete _list _oldlist _expand _ignored _match _correct _approximate _prefix +## complete less +#zstyle ':completion:*' completer _expand _complete _list _ignored _approximate +## complete minimal +#zstyle ':completion:*' completer _complete _ignored + +local _myhosts +_myhosts=( ${${${${(f)"$(<$HOME/.ssh/known_hosts)"}:#[0-9]*}%%\ *}%%,*} ) +zstyle ':completion:*' hosts $_myhosts + +## allow one error +#zstyle ':completion:*:approximate:*' max-errors 1 numeric +## allow one error for every three characters typed in approximate completer +zstyle -e ':completion:*:approximate:*' max-errors \ +'reply=( $(( ($#PREFIX+$#SUFFIX)/3 )) numeric )' + +## formatting and messages +zstyle ':completion:*' verbose yes +#describe options presented at completion +#zstyle ':completion:*:descriptions' format $'%{\e[0;31m%}%d%{\e[0m%}' +zstyle ':completion:*:messages' format $'%{\e[0;31m%}%d%{\e[0m%}' +zstyle ':completion:*:warnings' format $'%{\e[0;31m%}No matches for: %d%{\e[0m%}' +zstyle ':completion:*:corrections' format $'%{\e[0;31m%}%d (errors: %e)%{\e[0m%}' +zstyle ':completion:*' group-name '' + +## determine in which order the names (files) should be +## listed and completed when using menu completion. +## `size' to sort them by the size of the file +## `links' to sort them by the number of links to the file +## `modification' or `time' or `date' to sort them by the last modification time +## `access' to sort them by the last access time +## `inode' or `change' to sort them by the last inode change time +## `reverse' to sort in decreasing order +## If the style is set to any other value, or is unset, files will be +## sorted alphabetically by name. +zstyle ':completion:*' file-sort name + +## how many completions switch on menu selection +## use 'long' to start menu compl. if list is bigger than screen +## or some number to start menu compl. if list has that number +## of completions (or more). +zstyle ':completion:*' menu select=long + +## case-insensitive (uppercase from lowercase) completion +#zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' +## case-insensitive (all) completion +#zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' +## case-insensitive,partial-word and then substring completion +zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*' + +## offer indexes before parameters in subscripts +zstyle ':completion:*:*:-subscript-:*' tag-order indexes parameters + +## insert all expansions for expand completer +zstyle ':completion:*:expand:*' tag-order all-expansions + +## ignore completion functions (until the _ignored completer) +zstyle ':completion:*:functions' ignored-patterns '_*' + +## completion caching +zstyle ':completion::complete:*' use-cache 1 +zstyle ':completion::complete:*' cache-path ~/.zcompcache/$HOST + +## add colors to completions +zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS} + +## don't complete backup files as executables +zstyle ':completion:*:complete:-command-::commands' ignored-patterns '*\~' + +## filename suffixes to ignore during completion (except after rm command) +zstyle ':completion:*:*:(^rm):*:*files' ignored-patterns '*?.(o|c~|old|pro|zwc)' + +## add colors to processes for kill completion +zstyle ':completion:*:*:kill:*:processes' command 'ps -axco pid,user,command' +zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31'
new file mode 100644 --- /dev/null +++ b/.zsh/50.keybindings.zsh @@ -0,0 +1,6 @@ +bindkey -e +autoload -U history-search-end +zle -N history-beginning-search-backward-end history-search-end +zle -N history-beginning-search-forward-end history-search-end +bindkey '\e[A' history-beginning-search-backward-end +bindkey '\e[B' history-beginning-search-forward-end
new file mode 100644 --- /dev/null +++ b/.zsh/50.misc_functions.zsh @@ -0,0 +1,31 @@ +# Cleanup pyc files in $1 +function clean_pyc() { + local DIR + DIR='.' + if [ "x$1" != "x" ] ; then + DIR=$1 + fi + find $DIR -name \*.pyc -print0 | xargs -0 rm +} + +## Searching stuff +# Function to use mdfind instead of find on Mac OS X +function ffind() { + mdfind -onlyin "$PWD" "kMDItemFSName=$1" +} + +# Function to use grep on a particular filetype in the current dir. +function tgrep() { + egrep -R --include="*.$1" "$2" . +} + + +## Django stuff +function django_settings_set() { + export DJANGO_SETTINGS_MODULE="$1" +} + +# run a django app in spawn +function spawn_django() { + spawn --factory=spawning.django_factory.config_factory $@ +}
new file mode 100644 --- /dev/null +++ b/.zsh/50.vcs_functions.zsh @@ -0,0 +1,126 @@ +## vcs_functions +# This file contains a handful of functions that relate to using some version +# control tool or other. + +# Function to grab the url of an svn wc +function svnurlof() { + local url + url=`svn info $1 2> /dev/null | grep '^URL: ' | sed 's/URL: //'` + if [ x"$url" = "x" ] ; then + url=`git svn info $1 2> /dev/null | grep '^URL: ' | sed 's/URL: //'` + fi + if [ x"$url" = "x" ] ; then + local dir + dir=$1 + if [ x"$dir" = "x" ] ; then + dir="." + fi + url=`hg -R $dir svn info 2> /dev/null | grep '^URL: ' | sed 's/URL: //'` + fi + if [ x"$url" = "x" ] ; then + echo -n 'No repo found (tried svn, git-svn, hgsvnclient)' + fi + echo $url +} + +# Function to clean locks out of svn wcs +function clean_svn_source_trees() { + for aa in */ ; do + pushd $aa > /dev/null + if [ -e .svn ] ; then + echo $aa 'is an svn checkout, cleaning' && svn cleanup + fi + popd > /dev/null + done +} + +# Function to update source trees of known VCS tools in the working dir. +# "known" means ones I'm forced to use on a regular basis +function update_source_trees() { + for aa in */ ; do + pushd $aa > /dev/null + if [ -e .svn ] ; then + echo $aa 'is an svn checkout, updating' + svn up + elif [ -e .hg/svn ] ; then + echo $aa 'is an hg-svn checkout, updating' + hg svn pull + hg svn gentags + hg up + elif [ -e .git/svn ] ; then + echo $aa 'is a git-svn checkout, updating' + git svn fetch + git repack + git gc + elif [ -e .git ] ; then + echo $aa 'is a git checkout, updating' + git pull origin master + git repack + git gc + elif [ -e .hg ] ; then + echo $aa 'is an hg checkout, updating' && hg pull && hg up + elif [ -e _MTN ] ; then + echo $aa "is an mtn co, updating" && mtn pull && mtn up + fi + popd > /dev/null + done +} + +function vcs_current_branch() { + hg branch 2> /dev/null > /dev/null + if [ $? = 0 ] ; then + local br=`hg branch | head -c 10` + local rid=`hg parents | head -n 1 | awk '{print $2}' | head -c 7` + echo "$br $rid" + return 0 + fi + git branch 2> /dev/null > /dev/null + if [ $? = 0 ] ; then + git branch --verbose | grep '^*' | sed 's/* //;s/ /:/;s/ .*//;s/:/ /' + return 0 + fi + # when wc-ng comes out, we'll probably do the following + # svn info 2> /dev/null > /dev/null + if [ -e .svn ] ; then + local d=`svn info | grep URL | sed 's/.*://'` + local br=`echo $d | awk '{ + if ( index($1, "trunk") ) { + print "trunk" + } else { + x = index($1, "branches") ; + if ( x != 0 ) { + sub(".*/branches/", ""); + sub("/.*", ""); + print $0 + } + } + }'` + local rev=`svn info | grep Revision | sed 's/.*: /r/'` + echo $br $rev + return 0 + fi + return 1 +} + +# change to the root dir of the current wc +function wcroot() { + local root=`hg root 2> /dev/null` + if [ x$root != 'x' ] ; then + cd $root + return 0 + fi + git branch 2> /dev/null > /dev/null + if [ $? = 0 ] ; then + while [ ! -e .git ] ; do + cd .. + done + return 0 + fi + if [ -e .svn ] ; then + while [ -e ../.svn ] ; do + cd .. + done + return 0 + fi + echo No working copy found'!' +}
new file mode 100644 --- /dev/null +++ b/.zsh/99.prompt.zsh @@ -0,0 +1,59 @@ +# Magic prompt with colors +# username in blue if not root +# username in red if root - time in pink w/ +# DANGER replacing the date +# failed jobs print exit status in the RPS1 with yellow text +setopt prompt_subst + +preexec () { + currJob="`echo $3 | cut -d ' ' -f 1 | head -n 1`" + if [[ "$TERM" == "screen" ]]; then + hostUser="%10<...<%~%<<" + print -Pn "\ek[$hostUser $currJob]\e\\" + else + if [[ "$FANCYTYPE" == "YES" ]]; then + hostUser="%n@%m: %2~" + print -Pn "\e]0;$hostUser [$currJob]\a" + fi + fi +} + +function right_side_prompt() { + branch=`vcs_current_branch` + if [ $? = 0 ] ; then + echo -n $branch + else + date '+%D %H:%M' + fi +} + +function precmd () { + rps_branch=`right_side_prompt` +} + +if [ "x`whoami`" = "xroot" ] ; then + PS1=$'%{\e];%n@%m: %2~ [zsh]\a%}[%m:%3~] %{\e[31m%}%n%{\e[0m%}%# ' + if [[ "$TERM" == "screen" ]] ; then + PS1=$'%{\ek[%2~ zsh]\e\\%}[%m:%3~] %{\e[31m%}%n%{\e[0m%}%# ' + fi + RPS1=$'%{\e[1;35m%}[DANGER %t]%{\e[0m%}%(?..%{\e[1;33m%} %?%{\e[1;0m%})' +else + PS1=$'%{\e];%n@%m: %2~ [zsh]\a%}[%m:%3~] %{\e[34m%}%n%{\e[0m%}%# ' + if [[ "$TERM" == "screen" ]]; then + PS1=$'%{\ek[%2~ zsh]\e\\%}[%m:%3~] %{\e[34m%}%n%{\e[0m%}%# ' + fi + RPS1=$'[$rps_branch]%(?..%{\e[1;33m%} %?%{\e[0m%})' +fi + +if [ "$TERM" = "rxvt" ] || [ "$TERM" = "xterm" ] \ + || [ "$TERM" = "xterm-color" ] || [ "$TERM" = "screen" ]; then + export FANCYTYPE="YES" +else + export FANCYTYPE="NO" + prompt='[%m:%3~] %n%# ' + RPS1=$'[%W %t]' +fi + +#nonfancy no-frills prompt - disabled (same as the "non-fancy" one above) +#prompt='[%m:%3~] %n%# ' +#RPS1=$'[%W %t]'
new file mode 100644 --- /dev/null +++ b/.zshenv @@ -0,0 +1,63 @@ +# generic settings +ulimit -c 0 # no core dumps +setopt CORRECT +setopt autolist # List tab-complete opts after the first tab +setopt c_bases +setopt autocd # Allows one to type just a dir name to cd to that dir +complete='enhance' +WORDCHARS=${WORDCHARS//[\/.]} +setopt no_beep # don't beep about stuff + +# History Settings +HISTFILE=~/.zhistory +setopt histignoredups +HISTSIZE='10000' +SAVEHIST='10000' +setopt extended_history +setopt hist_save_no_dups # don't save duplicates in history +setopt inc_append_history # append to history file so multiple processes DTRT + +# Get me colors in ls +export CLICOLOR="yes" +export LSCOLORS="exgxcxDxCxEGEDcbcgExEx" + +# Generic environment variables +export PAGER='less -M -X' +export EDITOR='vim' +export SVN_MERGE='svn-hgmerge.py' +export CVS_RSH='ssh' +export LESS="-R" + +source $HOME/.zsh/00.path_manipulation.zsh +insert_path_element ~/unixSoft/bin +unset MANPATH # smart man(1)s autodetect that from PATH. + +# Python stuff +export PYTHONSTARTUP="$HOME/.python/startup.py" + +local MYPYTHONPATH="$HOME/unixSoft/lib/python:/opt/durin/lib/svn-python" +if [[ "$PYTHONPATH" == "" ]] ; then + export PYTHONPATH="$MYPYTHONPATH" +else + export PYTHONPATH="$PYTHONPATH:$MYPYTHONPATH" +fi + +# prep to parse the zshenv-machine +if [[ "x$TZ" == "x" ]] ; then + export MACHINE_TZ="xNoTimeZone" + local DEFAULT_TZ="America/Detroit" +fi + +# Do this right before the timezone stuff +if [[ -a ~/.zshenv-machine ]]; then + source ~/.zshenv-machine +fi + +# do this last so that we can tell if .zshenv-machine set the timezone +if [[ "x$TZ" == "x" ]] ; then + if [ "$MACHINE_TZ" = "xNoTimeZone" ]; then + export TZ="$DEFAULT_TZ" + else + export TZ="$MACHINE_TZ" + fi +fi
new file mode 100644 --- /dev/null +++ b/.zshrc @@ -0,0 +1,29 @@ +# Augie Fackler's zshrc file + +for file in $(ls $HOME/.zsh) ; do + source $HOME/.zsh/$file +done + +# Login greeting ------------------ + +if [ ! "$SHOWED_SCREEN_MESSAGE" = "true" ]; then + if which screen > /dev/null; then + detached_screens=`screen -list | grep Detached | sed -e 's/ (Detached)//'` + if [ ! -z "$detached_screens" ]; then + echo "+---------------------------------------+" + echo "| Detached screens are available: |" + echo "$detached_screens" + echo "+---------------------------------------+" + fi + fi + export SHOWED_SCREEN_MESSAGE="true" +fi + +# If available, source private extensions stored in a different repo +if [[ -a ~/.private/zshrc ]]; then + source ~/.private/zshrc +fi + +if [[ -a ~/.zshrc-machine ]]; then + source ~/.zshrc-machine +fi
new file mode 100755 --- /dev/null +++ b/unixSoft/bin/OCLC-to-bibtex.awk @@ -0,0 +1,244 @@ +# +# OCLC-to-bibtex.awk is an GAWK script to convert the export format of the +# OCLC databases to BibTeX. It processes the input and tries to convert it into +# BibTeX entries which are written to a file in /tmp. This file is then opened +# using the program specified in "prog" (by default: emacsclient). +# +# NOTE: It does not do an extensive job of testing what kind of publications +# are being processed. It has some rudimentary checks of discovering wether the +# processed publications are either InBook's or Articles. +# +# Hedderik van Rijn, 020912-020914 +# +# Do whatever you want with this script, but if you improve it, please send me a copy! +# email: hvr-OCLC@van-rijn.org +# + +BEGIN { + tmpfile = "/tmp/tobib." systime() ".tmp.bib"; + oclc-version = "OLCL-to-bibtex v0.1"; + + # External interactive progs +# prog = "xless "; + prog = "emacsclient "; +# prog = "open -a TextEdit "; + atEnd = "&"; + + # (Indirect) Output to stdout +# prog = "cat "; +# atEnd = ""; + + + print "# Exported from the OLCL FirstSearch PsychINFO database using" olcl-version; + + + +} + +/* ------------------------------------------------------------------------- */ + +(match($1,/[A-Za-z]+:/) || match($2,/[A-Za-z]+:/)) { + + if (inDescriptor == 1) { + keywords = keywords "}"; + inDescriptor = 0; + } + if (inAbstract == 1) { + abstract = abstract "}"; + inAbstract = 0; + } +} + +(!match($1,/[A-Za-z()]+:/) && !match($2,/[A-Za-z()]+:/)) { + + if (inDescriptor == 1) { + keywords = keywords ", " $0; + } + + if (inAbstract == 1) { + abstract = abstract " " $0; + } +} + + + +$1 == "Author(s):" { + author = "\tauthor = {"; + gsub(/Affiliation:.*/,"") + firstauthor = 1; + for (i=2;i<=NF;i++) { + if ($i == ";") { + $i = "and"; + firstauthor = 0; + } + author = author $i; + if (firstauthor) { + mainauthor = mainauthor tolower($i); + } + if (match($i,",")) { + firstauthor = 0; + } + + if (i<NF) { + author = author " "; + } + } + author = author "}"; + gsub(",","",mainauthor) + +} + +$1 == "Descriptor:" { + inDescriptor = 1; + gsub(/Descriptor:[ \t]+/,"") + gsub(/\(Major\):[ \t]+/,"") + keywords = "\tkeywords = {{" $0; +} + +$1 == "Identifier:" { + descriptor = 0; + gsub(/Identifier:[ \t]+/,"") + keywords = keywords "{" $0 "}}"; +} + +$1 == "Source:" { + if ($2 == "In:") { + type = 1; # In Book + + pages = "\tpages = {" $NF "}"; + gsub("-","--",pages) + + booktitle = ""; + for (i=NF-2;$i != "Ed;";i--) { + if (booktitle == "") { + booktitle = $i; + } else { + booktitle = $i " " booktitle; + } + } + gsub(";","",booktitle); + booktitle = "\tbooktitle = {" booktitle "}"; + gsub("\\.}","}",booktitle); + + + editors = ""; + for (;i > 2;i--) { + if (editors == "") { + editors = $i; + } else { + editors = $i " " editors; + } + } + gsub(" Ed;","",editors); + gsub("; "," and ",editors); + gsub(";","",editors); + editors = "\teditors = {" editors "}"; + } else { + type = 2; # Journal + + journal = "\tjournal = {"; + for (i=2;$i!="Vol";i++) { + journal = journal $i " "; + } + journal = journal "}"; + i++; + vol = $i; + sub(/\(.*\),/,"",vol) + volume = "\tvolume = {" vol "}" + sub(/.*\(/,"",$i) + sub(/\),/,"",$i) + number = "\tnumber = {" $i "}" + i++; + if ($i+1 == 1) { # Skip the month if necessary + i++; + } + sub(",","",$i); + year = "\tyear = {" $i "}"; + sub("[0-9][0-9]","",$i); + mainyear = $i; + + pages = "\tpages = {" $NF "}"; + gsub("-","--",pages) + gsub("\\.","",pages) + + } + +} + +$1 == "Title:" { + title = "\ttitle = {"; + for (i=2;i<=NF;i++) { + if ($i == toupper($i)) { + $i = "{" $i "}"; + } else { + gsub(/[A-Z]/,"{&}",$i); + } + title = title ($i); + if (i<NF) { + title = title " "; + } + } + title = title "}"; + gsub("\\.}","}",title); + +} + +$1 == "Abstract:" { + gsub(/Abstract:[ \t]*/,"") + abstract = "\tabstract = {" $0; + inAbstract = 1; +} + +## Use the Accession No: for the year if the year has not been found yet. + +$1 == "Accession" { + if (mainyear == "") { + gsub(/-.*/,"",$3); + year = "\tyear = {" $3 " (had to use heuristics to determine the year!)}"; + + sub("[0-9][0-9]","",$3); + mainyear = $3 "?"; + } + +} + +function printEntry() { + if (mainauthor != "") { + if (type == 1) { # In Book + typestring = "InBook"; + } else { + typestring = "Article"; + } + print("@" typestring "{" mainauthor ":" mainyear "x,") >> tmpfile; + print(author ",") >> tmpfile; + print(title ",") >> tmpfile; + print(year ",") >> tmpfile; + if (type == 1) { # In Book + print(booktitle ",") >> tmpfile; + print(editors ",") >> tmpfile; + print(pages ",") >> tmpfile; + } + if (type == 2) { # Article + print(journal ",") >> tmpfile; + print(volume ",") >> tmpfile; + print(number ",") >> tmpfile; + print(pages ",") >> tmpfile; + } + print(abstract ",") >> tmpfile; + print(keywords) >> tmpfile; + print("}") >> tmpfile; + print("") >> tmpfile; + print("") >> tmpfile; + } + mainauthor = ""; + mainyear = ""; +} + +NF == 0 { + printEntry(); +} + +END { + printEntry(); + system(prog " " tmpfile " " atEnd); +}
new file mode 100755 --- /dev/null +++ b/unixSoft/bin/OCLC-to-bibtex.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +gawk -f /Users/durin/unixSoft/bin/OCLC-to-bibtex.awk $@ +
new file mode 100755 --- /dev/null +++ b/unixSoft/bin/change-svn-wc-format.py @@ -0,0 +1,375 @@ +#!/usr/bin/env python +# +# change-svn-wc-format.py: Change the format of a Subversion working copy. +# +# ==================================================================== +# Copyright (c) 2007-2008 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +# This software consists of voluntary contributions made by many +# individuals. For exact contribution history, see the revision +# history and logs, available at http://subversion.tigris.org/. +# ==================================================================== + +import sys +import os +import getopt +try: + my_getopt = getopt.gnu_getopt +except AttributeError: + my_getopt = getopt.getopt + +# Pretend we have true booleans on older python versions +try: + True +except: + True = 1 + False = 0 + +### The entries file parser in subversion/tests/cmdline/svntest/entry.py +### handles the XML-based WC entries file format used by Subversion +### 1.3 and lower. It could be rolled into this script. + +LATEST_FORMATS = { "1.4" : 8, + "1.5" : 9, + "1.6" : 10, + } + +def usage_and_exit(error_msg=None): + """Write usage information and exit. If ERROR_MSG is provide, that + error message is printed first (to stderr), the usage info goes to + stderr, and the script exits with a non-zero status. Otherwise, + usage info goes to stdout and the script exits with a zero status.""" + progname = os.path.basename(sys.argv[0]) + + stream = error_msg and sys.stderr or sys.stdout + if error_msg: + print >> stream, "ERROR: %s\n" % error_msg + print >> stream, """\ +usage: %s WC_PATH SVN_VERSION [--verbose] [--force] [--skip-unknown-format] + %s --help + +Change the format of a Subversion working copy to that of SVN_VERSION. + + --skip-unknown-format : skip directories with unknown working copy + format and continue the update +""" % (progname, progname) + sys.exit(error_msg and 1 or 0) + +def get_adm_dir(): + """Return the name of Subversion's administrative directory, + adjusted for the SVN_ASP_DOT_NET_HACK environment variable. See + <http://svn.collab.net/repos/svn/trunk/notes/asp-dot-net-hack.txt> + for details.""" + return "SVN_ASP_DOT_NET_HACK" in os.environ and "_svn" or ".svn" + +class WCFormatConverter: + "Performs WC format conversions." + root_path = None + error_on_unrecognized = True + force = False + verbosity = 0 + + def write_dir_format(self, format_nbr, dirname, paths): + """Attempt to write the WC format FORMAT_NBR to the entries file + for DIRNAME. Throws LossyConversionException when not in --force + mode, and unconvertable WC data is encountered.""" + + # Avoid iterating in unversioned directories. + if not (get_adm_dir() in paths): + del paths[:] + return + + # Process the entries file for this versioned directory. + if self.verbosity: + print "Processing directory '%s'" % dirname + entries = Entries(os.path.join(dirname, get_adm_dir(), "entries")) + + if self.verbosity: + print "Parsing file '%s'" % entries.path + try: + entries.parse(self.verbosity) + except UnrecognizedWCFormatException, e: + if self.error_on_unrecognized: + raise + print >>sys.stderr, "%s, skipping" % (e,) + + if self.verbosity: + print "Checking whether WC format can be converted" + try: + entries.assert_valid_format(format_nbr, self.verbosity) + except LossyConversionException, e: + # In --force mode, ignore complaints about lossy conversion. + if self.force: + print "WARNING: WC format conversion will be lossy. Dropping "\ + "field(s) %s " % ", ".join(e.lossy_fields) + else: + raise + + if self.verbosity: + print "Writing WC format" + entries.write_format(format_nbr) + + def change_wc_format(self, format_nbr): + """Walk all paths in a WC tree, and change their format to + FORMAT_NBR. Throw LossyConversionException or NotImplementedError + if the WC format should not be converted, or is unrecognized.""" + os.path.walk(self.root_path, self.write_dir_format, format_nbr) + +class Entries: + """Represents a .svn/entries file. + + 'The entries file' section in subversion/libsvn_wc/README is a + useful reference.""" + + # The name and index of each field composing an entry's record. + entry_fields = ( + "name", + "kind", + "revision", + "url", + "repos", + "schedule", + "text-time", + "checksum", + "committed-date", + "committed-rev", + "last-author", + "has-props", + "has-prop-mods", + "cachable-props", + "present-props", + "conflict-old", + "conflict-new", + "conflict-wrk", + "prop-reject-file", + "copied", + "copyfrom-url", + "copyfrom-rev", + "deleted", + "absent", + "incomplete", + "uuid", + "lock-token", + "lock-owner", + "lock-comment", + "lock-creation-date", + "changelist", + "keep-local", + "working-size", + "depth", + "tree-conflicts", + "file-external", + ) + + # The format number. + format_nbr = -1 + + # How many bytes the format number takes in the file. (The format number + # may have leading zeroes after using this script to convert format 10 to + # format 9 -- which would write the format number as '09'.) + format_nbr_bytes = -1 + + def __init__(self, path): + self.path = path + self.entries = [] + + def parse(self, verbosity=0): + """Parse the entries file. Throw NotImplementedError if the WC + format is unrecognized.""" + + input = open(self.path, "r") + + # Read WC format number from INPUT. Validate that it + # is a supported format for conversion. + format_line = input.readline() + try: + self.format_nbr = int(format_line) + self.format_nbr_bytes = len(format_line.rstrip()) # remove '\n' + except ValueError: + self.format_nbr = -1 + self.format_nbr_bytes = -1 + if not self.format_nbr in LATEST_FORMATS.values(): + raise UnrecognizedWCFormatException(self.format_nbr, self.path) + + # Parse file into individual entries, to later inspect for + # non-convertable data. + entry = None + while True: + entry = self.parse_entry(input, verbosity) + if entry is None: + break + self.entries.append(entry) + + input.close() + + def assert_valid_format(self, format_nbr, verbosity=0): + if verbosity >= 2: + print "Validating format for entries file '%s'" % self.path + for entry in self.entries: + if verbosity >= 3: + print "Validating format for entry '%s'" % entry.get_name() + try: + entry.assert_valid_format(format_nbr) + except LossyConversionException: + if verbosity >= 3: + print >> sys.stderr, "Offending entry:" + print >> sys.stderr, str(entry) + raise + + def parse_entry(self, input, verbosity=0): + "Read an individual entry from INPUT stream." + entry = None + + while True: + line = input.readline() + if line in ("", "\x0c\n"): + # EOF or end of entry terminator encountered. + break + + if entry is None: + entry = Entry() + + # Retain the field value, ditching its field terminator ("\x0a"). + entry.fields.append(line[:-1]) + + if entry is not None and verbosity >= 3: + sys.stdout.write(str(entry)) + print "-" * 76 + return entry + + def write_format(self, format_nbr): + # Overwrite all bytes of the format number (which are the first bytes in + # the file). Overwrite format '10' by format '09', which will be converted + # to '9' by Subversion when it rewrites the file. (Subversion 1.4 and later + # ignore leading zeroes in the format number.) + assert len(str(format_nbr)) <= self.format_nbr_bytes + format_string = '%0' + str(self.format_nbr_bytes) + 'd' + + os.chmod(self.path, 0600) + output = open(self.path, "r+", 0) + output.write(format_string % format_nbr) + output.close() + os.chmod(self.path, 0400) + +class Entry: + "Describes an entry in a WC." + + # Maps format numbers to indices of fields within an entry's record that must + # be retained when downgrading to that format. + must_retain_fields = { + # Not in 1.4: changelist, keep-local, depth, tree-conflicts, file-externals + 8 : (30, 31, 33, 34, 35), + # Not in 1.5: tree-conflicts, file-externals + 9 : (34, 35), + } + + def __init__(self): + self.fields = [] + + def assert_valid_format(self, format_nbr): + "Assure that conversion will be non-lossy by examining fields." + + # Check whether lossy conversion is being attempted. + lossy_fields = [] + for field_index in self.must_retain_fields[format_nbr]: + if len(self.fields) - 1 >= field_index and self.fields[field_index]: + lossy_fields.append(Entries.entry_fields[field_index]) + if lossy_fields: + raise LossyConversionException(lossy_fields, + "Lossy WC format conversion requested for entry '%s'\n" + "Data for the following field(s) is unsupported by older versions " + "of\nSubversion, and is likely to be subsequently discarded, and/or " + "have\nunexpected side-effects: %s\n\n" + "WC format conversion was cancelled, use the --force option to " + "override\nthe default behavior." + % (self.get_name(), ", ".join(lossy_fields))) + + def get_name(self): + "Return the name of this entry." + return len(self.fields) > 0 and self.fields[0] or "" + + def __str__(self): + "Return all fields from this entry as a multi-line string." + rep = "" + for i in range(0, len(self.fields)): + rep += "[%s] %s\n" % (Entries.entry_fields[i], self.fields[i]) + return rep + + +class LocalException(Exception): + """Root of local exception class hierarchy.""" + pass + +class LossyConversionException(LocalException): + "Exception thrown when a lossy WC format conversion is requested." + def __init__(self, lossy_fields, str): + self.lossy_fields = lossy_fields + self.str = str + def __str__(self): + return self.str + +class UnrecognizedWCFormatException(LocalException): + def __init__(self, format, path): + self.format = format + self.path = path + def __str__(self): + return "Unrecognized WC format %d in '%s'" % (self.format, self.path) + + +def main(): + try: + opts, args = my_getopt(sys.argv[1:], "vh?", + ["debug", "force", "skip-unknown-format", + "verbose", "help"]) + except: + usage_and_exit("Unable to process arguments/options") + + converter = WCFormatConverter() + + # Process arguments. + if len(args) == 2: + converter.root_path = args[0] + svn_version = args[1] + else: + usage_and_exit() + + # Process options. + debug = False + for opt, value in opts: + if opt in ("--help", "-h", "-?"): + usage_and_exit() + elif opt == "--force": + converter.force = True + elif opt == "--skip-unknown-format": + converter.error_on_unrecognized = False + elif opt in ("--verbose", "-v"): + converter.verbosity += 1 + elif opt == "--debug": + debug = True + else: + usage_and_exit("Unknown option '%s'" % opt) + + try: + new_format_nbr = LATEST_FORMATS[svn_version] + except KeyError: + usage_and_exit("Unsupported version number '%s'" % svn_version) + + try: + converter.change_wc_format(new_format_nbr) + except LocalException, e: + if debug: + raise + print >> sys.stderr, str(e) + sys.exit(1) + + print "Converted WC at '%s' into format %d for Subversion %s" % \ + (converter.root_path, new_format_nbr, svn_version) + +if __name__ == "__main__": + main()
new file mode 100755 --- /dev/null +++ b/unixSoft/bin/em.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# desc: +# Allows stdin to be 'piped' to an emacs server. +# +# options: +# none +# +# usage: +# $ echo "hello there" | emacsclientw.sh +# $ cat ~/.emacs | emacsclientw.sh +# $ emacsclientw.sh ~/.emacs +# +# author: +# Phil Jackson (phil@shellarchive.co.uk) + +unset DISPLAY + +tmp_file="$(mktemp /tmp/emacs.tmp.XXXXX)" +lisp_to_accept_file="(~/unixSoft/emacs/fake-stdin-slurp.el \"${tmp_file}\")" + +if [ ! -t 0 ]; then + cat > "${tmp_file}" + + emacsclient -a emacs -e "${lisp_to_accept_file}" ${@} + + if [ ${?} -ne 0 ]; then + echo "failed: your input was saved in '${tmp_file}'" + else + rm -f "${tmp_file}" + fi +else + # nothing from stdin + emacsclient -n -a emacs ${@} +fi
new file mode 100755 --- /dev/null +++ b/unixSoft/bin/hgk @@ -0,0 +1,3683 @@ +#!/usr/bin/env wish + +# Copyright (C) 2005 Paul Mackerras. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or (at your option) any later version. + +proc gitdir {} { + global env + if {[info exists env(GIT_DIR)]} { + return $env(GIT_DIR) + } else { + return ".hg" + } +} + +proc getcommits {rargs} { + global commits commfd phase canv mainfont env + global startmsecs nextupdate ncmupdate + global ctext maincursor textcursor leftover + + # check that we can find a .git directory somewhere... + set gitdir [gitdir] + if {![file isdirectory $gitdir]} { + error_popup "Cannot find the git directory \"$gitdir\"." + exit 1 + } + set commits {} + set phase getcommits + set startmsecs [clock clicks -milliseconds] + set nextupdate [expr $startmsecs + 100] + set ncmupdate 1 + set limit 0 + set revargs {} + for {set i 0} {$i < [llength $rargs]} {incr i} { + set opt [lindex $rargs $i] + if {$opt == "--limit"} { + incr i + set limit [lindex $rargs $i] + } else { + lappend revargs $opt + } + } + if [catch { + set parse_args [concat --default HEAD $revargs] + set parse_temp [eval exec {$env(HG)} debug-rev-parse $parse_args] + regsub -all "\r\n" $parse_temp "\n" parse_temp + set parsed_args [split $parse_temp "\n"] + } err] { + # if git-rev-parse failed for some reason... + if {$rargs == {}} { + set revargs HEAD + } + set parsed_args $revargs + } + if {$limit > 0} { + set parsed_args [concat -n $limit $parsed_args] + } + if [catch { + set commfd [open "|{$env(HG)} debug-rev-list --header --topo-order --parents $parsed_args" r] + } err] { + puts stderr "Error executing hg debug-rev-list: $err" + exit 1 + } + set leftover {} + fconfigure $commfd -blocking 0 -translation lf + fileevent $commfd readable [list getcommitlines $commfd] + $canv delete all + $canv create text 3 3 -anchor nw -text "Reading commits..." \ + -font $mainfont -tags textitems + . config -cursor watch + settextcursor watch +} + +proc getcommitlines {commfd} { + global commits parents cdate children + global commitlisted phase commitinfo nextupdate + global stopped redisplaying leftover + + set stuff [read $commfd] + if {$stuff == {}} { + if {![eof $commfd]} return + # set it blocking so we wait for the process to terminate + fconfigure $commfd -blocking 1 + if {![catch {close $commfd} err]} { + after idle finishcommits + return + } + if {[string range $err 0 4] == "usage"} { + set err \ +{Gitk: error reading commits: bad arguments to git-rev-list. +(Note: arguments to gitk are passed to git-rev-list +to allow selection of commits to be displayed.)} + } else { + set err "Error reading commits: $err" + } + error_popup $err + exit 1 + } + set start 0 + while 1 { + set i [string first "\0" $stuff $start] + if {$i < 0} { + append leftover [string range $stuff $start end] + return + } + set cmit [string range $stuff $start [expr {$i - 1}]] + if {$start == 0} { + set cmit "$leftover$cmit" + set leftover {} + } + set start [expr {$i + 1}] + regsub -all "\r\n" $cmit "\n" cmit + set j [string first "\n" $cmit] + set ok 0 + if {$j >= 0} { + set ids [string range $cmit 0 [expr {$j - 1}]] + set ok 1 + foreach id $ids { + if {![regexp {^[0-9a-f]{12}$} $id]} { + set ok 0 + break + } + } + } + if {!$ok} { + set shortcmit $cmit + if {[string length $shortcmit] > 80} { + set shortcmit "[string range $shortcmit 0 80]..." + } + error_popup "Can't parse hg debug-rev-list output: {$shortcmit}" + exit 1 + } + set id [lindex $ids 0] + set olds [lrange $ids 1 end] + set cmit [string range $cmit [expr {$j + 1}] end] + lappend commits $id + set commitlisted($id) 1 + parsecommit $id $cmit 1 [lrange $ids 1 end] + drawcommit $id + if {[clock clicks -milliseconds] >= $nextupdate} { + doupdate 1 + } + while {$redisplaying} { + set redisplaying 0 + if {$stopped == 1} { + set stopped 0 + set phase "getcommits" + foreach id $commits { + drawcommit $id + if {$stopped} break + if {[clock clicks -milliseconds] >= $nextupdate} { + doupdate 1 + } + } + } + } + } +} + +proc doupdate {reading} { + global commfd nextupdate numcommits ncmupdate + + if {$reading} { + fileevent $commfd readable {} + } + update + set nextupdate [expr {[clock clicks -milliseconds] + 100}] + if {$numcommits < 100} { + set ncmupdate [expr {$numcommits + 1}] + } elseif {$numcommits < 10000} { + set ncmupdate [expr {$numcommits + 10}] + } else { + set ncmupdate [expr {$numcommits + 100}] + } + if {$reading} { + fileevent $commfd readable [list getcommitlines $commfd] + } +} + +proc readcommit {id} { + global env + if [catch {set contents [exec $env(HG) debug-cat-file commit $id]}] return + parsecommit $id $contents 0 {} +} + +proc parsecommit {id contents listed olds} { + global commitinfo children nchildren parents nparents cdate ncleft + + set inhdr 1 + set comment {} + set headline {} + set auname {} + set audate {} + set comname {} + set comdate {} + set rev {} + if {![info exists nchildren($id)]} { + set children($id) {} + set nchildren($id) 0 + set ncleft($id) 0 + } + set parents($id) $olds + set nparents($id) [llength $olds] + foreach p $olds { + if {![info exists nchildren($p)]} { + set children($p) [list $id] + set nchildren($p) 1 + set ncleft($p) 1 + } elseif {[lsearch -exact $children($p) $id] < 0} { + lappend children($p) $id + incr nchildren($p) + incr ncleft($p) + } + } + regsub -all "\r\n" $contents "\n" contents + foreach line [split $contents "\n"] { + if {$inhdr} { + set line [split $line] + if {$line == {}} { + set inhdr 0 + } else { + set tag [lindex $line 0] + if {$tag == "author"} { + set x [expr {[llength $line] - 2}] + set audate [lindex $line $x] + set auname [join [lrange $line 1 [expr {$x - 1}]]] + } elseif {$tag == "committer"} { + set x [expr {[llength $line] - 2}] + set comdate [lindex $line $x] + set comname [join [lrange $line 1 [expr {$x - 1}]]] + } elseif {$tag == "revision"} { + set rev [lindex $line 1] + } + } + } else { + if {$comment == {}} { + set headline [string trim $line] + } else { + append comment "\n" + } + if {!$listed} { + # git-rev-list indents the comment by 4 spaces; + # if we got this via git-cat-file, add the indentation + append comment " " + } + append comment $line + } + } + if {$audate != {}} { + set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"] + } + if {$comdate != {}} { + set cdate($id) $comdate + set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"] + } + set commitinfo($id) [list $headline $auname $audate \ + $comname $comdate $comment $rev] +} + +proc readrefs {} { + global tagids idtags headids idheads tagcontents env + + set tags [exec $env(HG) tags] + regsub -all "\r\n" $tags "\n" tags + set lines [split $tags "\n"] + foreach f $lines { + regexp {(\S+)$} $f full + regsub {\s+(\S+)$} $f "" direct + set sha [split $full ':'] + set tag [lindex $sha 1] + lappend tagids($direct) $tag + lappend idtags($tag) $direct + } +} + +proc readotherrefs {base dname excl} { + global otherrefids idotherrefs + + set git [gitdir] + set files [glob -nocomplain -types f [file join $git $base *]] + foreach f $files { + catch { + set fd [open $f r] + set line [read $fd 40] + if {[regexp {^[0-9a-f]{12}} $line id]} { + set name "$dname[file tail $f]" + set otherrefids($name) $id + lappend idotherrefs($id) $name + } + close $fd + } + } + set dirs [glob -nocomplain -types d [file join $git $base *]] + foreach d $dirs { + set dir [file tail $d] + if {[lsearch -exact $excl $dir] >= 0} continue + readotherrefs [file join $base $dir] "$dname$dir/" {} + } +} + +proc error_popup msg { + set w .error + toplevel $w + wm transient $w . + message $w.m -text $msg -justify center -aspect 400 + pack $w.m -side top -fill x -padx 20 -pady 20 + button $w.ok -text OK -command "destroy $w" + pack $w.ok -side bottom -fill x + bind $w <Visibility> "grab $w; focus $w" + tkwait window $w +} + +proc makewindow {} { + global canv canv2 canv3 linespc charspc ctext cflist textfont + global findtype findtypemenu findloc findstring fstring geometry + global entries sha1entry sha1string sha1but + global maincursor textcursor curtextcursor + global rowctxmenu gaudydiff mergemax + + menu .bar + .bar add cascade -label "File" -menu .bar.file + menu .bar.file + .bar.file add command -label "Reread references" -command rereadrefs + .bar.file add command -label "Quit" -command doquit + menu .bar.help + .bar add cascade -label "Help" -menu .bar.help + .bar.help add command -label "About gitk" -command about + . configure -menu .bar + + if {![info exists geometry(canv1)]} { + set geometry(canv1) [expr 45 * $charspc] + set geometry(canv2) [expr 30 * $charspc] + set geometry(canv3) [expr 15 * $charspc] + set geometry(canvh) [expr 25 * $linespc + 4] + set geometry(ctextw) 80 + set geometry(ctexth) 30 + set geometry(cflistw) 30 + } + panedwindow .ctop -orient vertical + if {[info exists geometry(width)]} { + .ctop conf -width $geometry(width) -height $geometry(height) + set texth [expr {$geometry(height) - $geometry(canvh) - 56}] + set geometry(ctexth) [expr {($texth - 8) / + [font metrics $textfont -linespace]}] + } + frame .ctop.top + frame .ctop.top.bar + pack .ctop.top.bar -side bottom -fill x + set cscroll .ctop.top.csb + scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0 + pack $cscroll -side right -fill y + panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4 + pack .ctop.top.clist -side top -fill both -expand 1 + .ctop add .ctop.top + set canv .ctop.top.clist.canv + canvas $canv -height $geometry(canvh) -width $geometry(canv1) \ + -bg white -bd 0 \ + -yscrollincr $linespc -yscrollcommand "$cscroll set" -selectbackground grey + .ctop.top.clist add $canv + set canv2 .ctop.top.clist.canv2 + canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \ + -bg white -bd 0 -yscrollincr $linespc -selectbackground grey + .ctop.top.clist add $canv2 + set canv3 .ctop.top.clist.canv3 + canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \ + -bg white -bd 0 -yscrollincr $linespc -selectbackground grey + .ctop.top.clist add $canv3 + bind .ctop.top.clist <Configure> {resizeclistpanes %W %w} + + set sha1entry .ctop.top.bar.sha1 + set entries $sha1entry + set sha1but .ctop.top.bar.sha1label + button $sha1but -text "SHA1 ID: " -state disabled -relief flat \ + -command gotocommit -width 8 + $sha1but conf -disabledforeground [$sha1but cget -foreground] + pack .ctop.top.bar.sha1label -side left + entry $sha1entry -width 40 -font $textfont -textvariable sha1string + trace add variable sha1string write sha1change + pack $sha1entry -side left -pady 2 + + image create bitmap bm-left -data { + #define left_width 16 + #define left_height 16 + static unsigned char left_bits[] = { + 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00, + 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00, + 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01}; + } + image create bitmap bm-right -data { + #define right_width 16 + #define right_height 16 + static unsigned char right_bits[] = { + 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c, + 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c, + 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01}; + } + button .ctop.top.bar.leftbut -image bm-left -command goback \ + -state disabled -width 26 + pack .ctop.top.bar.leftbut -side left -fill y + button .ctop.top.bar.rightbut -image bm-right -command goforw \ + -state disabled -width 26 + pack .ctop.top.bar.rightbut -side left -fill y + + button .ctop.top.bar.findbut -text "Find" -command dofind + pack .ctop.top.bar.findbut -side left + set findstring {} + set fstring .ctop.top.bar.findstring + lappend entries $fstring + entry $fstring -width 30 -font $textfont -textvariable findstring + pack $fstring -side left -expand 1 -fill x + set findtype Exact + set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \ + findtype Exact IgnCase Regexp] + set findloc "All fields" + tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \ + Comments Author Committer Files Pickaxe + pack .ctop.top.bar.findloc -side right + pack .ctop.top.bar.findtype -side right + # for making sure type==Exact whenever loc==Pickaxe + trace add variable findloc write findlocchange + + panedwindow .ctop.cdet -orient horizontal + .ctop add .ctop.cdet + frame .ctop.cdet.left + set ctext .ctop.cdet.left.ctext + text $ctext -bg white -state disabled -font $textfont \ + -width $geometry(ctextw) -height $geometry(ctexth) \ + -yscrollcommand ".ctop.cdet.left.sb set" \ + -xscrollcommand ".ctop.cdet.left.hb set" -wrap none + scrollbar .ctop.cdet.left.sb -command "$ctext yview" + scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview" + pack .ctop.cdet.left.sb -side right -fill y + pack .ctop.cdet.left.hb -side bottom -fill x + pack $ctext -side left -fill both -expand 1 + .ctop.cdet add .ctop.cdet.left + + $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa" + if {$gaudydiff} { + $ctext tag conf hunksep -back blue -fore white + $ctext tag conf d0 -back "#ff8080" + $ctext tag conf d1 -back green + } else { + $ctext tag conf hunksep -fore blue + $ctext tag conf d0 -fore red + $ctext tag conf d1 -fore "#00a000" + $ctext tag conf m0 -fore red + $ctext tag conf m1 -fore blue + $ctext tag conf m2 -fore green + $ctext tag conf m3 -fore purple + $ctext tag conf m4 -fore brown + $ctext tag conf mmax -fore darkgrey + set mergemax 5 + $ctext tag conf mresult -font [concat $textfont bold] + $ctext tag conf msep -font [concat $textfont bold] + $ctext tag conf found -back yellow + } + + frame .ctop.cdet.right + set cflist .ctop.cdet.right.cfiles + listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \ + -yscrollcommand ".ctop.cdet.right.sb set" + scrollbar .ctop.cdet.right.sb -command "$cflist yview" + pack .ctop.cdet.right.sb -side right -fill y + pack $cflist -side left -fill both -expand 1 + .ctop.cdet add .ctop.cdet.right + bind .ctop.cdet <Configure> {resizecdetpanes %W %w} + + pack .ctop -side top -fill both -expand 1 + + bindall <1> {selcanvline %W %x %y} + #bindall <B1-Motion> {selcanvline %W %x %y} + bindall <ButtonRelease-4> "allcanvs yview scroll -5 units" + bindall <ButtonRelease-5> "allcanvs yview scroll 5 units" + bindall <2> "allcanvs scan mark 0 %y" + bindall <B2-Motion> "allcanvs scan dragto 0 %y" + bind . <Key-Up> "selnextline -1" + bind . <Key-Down> "selnextline 1" + bind . <Key-Prior> "allcanvs yview scroll -1 pages" + bind . <Key-Next> "allcanvs yview scroll 1 pages" + bindkey <Key-Delete> "$ctext yview scroll -1 pages" + bindkey <Key-BackSpace> "$ctext yview scroll -1 pages" + bindkey <Key-space> "$ctext yview scroll 1 pages" + bindkey p "selnextline -1" + bindkey n "selnextline 1" + bindkey b "$ctext yview scroll -1 pages" + bindkey d "$ctext yview scroll 18 units" + bindkey u "$ctext yview scroll -18 units" + bindkey / {findnext 1} + bindkey <Key-Return> {findnext 0} + bindkey ? findprev + bindkey f nextfile + bind . <Control-q> doquit + bind . <Control-w> doquit + bind . <Control-f> dofind + bind . <Control-g> {findnext 0} + bind . <Control-r> findprev + bind . <Control-equal> {incrfont 1} + bind . <Control-KP_Add> {incrfont 1} + bind . <Control-minus> {incrfont -1} + bind . <Control-KP_Subtract> {incrfont -1} + bind $cflist <<ListboxSelect>> listboxsel + bind . <Destroy> {savestuff %W} + bind . <Button-1> "click %W" + bind $fstring <Key-Return> dofind + bind $sha1entry <Key-Return> gotocommit + bind $sha1entry <<PasteSelection>> clearsha1 + + set maincursor [. cget -cursor] + set textcursor [$ctext cget -cursor] + set curtextcursor $textcursor + + set rowctxmenu .rowctxmenu + menu $rowctxmenu -tearoff 0 + $rowctxmenu add command -label "Diff this -> selected" \ + -command {diffvssel 0} + $rowctxmenu add command -label "Diff selected -> this" \ + -command {diffvssel 1} + $rowctxmenu add command -label "Make patch" -command mkpatch + $rowctxmenu add command -label "Create tag" -command mktag + $rowctxmenu add command -label "Write commit to file" -command writecommit +} + +# when we make a key binding for the toplevel, make sure +# it doesn't get triggered when that key is pressed in the +# find string entry widget. +proc bindkey {ev script} { + global entries + bind . $ev $script + set escript [bind Entry $ev] + if {$escript == {}} { + set escript [bind Entry <Key>] + } + foreach e $entries { + bind $e $ev "$escript; break" + } +} + +# set the focus back to the toplevel for any click outside +# the entry widgets +proc click {w} { + global entries + foreach e $entries { + if {$w == $e} return + } + focus . +} + +proc savestuff {w} { + global canv canv2 canv3 ctext cflist mainfont textfont + global stuffsaved findmergefiles gaudydiff maxgraphpct + global maxwidth + + if {$stuffsaved} return + if {![winfo viewable .]} return + catch { + set f [open "~/.gitk-new" w] + puts $f [list set mainfont $mainfont] + puts $f [list set textfont $textfont] + puts $f [list set findmergefiles $findmergefiles] + puts $f [list set gaudydiff $gaudydiff] + puts $f [list set maxgraphpct $maxgraphpct] + puts $f [list set maxwidth $maxwidth] + puts $f "set geometry(width) [winfo width .ctop]" + puts $f "set geometry(height) [winfo height .ctop]" + puts $f "set geometry(canv1) [expr [winfo width $canv]-2]" + puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]" + puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]" + puts $f "set geometry(canvh) [expr [winfo height $canv]-2]" + set wid [expr {([winfo width $ctext] - 8) \ + / [font measure $textfont "0"]}] + puts $f "set geometry(ctextw) $wid" + set wid [expr {([winfo width $cflist] - 11) \ + / [font measure [$cflist cget -font] "0"]}] + puts $f "set geometry(cflistw) $wid" + close $f + file rename -force "~/.gitk-new" "~/.gitk" + } + set stuffsaved 1 +} + +proc resizeclistpanes {win w} { + global oldwidth + if [info exists oldwidth($win)] { + set s0 [$win sash coord 0] + set s1 [$win sash coord 1] + if {$w < 60} { + set sash0 [expr {int($w/2 - 2)}] + set sash1 [expr {int($w*5/6 - 2)}] + } else { + set factor [expr {1.0 * $w / $oldwidth($win)}] + set sash0 [expr {int($factor * [lindex $s0 0])}] + set sash1 [expr {int($factor * [lindex $s1 0])}] + if {$sash0 < 30} { + set sash0 30 + } + if {$sash1 < $sash0 + 20} { + set sash1 [expr $sash0 + 20] + } + if {$sash1 > $w - 10} { + set sash1 [expr $w - 10] + if {$sash0 > $sash1 - 20} { + set sash0 [expr $sash1 - 20] + } + } + } + $win sash place 0 $sash0 [lindex $s0 1] + $win sash place 1 $sash1 [lindex $s1 1] + } + set oldwidth($win) $w +} + +proc resizecdetpanes {win w} { + global oldwidth + if [info exists oldwidth($win)] { + set s0 [$win sash coord 0] + if {$w < 60} { + set sash0 [expr {int($w*3/4 - 2)}] + } else { + set factor [expr {1.0 * $w / $oldwidth($win)}] + set sash0 [expr {int($factor * [lindex $s0 0])}] + if {$sash0 < 45} { + set sash0 45 + } + if {$sash0 > $w - 15} { + set sash0 [expr $w - 15] + } + } + $win sash place 0 $sash0 [lindex $s0 1] + } + set oldwidth($win) $w +} + +proc allcanvs args { + global canv canv2 canv3 + eval $canv $args + eval $canv2 $args + eval $canv3 $args +} + +proc bindall {event action} { + global canv canv2 canv3 + bind $canv $event $action + bind $canv2 $event $action + bind $canv3 $event $action +} + +proc about {} { + set w .about + if {[winfo exists $w]} { + raise $w + return + } + toplevel $w + wm title $w "About gitk" + message $w.m -text { +Gitk version 1.2 + +Copyright � 2005 Paul Mackerras + +Use and redistribute under the terms of the GNU General Public License} \ + -justify center -aspect 400 + pack $w.m -side top -fill x -padx 20 -pady 20 + button $w.ok -text Close -command "destroy $w" + pack $w.ok -side bottom +} + +proc assigncolor {id} { + global commitinfo colormap commcolors colors nextcolor + global parents nparents children nchildren + global cornercrossings crossings + + if [info exists colormap($id)] return + set ncolors [llength $colors] + if {$nparents($id) <= 1 && $nchildren($id) == 1} { + set child [lindex $children($id) 0] + if {[info exists colormap($child)] + && $nparents($child) == 1} { + set colormap($id) $colormap($child) + return + } + } + set badcolors {} + if {[info exists cornercrossings($id)]} { + foreach x $cornercrossings($id) { + if {[info exists colormap($x)] + && [lsearch -exact $badcolors $colormap($x)] < 0} { + lappend badcolors $colormap($x) + } + } + if {[llength $badcolors] >= $ncolors} { + set badcolors {} + } + } + set origbad $badcolors + if {[llength $badcolors] < $ncolors - 1} { + if {[info exists crossings($id)]} { + foreach x $crossings($id) { + if {[info exists colormap($x)] + && [lsearch -exact $badcolors $colormap($x)] < 0} { + lappend badcolors $colormap($x) + } + } + if {[llength $badcolors] >= $ncolors} { + set badcolors $origbad + } + } + set origbad $badcolors + } + if {[llength $badcolors] < $ncolors - 1} { + foreach child $children($id) { + if {[info exists colormap($child)] + && [lsearch -exact $badcolors $colormap($child)] < 0} { + lappend badcolors $colormap($child) + } + if {[info exists parents($child)]} { + foreach p $parents($child) { + if {[info exists colormap($p)] + && [lsearch -exact $badcolors $colormap($p)] < 0} { + lappend badcolors $colormap($p) + } + } + } + } + if {[llength $badcolors] >= $ncolors} { + set badcolors $origbad + } + } + for {set i 0} {$i <= $ncolors} {incr i} { + set c [lindex $colors $nextcolor] + if {[incr nextcolor] >= $ncolors} { + set nextcolor 0 + } + if {[lsearch -exact $badcolors $c]} break + } + set colormap($id) $c +} + +proc initgraph {} { + global canvy canvy0 lineno numcommits nextcolor linespc + global mainline mainlinearrow sidelines + global nchildren ncleft + global displist nhyperspace + + allcanvs delete all + set nextcolor 0 + set canvy $canvy0 + set lineno -1 + set numcommits 0 + catch {unset mainline} + catch {unset mainlinearrow} + catch {unset sidelines} + foreach id [array names nchildren] { + set ncleft($id) $nchildren($id) + } + set displist {} + set nhyperspace 0 +} + +proc bindline {t id} { + global canv + + $canv bind $t <Enter> "lineenter %x %y $id" + $canv bind $t <Motion> "linemotion %x %y $id" + $canv bind $t <Leave> "lineleave $id" + $canv bind $t <Button-1> "lineclick %x %y $id 1" +} + +proc drawlines {id xtra} { + global mainline mainlinearrow sidelines lthickness colormap canv + + $canv delete lines.$id + if {[info exists mainline($id)]} { + set t [$canv create line $mainline($id) \ + -width [expr {($xtra + 1) * $lthickness}] \ + -fill $colormap($id) -tags lines.$id \ + -arrow $mainlinearrow($id)] + $canv lower $t + bindline $t $id + } + if {[info exists sidelines($id)]} { + foreach ls $sidelines($id) { + set coords [lindex $ls 0] + set thick [lindex $ls 1] + set arrow [lindex $ls 2] + set t [$canv create line $coords -fill $colormap($id) \ + -width [expr {($thick + $xtra) * $lthickness}] \ + -arrow $arrow -tags lines.$id] + $canv lower $t + bindline $t $id + } + } +} + +# level here is an index in displist +proc drawcommitline {level} { + global parents children nparents displist + global canv canv2 canv3 mainfont namefont canvy linespc + global lineid linehtag linentag linedtag commitinfo + global colormap numcommits currentparents dupparents + global idtags idline idheads idotherrefs + global lineno lthickness mainline mainlinearrow sidelines + global commitlisted rowtextx idpos lastuse displist + global oldnlines olddlevel olddisplist + + incr numcommits + incr lineno + set id [lindex $displist $level] + set lastuse($id) $lineno + set lineid($lineno) $id + set idline($id) $lineno + set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}] + if {![info exists commitinfo($id)]} { + readcommit $id + if {![info exists commitinfo($id)]} { + set commitinfo($id) {"No commit information available"} + set nparents($id) 0 + } + } + assigncolor $id + set currentparents {} + set dupparents {} + if {[info exists commitlisted($id)] && [info exists parents($id)]} { + foreach p $parents($id) { + if {[lsearch -exact $currentparents $p] < 0} { + lappend currentparents $p + } else { + # remember that this parent was listed twice + lappend dupparents $p + } + } + } + set x [xcoord $level $level $lineno] + set y1 $canvy + set canvy [expr $canvy + $linespc] + allcanvs conf -scrollregion \ + [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]] + if {[info exists mainline($id)]} { + lappend mainline($id) $x $y1 + if {$mainlinearrow($id) ne "none"} { + set mainline($id) [trimdiagstart $mainline($id)] + } + } + drawlines $id 0 + set orad [expr {$linespc / 3}] + set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \ + [expr $x + $orad - 1] [expr $y1 + $orad - 1] \ + -fill $ofill -outline black -width 1] + $canv raise $t + $canv bind $t <1> {selcanvline {} %x %y} + set xt [xcoord [llength $displist] $level $lineno] + if {[llength $currentparents] > 2} { + set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}] + } + set rowtextx($lineno) $xt + set idpos($id) [list $x $xt $y1] + if {[info exists idtags($id)] || [info exists idheads($id)] + || [info exists idotherrefs($id)]} { + set xt [drawtags $id $x $xt $y1] + } + set headline [lindex $commitinfo($id) 0] + set name [lindex $commitinfo($id) 1] + set date [lindex $commitinfo($id) 2] + set linehtag($lineno) [$canv create text $xt $y1 -anchor w \ + -text $headline -font $mainfont ] + $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id" + set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \ + -text $name -font $namefont] + set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \ + -text $date -font $mainfont] + + set olddlevel $level + set olddisplist $displist + set oldnlines [llength $displist] +} + +proc drawtags {id x xt y1} { + global idtags idheads idotherrefs + global linespc lthickness + global canv mainfont idline rowtextx + + set marks {} + set ntags 0 + set nheads 0 + if {[info exists idtags($id)]} { + set marks $idtags($id) + set ntags [llength $marks] + } + if {[info exists idheads($id)]} { + set marks [concat $marks $idheads($id)] + set nheads [llength $idheads($id)] + } + if {[info exists idotherrefs($id)]} { + set marks [concat $marks $idotherrefs($id)] + } + if {$marks eq {}} { + return $xt + } + + set delta [expr {int(0.5 * ($linespc - $lthickness))}] + set yt [expr $y1 - 0.5 * $linespc] + set yb [expr $yt + $linespc - 1] + set xvals {} + set wvals {} + foreach tag $marks { + set wid [font measure $mainfont $tag] + lappend xvals $xt + lappend wvals $wid + set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}] + } + set t [$canv create line $x $y1 [lindex $xvals end] $y1 \ + -width $lthickness -fill black -tags tag.$id] + $canv lower $t + foreach tag $marks x $xvals wid $wvals { + set xl [expr $x + $delta] + set xr [expr $x + $delta + $wid + $lthickness] + if {[incr ntags -1] >= 0} { + # draw a tag + set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \ + $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \ + -width 1 -outline black -fill yellow -tags tag.$id] + $canv bind $t <1> [list showtag $tag 1] + set rowtextx($idline($id)) [expr {$xr + $linespc}] + } else { + # draw a head or other ref + if {[incr nheads -1] >= 0} { + set col green + } else { + set col "#ddddff" + } + set xl [expr $xl - $delta/2] + $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \ + -width 1 -outline black -fill $col -tags tag.$id + } + set t [$canv create text $xl $y1 -anchor w -text $tag \ + -font $mainfont -tags tag.$id] + if {$ntags >= 0} { + $canv bind $t <1> [list showtag $tag 1] + } + } + return $xt +} + +proc notecrossings {id lo hi corner} { + global olddisplist crossings cornercrossings + + for {set i $lo} {[incr i] < $hi} {} { + set p [lindex $olddisplist $i] + if {$p == {}} continue + if {$i == $corner} { + if {![info exists cornercrossings($id)] + || [lsearch -exact $cornercrossings($id) $p] < 0} { + lappend cornercrossings($id) $p + } + if {![info exists cornercrossings($p)] + || [lsearch -exact $cornercrossings($p) $id] < 0} { + lappend cornercrossings($p) $id + } + } else { + if {![info exists crossings($id)] + || [lsearch -exact $crossings($id) $p] < 0} { + lappend crossings($id) $p + } + if {![info exists crossings($p)] + || [lsearch -exact $crossings($p) $id] < 0} { + lappend crossings($p) $id + } + } + } +} + +proc xcoord {i level ln} { + global canvx0 xspc1 xspc2 + + set x [expr {$canvx0 + $i * $xspc1($ln)}] + if {$i > 0 && $i == $level} { + set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}] + } elseif {$i > $level} { + set x [expr {$x + $xspc2 - $xspc1($ln)}] + } + return $x +} + +# it seems Tk can't draw arrows on the end of diagonal line segments... +proc trimdiagend {line} { + while {[llength $line] > 4} { + set x1 [lindex $line end-3] + set y1 [lindex $line end-2] + set x2 [lindex $line end-1] + set y2 [lindex $line end] + if {($x1 == $x2) != ($y1 == $y2)} break + set line [lreplace $line end-1 end] + } + return $line +} + +proc trimdiagstart {line} { + while {[llength $line] > 4} { + set x1 [lindex $line 0] + set y1 [lindex $line 1] + set x2 [lindex $line 2] + set y2 [lindex $line 3] + if {($x1 == $x2) != ($y1 == $y2)} break + set line [lreplace $line 0 1] + } + return $line +} + +proc drawslants {id needonscreen nohs} { + global canv mainline mainlinearrow sidelines + global canvx0 canvy xspc1 xspc2 lthickness + global currentparents dupparents + global lthickness linespc canvy colormap lineno geometry + global maxgraphpct maxwidth + global displist onscreen lastuse + global parents commitlisted + global oldnlines olddlevel olddisplist + global nhyperspace numcommits nnewparents + + if {$lineno < 0} { + lappend displist $id + set onscreen($id) 1 + return 0 + } + + set y1 [expr {$canvy - $linespc}] + set y2 $canvy + + # work out what we need to get back on screen + set reins {} + if {$onscreen($id) < 0} { + # next to do isn't displayed, better get it on screen... + lappend reins [list $id 0] + } + # make sure all the previous commits's parents are on the screen + foreach p $currentparents { + if {$onscreen($p) < 0} { + lappend reins [list $p 0] + } + } + # bring back anything requested by caller + if {$needonscreen ne {}} { + lappend reins $needonscreen + } + + # try the shortcut + if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} { + set dlevel $olddlevel + set x [xcoord $dlevel $dlevel $lineno] + set mainline($id) [list $x $y1] + set mainlinearrow($id) none + set lastuse($id) $lineno + set displist [lreplace $displist $dlevel $dlevel $id] + set onscreen($id) 1 + set xspc1([expr {$lineno + 1}]) $xspc1($lineno) + return $dlevel + } + + # update displist + set displist [lreplace $displist $olddlevel $olddlevel] + set j $olddlevel + foreach p $currentparents { + set lastuse($p) $lineno + if {$onscreen($p) == 0} { + set displist [linsert $displist $j $p] + set onscreen($p) 1 + incr j + } + } + if {$onscreen($id) == 0} { + lappend displist $id + set onscreen($id) 1 + } + + # remove the null entry if present + set nullentry [lsearch -exact $displist {}] + if {$nullentry >= 0} { + set displist [lreplace $displist $nullentry $nullentry] + } + + # bring back the ones we need now (if we did it earlier + # it would change displist and invalidate olddlevel) + foreach pi $reins { + # test again in case of duplicates in reins + set p [lindex $pi 0] + if {$onscreen($p) < 0} { + set onscreen($p) 1 + set lastuse($p) $lineno + set displist [linsert $displist [lindex $pi 1] $p] + incr nhyperspace -1 + } + } + + set lastuse($id) $lineno + + # see if we need to make any lines jump off into hyperspace + set displ [llength $displist] + if {$displ > $maxwidth} { + set ages {} + foreach x $displist { + lappend ages [list $lastuse($x) $x] + } + set ages [lsort -integer -index 0 $ages] + set k 0 + while {$displ > $maxwidth} { + set use [lindex $ages $k 0] + set victim [lindex $ages $k 1] + if {$use >= $lineno - 5} break + incr k + if {[lsearch -exact $nohs $victim] >= 0} continue + set i [lsearch -exact $displist $victim] + set displist [lreplace $displist $i $i] + set onscreen($victim) -1 + incr nhyperspace + incr displ -1 + if {$i < $nullentry} { + incr nullentry -1 + } + set x [lindex $mainline($victim) end-1] + lappend mainline($victim) $x $y1 + set line [trimdiagend $mainline($victim)] + set arrow "last" + if {$mainlinearrow($victim) ne "none"} { + set line [trimdiagstart $line] + set arrow "both" + } + lappend sidelines($victim) [list $line 1 $arrow] + unset mainline($victim) + } + } + + set dlevel [lsearch -exact $displist $id] + + # If we are reducing, put in a null entry + if {$displ < $oldnlines} { + # does the next line look like a merge? + # i.e. does it have > 1 new parent? + if {$nnewparents($id) > 1} { + set i [expr {$dlevel + 1}] + } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} { + set i $olddlevel + if {$nullentry >= 0 && $nullentry < $i} { + incr i -1 + } + } elseif {$nullentry >= 0} { + set i $nullentry + while {$i < $displ + && [lindex $olddisplist $i] == [lindex $displist $i]} { + incr i + } + } else { + set i $olddlevel + if {$dlevel >= $i} { + incr i + } + } + if {$i < $displ} { + set displist [linsert $displist $i {}] + incr displ + if {$dlevel >= $i} { + incr dlevel + } + } + } + + # decide on the line spacing for the next line + set lj [expr {$lineno + 1}] + set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}] + if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} { + set xspc1($lj) $xspc2 + } else { + set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}] + if {$xspc1($lj) < $lthickness} { + set xspc1($lj) $lthickness + } + } + + foreach idi $reins { + set id [lindex $idi 0] + set j [lsearch -exact $displist $id] + set xj [xcoord $j $dlevel $lj] + set mainline($id) [list $xj $y2] + set mainlinearrow($id) first + } + + set i -1 + foreach id $olddisplist { + incr i + if {$id == {}} continue + if {$onscreen($id) <= 0} continue + set xi [xcoord $i $olddlevel $lineno] + if {$i == $olddlevel} { + foreach p $currentparents { + set j [lsearch -exact $displist $p] + set coords [list $xi $y1] + set xj [xcoord $j $dlevel $lj] + if {$xj < $xi - $linespc} { + lappend coords [expr {$xj + $linespc}] $y1 + notecrossings $p $j $i [expr {$j + 1}] + } elseif {$xj > $xi + $linespc} { + lappend coords [expr {$xj - $linespc}] $y1 + notecrossings $p $i $j [expr {$j - 1}] + } + if {[lsearch -exact $dupparents $p] >= 0} { + # draw a double-width line to indicate the doubled parent + lappend coords $xj $y2 + lappend sidelines($p) [list $coords 2 none] + if {![info exists mainline($p)]} { + set mainline($p) [list $xj $y2] + set mainlinearrow($p) none + } + } else { + # normal case, no parent duplicated + set yb $y2 + set dx [expr {abs($xi - $xj)}] + if {0 && $dx < $linespc} { + set yb [expr {$y1 + $dx}] + } + if {![info exists mainline($p)]} { + if {$xi != $xj} { + lappend coords $xj $yb + } + set mainline($p) $coords + set mainlinearrow($p) none + } else { + lappend coords $xj $yb + if {$yb < $y2} { + lappend coords $xj $y2 + } + lappend sidelines($p) [list $coords 1 none] + } + } + } + } else { + set j $i + if {[lindex $displist $i] != $id} { + set j [lsearch -exact $displist $id] + } + if {$j != $i || $xspc1($lineno) != $xspc1($lj) + || ($olddlevel < $i && $i < $dlevel) + || ($dlevel < $i && $i < $olddlevel)} { + set xj [xcoord $j $dlevel $lj] + lappend mainline($id) $xi $y1 $xj $y2 + } + } + } + return $dlevel +} + +# search for x in a list of lists +proc llsearch {llist x} { + set i 0 + foreach l $llist { + if {$l == $x || [lsearch -exact $l $x] >= 0} { + return $i + } + incr i + } + return -1 +} + +proc drawmore {reading} { + global displayorder numcommits ncmupdate nextupdate + global stopped nhyperspace parents commitlisted + global maxwidth onscreen displist currentparents olddlevel + + set n [llength $displayorder] + while {$numcommits < $n} { + set id [lindex $displayorder $numcommits] + set ctxend [expr {$numcommits + 10}] + if {!$reading && $ctxend > $n} { + set ctxend $n + } + set dlist {} + if {$numcommits > 0} { + set dlist [lreplace $displist $olddlevel $olddlevel] + set i $olddlevel + foreach p $currentparents { + if {$onscreen($p) == 0} { + set dlist [linsert $dlist $i $p] + incr i + } + } + } + set nohs {} + set reins {} + set isfat [expr {[llength $dlist] > $maxwidth}] + if {$nhyperspace > 0 || $isfat} { + if {$ctxend > $n} break + # work out what to bring back and + # what we want to don't want to send into hyperspace + set room 1 + for {set k $numcommits} {$k < $ctxend} {incr k} { + set x [lindex $displayorder $k] + set i [llsearch $dlist $x] + if {$i < 0} { + set i [llength $dlist] + lappend dlist $x + } + if {[lsearch -exact $nohs $x] < 0} { + lappend nohs $x + } + if {$reins eq {} && $onscreen($x) < 0 && $room} { + set reins [list $x $i] + } + set newp {} + if {[info exists commitlisted($x)]} { + set right 0 + foreach p $parents($x) { + if {[llsearch $dlist $p] < 0} { + lappend newp $p + if {[lsearch -exact $nohs $p] < 0} { + lappend nohs $p + } + if {$reins eq {} && $onscreen($p) < 0 && $room} { + set reins [list $p [expr {$i + $right}]] + } + } + set right 1 + } + } + set l [lindex $dlist $i] + if {[llength $l] == 1} { + set l $newp + } else { + set j [lsearch -exact $l $x] + set l [concat [lreplace $l $j $j] $newp] + } + set dlist [lreplace $dlist $i $i $l] + if {$room && $isfat && [llength $newp] <= 1} { + set room 0 + } + } + } + + set dlevel [drawslants $id $reins $nohs] + drawcommitline $dlevel + if {[clock clicks -milliseconds] >= $nextupdate + && $numcommits >= $ncmupdate} { + doupdate $reading + if {$stopped} break + } + } +} + +# level here is an index in todo +proc updatetodo {level noshortcut} { + global ncleft todo nnewparents + global commitlisted parents onscreen + + set id [lindex $todo $level] + set olds {} + if {[info exists commitlisted($id)]} { + foreach p $parents($id) { + if {[lsearch -exact $olds $p] < 0} { + lappend olds $p + } + } + } + if {!$noshortcut && [llength $olds] == 1} { + set p [lindex $olds 0] + if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} { + set ncleft($p) 0 + set todo [lreplace $todo $level $level $p] + set onscreen($p) 0 + set nnewparents($id) 1 + return 0 + } + } + + set todo [lreplace $todo $level $level] + set i $level + set n 0 + foreach p $olds { + incr ncleft($p) -1 + set k [lsearch -exact $todo $p] + if {$k < 0} { + set todo [linsert $todo $i $p] + set onscreen($p) 0 + incr i + incr n + } + } + set nnewparents($id) $n + + return 1 +} + +proc decidenext {{noread 0}} { + global ncleft todo + global datemode cdate + global commitinfo + + # choose which one to do next time around + set todol [llength $todo] + set level -1 + set latest {} + for {set k $todol} {[incr k -1] >= 0} {} { + set p [lindex $todo $k] + if {$ncleft($p) == 0} { + if {$datemode} { + if {![info exists commitinfo($p)]} { + if {$noread} { + return {} + } + readcommit $p + } + if {$latest == {} || $cdate($p) > $latest} { + set level $k + set latest $cdate($p) + } + } else { + set level $k + break + } + } + } + if {$level < 0} { + if {$todo != {}} { + puts "ERROR: none of the pending commits can be done yet:" + foreach p $todo { + puts " $p ($ncleft($p))" + } + } + return -1 + } + + return $level +} + +proc drawcommit {id} { + global phase todo nchildren datemode nextupdate + global numcommits ncmupdate displayorder todo onscreen + + if {$phase != "incrdraw"} { + set phase incrdraw + set displayorder {} + set todo {} + initgraph + } + if {$nchildren($id) == 0} { + lappend todo $id + set onscreen($id) 0 + } + set level [decidenext 1] + if {$level == {} || $id != [lindex $todo $level]} { + return + } + while 1 { + lappend displayorder [lindex $todo $level] + if {[updatetodo $level $datemode]} { + set level [decidenext 1] + if {$level == {}} break + } + set id [lindex $todo $level] + if {![info exists commitlisted($id)]} { + break + } + } + drawmore 1 +} + +proc finishcommits {} { + global phase + global canv mainfont ctext maincursor textcursor + + if {$phase != "incrdraw"} { + $canv delete all + $canv create text 3 3 -anchor nw -text "No commits selected" \ + -font $mainfont -tags textitems + set phase {} + } else { + drawrest + } + . config -cursor $maincursor + settextcursor $textcursor +} + +# Don't change the text pane cursor if it is currently the hand cursor, +# showing that we are over a sha1 ID link. +proc settextcursor {c} { + global ctext curtextcursor + + if {[$ctext cget -cursor] == $curtextcursor} { + $ctext config -cursor $c + } + set curtextcursor $c +} + +proc drawgraph {} { + global nextupdate startmsecs ncmupdate + global displayorder onscreen + + if {$displayorder == {}} return + set startmsecs [clock clicks -milliseconds] + set nextupdate [expr $startmsecs + 100] + set ncmupdate 1 + initgraph + foreach id $displayorder { + set onscreen($id) 0 + } + drawmore 0 +} + +proc drawrest {} { + global phase stopped redisplaying selectedline + global datemode todo displayorder + global numcommits ncmupdate + global nextupdate startmsecs + + set level [decidenext] + if {$level >= 0} { + set phase drawgraph + while 1 { + lappend displayorder [lindex $todo $level] + set hard [updatetodo $level $datemode] + if {$hard} { + set level [decidenext] + if {$level < 0} break + } + } + drawmore 0 + } + set phase {} + set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs] + #puts "overall $drawmsecs ms for $numcommits commits" + if {$redisplaying} { + if {$stopped == 0 && [info exists selectedline]} { + selectline $selectedline 0 + } + if {$stopped == 1} { + set stopped 0 + after idle drawgraph + } else { + set redisplaying 0 + } + } +} + +proc findmatches {f} { + global findtype foundstring foundstrlen + if {$findtype == "Regexp"} { + set matches [regexp -indices -all -inline $foundstring $f] + } else { + if {$findtype == "IgnCase"} { + set str [string tolower $f] + } else { + set str $f + } + set matches {} + set i 0 + while {[set j [string first $foundstring $str $i]] >= 0} { + lappend matches [list $j [expr $j+$foundstrlen-1]] + set i [expr $j + $foundstrlen] + } + } + return $matches +} + +proc dofind {} { + global findtype findloc findstring markedmatches commitinfo + global numcommits lineid linehtag linentag linedtag + global mainfont namefont canv canv2 canv3 selectedline + global matchinglines foundstring foundstrlen + + stopfindproc + unmarkmatches + focus . + set matchinglines {} + if {$findloc == "Pickaxe"} { + findpatches + return + } + if {$findtype == "IgnCase"} { + set foundstring [string tolower $findstring] + } else { + set foundstring $findstring + } + set foundstrlen [string length $findstring] + if {$foundstrlen == 0} return + if {$findloc == "Files"} { + findfiles + return + } + if {![info exists selectedline]} { + set oldsel -1 + } else { + set oldsel $selectedline + } + set didsel 0 + set fldtypes {Headline Author Date Committer CDate Comment} + for {set l 0} {$l < $numcommits} {incr l} { + set id $lineid($l) + set info $commitinfo($id) + set doesmatch 0 + foreach f $info ty $fldtypes { + if {$findloc != "All fields" && $findloc != $ty} { + continue + } + set matches [findmatches $f] + if {$matches == {}} continue + set doesmatch 1 + if {$ty == "Headline"} { + markmatches $canv $l $f $linehtag($l) $matches $mainfont + } elseif {$ty == "Author"} { + markmatches $canv2 $l $f $linentag($l) $matches $namefont + } elseif {$ty == "Date"} { + markmatches $canv3 $l $f $linedtag($l) $matches $mainfont + } + } + if {$doesmatch} { + lappend matchinglines $l + if {!$didsel && $l > $oldsel} { + findselectline $l + set didsel 1 + } + } + } + if {$matchinglines == {}} { + bell + } elseif {!$didsel} { + findselectline [lindex $matchinglines 0] + } +} + +proc findselectline {l} { + global findloc commentend ctext + selectline $l 1 + if {$findloc == "All fields" || $findloc == "Comments"} { + # highlight the matches in the comments + set f [$ctext get 1.0 $commentend] + set matches [findmatches $f] + foreach match $matches { + set start [lindex $match 0] + set end [expr [lindex $match 1] + 1] + $ctext tag add found "1.0 + $start c" "1.0 + $end c" + } + } +} + +proc findnext {restart} { + global matchinglines selectedline + if {![info exists matchinglines]} { + if {$restart} { + dofind + } + return + } + if {![info exists selectedline]} return + foreach l $matchinglines { + if {$l > $selectedline} { + findselectline $l + return + } + } + bell +} + +proc findprev {} { + global matchinglines selectedline + if {![info exists matchinglines]} { + dofind + return + } + if {![info exists selectedline]} return + set prev {} + foreach l $matchinglines { + if {$l >= $selectedline} break + set prev $l + } + if {$prev != {}} { + findselectline $prev + } else { + bell + } +} + +proc findlocchange {name ix op} { + global findloc findtype findtypemenu + if {$findloc == "Pickaxe"} { + set findtype Exact + set state disabled + } else { + set state normal + } + $findtypemenu entryconf 1 -state $state + $findtypemenu entryconf 2 -state $state +} + +proc stopfindproc {{done 0}} { + global findprocpid findprocfile findids + global ctext findoldcursor phase maincursor textcursor + global findinprogress + + catch {unset findids} + if {[info exists findprocpid]} { + if {!$done} { + catch {exec kill $findprocpid} + } + catch {close $findprocfile} + unset findprocpid + } + if {[info exists findinprogress]} { + unset findinprogress + if {$phase != "incrdraw"} { + . config -cursor $maincursor + settextcursor $textcursor + } + } +} + +proc findpatches {} { + global findstring selectedline numcommits + global findprocpid findprocfile + global finddidsel ctext lineid findinprogress + global findinsertpos + global env + + if {$numcommits == 0} return + + # make a list of all the ids to search, starting at the one + # after the selected line (if any) + if {[info exists selectedline]} { + set l $selectedline + } else { + set l -1 + } + set inputids {} + for {set i 0} {$i < $numcommits} {incr i} { + if {[incr l] >= $numcommits} { + set l 0 + } + append inputids $lineid($l) "\n" + } + + if {[catch { + set f [open [list | $env(HG) debug-diff-tree --stdin -s -r -S$findstring \ + << $inputids] r] + } err]} { + error_popup "Error starting search process: $err" + return + } + + set findinsertpos end + set findprocfile $f + set findprocpid [pid $f] + fconfigure $f -blocking 0 + fileevent $f readable readfindproc + set finddidsel 0 + . config -cursor watch + settextcursor watch + set findinprogress 1 +} + +proc readfindproc {} { + global findprocfile finddidsel + global idline matchinglines findinsertpos + + set n [gets $findprocfile line] + if {$n < 0} { + if {[eof $findprocfile]} { + stopfindproc 1 + if {!$finddidsel} { + bell + } + } + return + } + if {![regexp {^[0-9a-f]{12}} $line id]} { + error_popup "Can't parse git-diff-tree output: $line" + stopfindproc + return + } + if {![info exists idline($id)]} { + puts stderr "spurious id: $id" + return + } + set l $idline($id) + insertmatch $l $id +} + +proc insertmatch {l id} { + global matchinglines findinsertpos finddidsel + + if {$findinsertpos == "end"} { + if {$matchinglines != {} && $l < [lindex $matchinglines 0]} { + set matchinglines [linsert $matchinglines 0 $l] + set findinsertpos 1 + } else { + lappend matchinglines $l + } + } else { + set matchinglines [linsert $matchinglines $findinsertpos $l] + incr findinsertpos + } + markheadline $l $id + if {!$finddidsel} { + findselectline $l + set finddidsel 1 + } +} + +proc findfiles {} { + global selectedline numcommits lineid ctext + global ffileline finddidsel parents nparents + global findinprogress findstartline findinsertpos + global treediffs fdiffids fdiffsneeded fdiffpos + global findmergefiles + global env + + if {$numcommits == 0} return + + if {[info exists selectedline]} { + set l [expr {$selectedline + 1}] + } else { + set l 0 + } + set ffileline $l + set findstartline $l + set diffsneeded {} + set fdiffsneeded {} + while 1 { + set id $lineid($l) + if {$findmergefiles || $nparents($id) == 1} { + foreach p $parents($id) { + if {![info exists treediffs([list $id $p])]} { + append diffsneeded "$id $p\n" + lappend fdiffsneeded [list $id $p] + } + } + } + if {[incr l] >= $numcommits} { + set l 0 + } + if {$l == $findstartline} break + } + + # start off a git-diff-tree process if needed + if {$diffsneeded ne {}} { + if {[catch { + set df [open [list | $env(HG) debug-diff-tree -r --stdin << $diffsneeded] r] + } err ]} { + error_popup "Error starting search process: $err" + return + } + catch {unset fdiffids} + set fdiffpos 0 + fconfigure $df -blocking 0 + fileevent $df readable [list readfilediffs $df] + } + + set finddidsel 0 + set findinsertpos end + set id $lineid($l) + set p [lindex $parents($id) 0] + . config -cursor watch + settextcursor watch + set findinprogress 1 + findcont [list $id $p] + update +} + +proc readfilediffs {df} { + global findids fdiffids fdiffs + + set n [gets $df line] + if {$n < 0} { + if {[eof $df]} { + donefilediff + if {[catch {close $df} err]} { + stopfindproc + bell + error_popup "Error in hg debug-diff-tree: $err" + } elseif {[info exists findids]} { + set ids $findids + stopfindproc + bell + error_popup "Couldn't find diffs for {$ids}" + } + } + return + } + if {[regexp {^([0-9a-f]{12}) \(from ([0-9a-f]{12})\)} $line match id p]} { + # start of a new string of diffs + donefilediff + set fdiffids [list $id $p] + set fdiffs {} + } elseif {[string match ":*" $line]} { + lappend fdiffs [lindex $line 5] + } +} + +proc donefilediff {} { + global fdiffids fdiffs treediffs findids + global fdiffsneeded fdiffpos + + if {[info exists fdiffids]} { + while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids + && $fdiffpos < [llength $fdiffsneeded]} { + # git-diff-tree doesn't output anything for a commit + # which doesn't change anything + set nullids [lindex $fdiffsneeded $fdiffpos] + set treediffs($nullids) {} + if {[info exists findids] && $nullids eq $findids} { + unset findids + findcont $nullids + } + incr fdiffpos + } + incr fdiffpos + + if {![info exists treediffs($fdiffids)]} { + set treediffs($fdiffids) $fdiffs + } + if {[info exists findids] && $fdiffids eq $findids} { + unset findids + findcont $fdiffids + } + } +} + +proc findcont {ids} { + global findids treediffs parents nparents + global ffileline findstartline finddidsel + global lineid numcommits matchinglines findinprogress + global findmergefiles + + set id [lindex $ids 0] + set p [lindex $ids 1] + set pi [lsearch -exact $parents($id) $p] + set l $ffileline + while 1 { + if {$findmergefiles || $nparents($id) == 1} { + if {![info exists treediffs($ids)]} { + set findids $ids + set ffileline $l + return + } + set doesmatch 0 + foreach f $treediffs($ids) { + set x [findmatches $f] + if {$x != {}} { + set doesmatch 1 + break + } + } + if {$doesmatch} { + insertmatch $l $id + set pi $nparents($id) + } + } else { + set pi $nparents($id) + } + if {[incr pi] >= $nparents($id)} { + set pi 0 + if {[incr l] >= $numcommits} { + set l 0 + } + if {$l == $findstartline} break + set id $lineid($l) + } + set p [lindex $parents($id) $pi] + set ids [list $id $p] + } + stopfindproc + if {!$finddidsel} { + bell + } +} + +# mark a commit as matching by putting a yellow background +# behind the headline +proc markheadline {l id} { + global canv mainfont linehtag commitinfo + + set bbox [$canv bbox $linehtag($l)] + set t [$canv create rect $bbox -outline {} -tags matches -fill yellow] + $canv lower $t +} + +# mark the bits of a headline, author or date that match a find string +proc markmatches {canv l str tag matches font} { + set bbox [$canv bbox $tag] + set x0 [lindex $bbox 0] + set y0 [lindex $bbox 1] + set y1 [lindex $bbox 3] + foreach match $matches { + set start [lindex $match 0] + set end [lindex $match 1] + if {$start > $end} continue + set xoff [font measure $font [string range $str 0 [expr $start-1]]] + set xlen [font measure $font [string range $str 0 [expr $end]]] + set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \ + -outline {} -tags matches -fill yellow] + $canv lower $t + } +} + +proc unmarkmatches {} { + global matchinglines findids + allcanvs delete matches + catch {unset matchinglines} + catch {unset findids} +} + +proc selcanvline {w x y} { + global canv canvy0 ctext linespc + global lineid linehtag linentag linedtag rowtextx + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax == {}} return + set yfrac [lindex [$canv yview] 0] + set y [expr {$y + $yfrac * $ymax}] + set l [expr {int(($y - $canvy0) / $linespc + 0.5)}] + if {$l < 0} { + set l 0 + } + if {$w eq $canv} { + if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return + } + unmarkmatches + selectline $l 1 +} + +proc commit_descriptor {p} { + global commitinfo + set l "..." + if {[info exists commitinfo($p)]} { + set l [lindex $commitinfo($p) 0] + set r [lindex $commitinfo($p) 6] + } + return "$r:$p ($l)" +} + +# append some text to the ctext widget, and make any SHA1 ID +# that we know about be a clickable link. +proc appendwithlinks {text} { + global ctext idline linknum + + set start [$ctext index "end - 1c"] + $ctext insert end $text + $ctext insert end "\n" + set links [regexp -indices -all -inline {[0-9a-f]{12}} $text] + foreach l $links { + set s [lindex $l 0] + set e [lindex $l 1] + set linkid [string range $text $s $e] + if {![info exists idline($linkid)]} continue + incr e + $ctext tag add link "$start + $s c" "$start + $e c" + $ctext tag add link$linknum "$start + $s c" "$start + $e c" + $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1] + incr linknum + } + $ctext tag conf link -foreground blue -underline 1 + $ctext tag bind link <Enter> { %W configure -cursor hand2 } + $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } +} + +proc selectline {l isnew} { + global canv canv2 canv3 ctext commitinfo selectedline + global lineid linehtag linentag linedtag + global canvy0 linespc parents nparents children + global cflist currentid sha1entry + global commentend idtags idline linknum + + $canv delete hover + normalline + if {![info exists lineid($l)] || ![info exists linehtag($l)]} return + $canv delete secsel + set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \ + -tags secsel -fill [$canv cget -selectbackground]] + $canv lower $t + $canv2 delete secsel + set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \ + -tags secsel -fill [$canv2 cget -selectbackground]] + $canv2 lower $t + $canv3 delete secsel + set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \ + -tags secsel -fill [$canv3 cget -selectbackground]] + $canv3 lower $t + set y [expr {$canvy0 + $l * $linespc}] + set ymax [lindex [$canv cget -scrollregion] 3] + set ytop [expr {$y - $linespc - 1}] + set ybot [expr {$y + $linespc + 1}] + set wnow [$canv yview] + set wtop [expr [lindex $wnow 0] * $ymax] + set wbot [expr [lindex $wnow 1] * $ymax] + set wh [expr {$wbot - $wtop}] + set newtop $wtop + if {$ytop < $wtop} { + if {$ybot < $wtop} { + set newtop [expr {$y - $wh / 2.0}] + } else { + set newtop $ytop + if {$newtop > $wtop - $linespc} { + set newtop [expr {$wtop - $linespc}] + } + } + } elseif {$ybot > $wbot} { + if {$ytop > $wbot} { + set newtop [expr {$y - $wh / 2.0}] + } else { + set newtop [expr {$ybot - $wh}] + if {$newtop < $wtop + $linespc} { + set newtop [expr {$wtop + $linespc}] + } + } + } + if {$newtop != $wtop} { + if {$newtop < 0} { + set newtop 0 + } + allcanvs yview moveto [expr $newtop * 1.0 / $ymax] + } + + if {$isnew} { + addtohistory [list selectline $l 0] + } + + set selectedline $l + + set id $lineid($l) + set currentid $id + $sha1entry delete 0 end + $sha1entry insert 0 $id + $sha1entry selection from 0 + $sha1entry selection to end + + $ctext conf -state normal + $ctext delete 0.0 end + set linknum 0 + $ctext mark set fmark.0 0.0 + $ctext mark gravity fmark.0 left + set info $commitinfo($id) + $ctext insert end "Revision: [lindex $info 6]\n" + $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n" + $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n" + if {[info exists idtags($id)]} { + $ctext insert end "Tags:" + foreach tag $idtags($id) { + $ctext insert end " $tag" + } + $ctext insert end "\n" + } + + set comment {} + if {[info exists parents($id)]} { + foreach p $parents($id) { + append comment "Parent: [commit_descriptor $p]\n" + } + } + if {[info exists children($id)]} { + foreach c $children($id) { + append comment "Child: [commit_descriptor $c]\n" + } + } + append comment "\n" + append comment [lindex $info 5] + + # make anything that looks like a SHA1 ID be a clickable link + appendwithlinks $comment + + $ctext tag delete Comments + $ctext tag remove found 1.0 end + $ctext conf -state disabled + set commentend [$ctext index "end - 1c"] + + $cflist delete 0 end + $cflist insert end "Comments" + if {$nparents($id) == 1} { + startdiff [concat $id $parents($id)] + } elseif {$nparents($id) > 1} { + mergediff $id + } +} + +proc selnextline {dir} { + global selectedline + if {![info exists selectedline]} return + set l [expr $selectedline + $dir] + unmarkmatches + selectline $l 1 +} + +proc unselectline {} { + global selectedline + + catch {unset selectedline} + allcanvs delete secsel +} + +proc addtohistory {cmd} { + global history historyindex + + if {$historyindex > 0 + && [lindex $history [expr {$historyindex - 1}]] == $cmd} { + return + } + + if {$historyindex < [llength $history]} { + set history [lreplace $history $historyindex end $cmd] + } else { + lappend history $cmd + } + incr historyindex + if {$historyindex > 1} { + .ctop.top.bar.leftbut conf -state normal + } else { + .ctop.top.bar.leftbut conf -state disabled + } + .ctop.top.bar.rightbut conf -state disabled +} + +proc goback {} { + global history historyindex + + if {$historyindex > 1} { + incr historyindex -1 + set cmd [lindex $history [expr {$historyindex - 1}]] + eval $cmd + .ctop.top.bar.rightbut conf -state normal + } + if {$historyindex <= 1} { + .ctop.top.bar.leftbut conf -state disabled + } +} + +proc goforw {} { + global history historyindex + + if {$historyindex < [llength $history]} { + set cmd [lindex $history $historyindex] + incr historyindex + eval $cmd + .ctop.top.bar.leftbut conf -state normal + } + if {$historyindex >= [llength $history]} { + .ctop.top.bar.rightbut conf -state disabled + } +} + +proc mergediff {id} { + global parents diffmergeid diffmergegca mergefilelist diffpindex + + set diffmergeid $id + set diffpindex -1 + set diffmergegca [findgca $parents($id)] + if {[info exists mergefilelist($id)]} { + if {$mergefilelist($id) ne {}} { + showmergediff + } + } else { + contmergediff {} + } +} + +proc findgca {ids} { + global env + set gca {} + foreach id $ids { + if {$gca eq {}} { + set gca $id + } else { + if {[catch { + set gca [exec $env(HG) debug-merge-base $gca $id] + } err]} { + return {} + } + } + } + return $gca +} + +proc contmergediff {ids} { + global diffmergeid diffpindex parents nparents diffmergegca + global treediffs mergefilelist diffids treepending + + # diff the child against each of the parents, and diff + # each of the parents against the GCA. + while 1 { + if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} { + set ids [list [lindex $ids 1] $diffmergegca] + } else { + if {[incr diffpindex] >= $nparents($diffmergeid)} break + set p [lindex $parents($diffmergeid) $diffpindex] + set ids [list $diffmergeid $p] + } + if {![info exists treediffs($ids)]} { + set diffids $ids + if {![info exists treepending]} { + gettreediffs $ids + } + return + } + } + + # If a file in some parent is different from the child and also + # different from the GCA, then it's interesting. + # If we don't have a GCA, then a file is interesting if it is + # different from the child in all the parents. + if {$diffmergegca ne {}} { + set files {} + foreach p $parents($diffmergeid) { + set gcadiffs $treediffs([list $p $diffmergegca]) + foreach f $treediffs([list $diffmergeid $p]) { + if {[lsearch -exact $files $f] < 0 + && [lsearch -exact $gcadiffs $f] >= 0} { + lappend files $f + } + } + } + set files [lsort $files] + } else { + set p [lindex $parents($diffmergeid) 0] + set files $treediffs([list $diffmergeid $p]) + for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} { + set p [lindex $parents($diffmergeid) $i] + set df $treediffs([list $diffmergeid $p]) + set nf {} + foreach f $files { + if {[lsearch -exact $df $f] >= 0} { + lappend nf $f + } + } + set files $nf + } + } + + set mergefilelist($diffmergeid) $files + if {$files ne {}} { + showmergediff + } +} + +proc showmergediff {} { + global cflist diffmergeid mergefilelist parents + global diffopts diffinhunk currentfile currenthunk filelines + global diffblocked groupfilelast mergefds groupfilenum grouphunks + global env + + set files $mergefilelist($diffmergeid) + foreach f $files { + $cflist insert end $f + } + set env(GIT_DIFF_OPTS) $diffopts + set flist {} + catch {unset currentfile} + catch {unset currenthunk} + catch {unset filelines} + catch {unset groupfilenum} + catch {unset grouphunks} + set groupfilelast -1 + foreach p $parents($diffmergeid) { + set cmd [list | $env(HG) debug-diff-tree -p $p $diffmergeid] + set cmd [concat $cmd $mergefilelist($diffmergeid)] + if {[catch {set f [open $cmd r]} err]} { + error_popup "Error getting diffs: $err" + foreach f $flist { + catch {close $f} + } + return + } + lappend flist $f + set ids [list $diffmergeid $p] + set mergefds($ids) $f + set diffinhunk($ids) 0 + set diffblocked($ids) 0 + fconfigure $f -blocking 0 + fileevent $f readable [list getmergediffline $f $ids $diffmergeid] + } +} + +proc getmergediffline {f ids id} { + global diffmergeid diffinhunk diffoldlines diffnewlines + global currentfile currenthunk + global diffoldstart diffnewstart diffoldlno diffnewlno + global diffblocked mergefilelist + global noldlines nnewlines difflcounts filelines + + set n [gets $f line] + if {$n < 0} { + if {![eof $f]} return + } + + if {!([info exists diffmergeid] && $diffmergeid == $id)} { + if {$n < 0} { + close $f + } + return + } + + if {$diffinhunk($ids) != 0} { + set fi $currentfile($ids) + if {$n > 0 && [regexp {^[-+ \\]} $line match]} { + # continuing an existing hunk + set line [string range $line 1 end] + set p [lindex $ids 1] + if {$match eq "-" || $match eq " "} { + set filelines($p,$fi,$diffoldlno($ids)) $line + incr diffoldlno($ids) + } + if {$match eq "+" || $match eq " "} { + set filelines($id,$fi,$diffnewlno($ids)) $line + incr diffnewlno($ids) + } + if {$match eq " "} { + if {$diffinhunk($ids) == 2} { + lappend difflcounts($ids) \ + [list $noldlines($ids) $nnewlines($ids)] + set noldlines($ids) 0 + set diffinhunk($ids) 1 + } + incr noldlines($ids) + } elseif {$match eq "-" || $match eq "+"} { + if {$diffinhunk($ids) == 1} { + lappend difflcounts($ids) [list $noldlines($ids)] + set noldlines($ids) 0 + set nnewlines($ids) 0 + set diffinhunk($ids) 2 + } + if {$match eq "-"} { + incr noldlines($ids) + } else { + incr nnewlines($ids) + } + } + # and if it's \ No newline at end of line, then what? + return + } + # end of a hunk + if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} { + lappend difflcounts($ids) [list $noldlines($ids)] + } elseif {$diffinhunk($ids) == 2 + && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} { + lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)] + } + set currenthunk($ids) [list $currentfile($ids) \ + $diffoldstart($ids) $diffnewstart($ids) \ + $diffoldlno($ids) $diffnewlno($ids) \ + $difflcounts($ids)] + set diffinhunk($ids) 0 + # -1 = need to block, 0 = unblocked, 1 = is blocked + set diffblocked($ids) -1 + processhunks + if {$diffblocked($ids) == -1} { + fileevent $f readable {} + set diffblocked($ids) 1 + } + } + + if {$n < 0} { + # eof + if {!$diffblocked($ids)} { + close $f + set currentfile($ids) [llength $mergefilelist($diffmergeid)] + set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}] + processhunks + } + } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} { + # start of a new file + set currentfile($ids) \ + [lsearch -exact $mergefilelist($diffmergeid) $fname] + } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \ + $line match f1l f1c f2l f2c rest]} { + if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} { + # start of a new hunk + if {$f1l == 0 && $f1c == 0} { + set f1l 1 + } + if {$f2l == 0 && $f2c == 0} { + set f2l 1 + } + set diffinhunk($ids) 1 + set diffoldstart($ids) $f1l + set diffnewstart($ids) $f2l + set diffoldlno($ids) $f1l + set diffnewlno($ids) $f2l + set difflcounts($ids) {} + set noldlines($ids) 0 + set nnewlines($ids) 0 + } + } +} + +proc processhunks {} { + global diffmergeid parents nparents currenthunk + global mergefilelist diffblocked mergefds + global grouphunks grouplinestart grouplineend groupfilenum + + set nfiles [llength $mergefilelist($diffmergeid)] + while 1 { + set fi $nfiles + set lno 0 + # look for the earliest hunk + foreach p $parents($diffmergeid) { + set ids [list $diffmergeid $p] + if {![info exists currenthunk($ids)]} return + set i [lindex $currenthunk($ids) 0] + set l [lindex $currenthunk($ids) 2] + if {$i < $fi || ($i == $fi && $l < $lno)} { + set fi $i + set lno $l + set pi $p + } + } + + if {$fi < $nfiles} { + set ids [list $diffmergeid $pi] + set hunk $currenthunk($ids) + unset currenthunk($ids) + if {$diffblocked($ids) > 0} { + fileevent $mergefds($ids) readable \ + [list getmergediffline $mergefds($ids) $ids $diffmergeid] + } + set diffblocked($ids) 0 + + if {[info exists groupfilenum] && $groupfilenum == $fi + && $lno <= $grouplineend} { + # add this hunk to the pending group + lappend grouphunks($pi) $hunk + set endln [lindex $hunk 4] + if {$endln > $grouplineend} { + set grouplineend $endln + } + continue + } + } + + # succeeding stuff doesn't belong in this group, so + # process the group now + if {[info exists groupfilenum]} { + processgroup + unset groupfilenum + unset grouphunks + } + + if {$fi >= $nfiles} break + + # start a new group + set groupfilenum $fi + set grouphunks($pi) [list $hunk] + set grouplinestart $lno + set grouplineend [lindex $hunk 4] + } +} + +proc processgroup {} { + global groupfilelast groupfilenum difffilestart + global mergefilelist diffmergeid ctext filelines + global parents diffmergeid diffoffset + global grouphunks grouplinestart grouplineend nparents + global mergemax + + $ctext conf -state normal + set id $diffmergeid + set f $groupfilenum + if {$groupfilelast != $f} { + $ctext insert end "\n" + set here [$ctext index "end - 1c"] + set difffilestart($f) $here + set mark fmark.[expr {$f + 1}] + $ctext mark set $mark $here + $ctext mark gravity $mark left + set header [lindex $mergefilelist($id) $f] + set l [expr {(78 - [string length $header]) / 2}] + set pad [string range "----------------------------------------" 1 $l] + $ctext insert end "$pad $header $pad\n" filesep + set groupfilelast $f + foreach p $parents($id) { + set diffoffset($p) 0 + } + } + + $ctext insert end "@@" msep + set nlines [expr {$grouplineend - $grouplinestart}] + set events {} + set pnum 0 + foreach p $parents($id) { + set startline [expr {$grouplinestart + $diffoffset($p)}] + set ol $startline + set nl $grouplinestart + if {[info exists grouphunks($p)]} { + foreach h $grouphunks($p) { + set l [lindex $h 2] + if {$nl < $l} { + for {} {$nl < $l} {incr nl} { + set filelines($p,$f,$ol) $filelines($id,$f,$nl) + incr ol + } + } + foreach chunk [lindex $h 5] { + if {[llength $chunk] == 2} { + set olc [lindex $chunk 0] + set nlc [lindex $chunk 1] + set nnl [expr {$nl + $nlc}] + lappend events [list $nl $nnl $pnum $olc $nlc] + incr ol $olc + set nl $nnl + } else { + incr ol [lindex $chunk 0] + incr nl [lindex $chunk 0] + } + } + } + } + if {$nl < $grouplineend} { + for {} {$nl < $grouplineend} {incr nl} { + set filelines($p,$f,$ol) $filelines($id,$f,$nl) + incr ol + } + } + set nlines [expr {$ol - $startline}] + $ctext insert end " -$startline,$nlines" msep + incr pnum + } + + set nlines [expr {$grouplineend - $grouplinestart}] + $ctext insert end " +$grouplinestart,$nlines @@\n" msep + + set events [lsort -integer -index 0 $events] + set nevents [llength $events] + set nmerge $nparents($diffmergeid) + set l $grouplinestart + for {set i 0} {$i < $nevents} {set i $j} { + set nl [lindex $events $i 0] + while {$l < $nl} { + $ctext insert end " $filelines($id,$f,$l)\n" + incr l + } + set e [lindex $events $i] + set enl [lindex $e 1] + set j $i + set active {} + while 1 { + set pnum [lindex $e 2] + set olc [lindex $e 3] + set nlc [lindex $e 4] + if {![info exists delta($pnum)]} { + set delta($pnum) [expr {$olc - $nlc}] + lappend active $pnum + } else { + incr delta($pnum) [expr {$olc - $nlc}] + } + if {[incr j] >= $nevents} break + set e [lindex $events $j] + if {[lindex $e 0] >= $enl} break + if {[lindex $e 1] > $enl} { + set enl [lindex $e 1] + } + } + set nlc [expr {$enl - $l}] + set ncol mresult + set bestpn -1 + if {[llength $active] == $nmerge - 1} { + # no diff for one of the parents, i.e. it's identical + for {set pnum 0} {$pnum < $nmerge} {incr pnum} { + if {![info exists delta($pnum)]} { + if {$pnum < $mergemax} { + lappend ncol m$pnum + } else { + lappend ncol mmax + } + break + } + } + } elseif {[llength $active] == $nmerge} { + # all parents are different, see if one is very similar + set bestsim 30 + for {set pnum 0} {$pnum < $nmerge} {incr pnum} { + set sim [similarity $pnum $l $nlc $f \ + [lrange $events $i [expr {$j-1}]]] + if {$sim > $bestsim} { + set bestsim $sim + set bestpn $pnum + } + } + if {$bestpn >= 0} { + lappend ncol m$bestpn + } + } + set pnum -1 + foreach p $parents($id) { + incr pnum + if {![info exists delta($pnum)] || $pnum == $bestpn} continue + set olc [expr {$nlc + $delta($pnum)}] + set ol [expr {$l + $diffoffset($p)}] + incr diffoffset($p) $delta($pnum) + unset delta($pnum) + for {} {$olc > 0} {incr olc -1} { + $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum + incr ol + } + } + set endl [expr {$l + $nlc}] + if {$bestpn >= 0} { + # show this pretty much as a normal diff + set p [lindex $parents($id) $bestpn] + set ol [expr {$l + $diffoffset($p)}] + incr diffoffset($p) $delta($bestpn) + unset delta($bestpn) + for {set k $i} {$k < $j} {incr k} { + set e [lindex $events $k] + if {[lindex $e 2] != $bestpn} continue + set nl [lindex $e 0] + set ol [expr {$ol + $nl - $l}] + for {} {$l < $nl} {incr l} { + $ctext insert end "+$filelines($id,$f,$l)\n" $ncol + } + set c [lindex $e 3] + for {} {$c > 0} {incr c -1} { + $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn + incr ol + } + set nl [lindex $e 1] + for {} {$l < $nl} {incr l} { + $ctext insert end "+$filelines($id,$f,$l)\n" mresult + } + } + } + for {} {$l < $endl} {incr l} { + $ctext insert end "+$filelines($id,$f,$l)\n" $ncol + } + } + while {$l < $grouplineend} { + $ctext insert end " $filelines($id,$f,$l)\n" + incr l + } + $ctext conf -state disabled +} + +proc similarity {pnum l nlc f events} { + global diffmergeid parents diffoffset filelines + + set id $diffmergeid + set p [lindex $parents($id) $pnum] + set ol [expr {$l + $diffoffset($p)}] + set endl [expr {$l + $nlc}] + set same 0 + set diff 0 + foreach e $events { + if {[lindex $e 2] != $pnum} continue + set nl [lindex $e 0] + set ol [expr {$ol + $nl - $l}] + for {} {$l < $nl} {incr l} { + incr same [string length $filelines($id,$f,$l)] + incr same + } + set oc [lindex $e 3] + for {} {$oc > 0} {incr oc -1} { + incr diff [string length $filelines($p,$f,$ol)] + incr diff + incr ol + } + set nl [lindex $e 1] + for {} {$l < $nl} {incr l} { + incr diff [string length $filelines($id,$f,$l)] + incr diff + } + } + for {} {$l < $endl} {incr l} { + incr same [string length $filelines($id,$f,$l)] + incr same + } + if {$same == 0} { + return 0 + } + return [expr {200 * $same / (2 * $same + $diff)}] +} + +proc startdiff {ids} { + global treediffs diffids treepending diffmergeid + + set diffids $ids + catch {unset diffmergeid} + if {![info exists treediffs($ids)]} { + if {![info exists treepending]} { + gettreediffs $ids + } + } else { + addtocflist $ids + } +} + +proc addtocflist {ids} { + global treediffs cflist + foreach f $treediffs($ids) { + $cflist insert end $f + } + getblobdiffs $ids +} + +proc gettreediffs {ids} { + global treediff parents treepending env + set treepending $ids + set treediff {} + set id [lindex $ids 0] + set p [lindex $ids 1] + if [catch {set gdtf [open "|{$env(HG)} debug-diff-tree -r $p $id" r]}] return + fconfigure $gdtf -blocking 0 + fileevent $gdtf readable [list gettreediffline $gdtf $ids] +} + +proc gettreediffline {gdtf ids} { + global treediff treediffs treepending diffids diffmergeid + + set n [gets $gdtf line] + if {$n < 0} { + if {![eof $gdtf]} return + close $gdtf + set treediffs($ids) $treediff + unset treepending + if {$ids != $diffids} { + gettreediffs $diffids + } else { + if {[info exists diffmergeid]} { + contmergediff $ids + } else { + addtocflist $ids + } + } + return + } + set file [lindex $line 5] + lappend treediff $file +} + +proc getblobdiffs {ids} { + global diffopts blobdifffd diffids env curdifftag curtagstart + global difffilestart nextupdate diffinhdr treediffs + + set id [lindex $ids 0] + set p [lindex $ids 1] + set env(GIT_DIFF_OPTS) $diffopts + set cmd [list | $env(HG) debug-diff-tree -r -p -C $p $id] + if {[catch {set bdf [open $cmd r]} err]} { + puts "error getting diffs: $err" + return + } + set diffinhdr 0 + fconfigure $bdf -blocking 0 + set blobdifffd($ids) $bdf + set curdifftag Comments + set curtagstart 0.0 + catch {unset difffilestart} + fileevent $bdf readable [list getblobdiffline $bdf $diffids] + set nextupdate [expr {[clock clicks -milliseconds] + 100}] +} + +proc getblobdiffline {bdf ids} { + global diffids blobdifffd ctext curdifftag curtagstart + global diffnexthead diffnextnote difffilestart + global nextupdate diffinhdr treediffs + global gaudydiff + + set n [gets $bdf line] + if {$n < 0} { + if {[eof $bdf]} { + close $bdf + if {$ids == $diffids && $bdf == $blobdifffd($ids)} { + $ctext tag add $curdifftag $curtagstart end + } + } + return + } + if {$ids != $diffids || $bdf != $blobdifffd($ids)} { + return + } + regsub -all "\r" $line "" line + $ctext conf -state normal + if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} { + # start of a new file + $ctext insert end "\n" + $ctext tag add $curdifftag $curtagstart end + set curtagstart [$ctext index "end - 1c"] + set header $newname + set here [$ctext index "end - 1c"] + set i [lsearch -exact $treediffs($diffids) $fname] + if {$i >= 0} { + set difffilestart($i) $here + incr i + $ctext mark set fmark.$i $here + $ctext mark gravity fmark.$i left + } + if {$newname != $fname} { + set i [lsearch -exact $treediffs($diffids) $newname] + if {$i >= 0} { + set difffilestart($i) $here + incr i + $ctext mark set fmark.$i $here + $ctext mark gravity fmark.$i left + } + } + set curdifftag "f:$fname" + $ctext tag delete $curdifftag + set l [expr {(78 - [string length $header]) / 2}] + set pad [string range "----------------------------------------" 1 $l] + $ctext insert end "$pad $header $pad\n" filesep + set diffinhdr 1 + } elseif {[regexp {^(---|\+\+\+)} $line]} { + set diffinhdr 0 + } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \ + $line match f1l f1c f2l f2c rest]} { + if {$gaudydiff} { + $ctext insert end "\t" hunksep + $ctext insert end " $f1l " d0 " $f2l " d1 + $ctext insert end " $rest \n" hunksep + } else { + $ctext insert end "$line\n" hunksep + } + set diffinhdr 0 + } else { + set x [string range $line 0 0] + if {$x == "-" || $x == "+"} { + set tag [expr {$x == "+"}] + if {$gaudydiff} { + set line [string range $line 1 end] + } + $ctext insert end "$line\n" d$tag + } elseif {$x == " "} { + if {$gaudydiff} { + set line [string range $line 1 end] + } + $ctext insert end "$line\n" + } elseif {$diffinhdr || $x == "\\"} { + # e.g. "\ No newline at end of file" + $ctext insert end "$line\n" filesep + } elseif {$line != ""} { + # Something else we don't recognize + if {$curdifftag != "Comments"} { + $ctext insert end "\n" + $ctext tag add $curdifftag $curtagstart end + set curtagstart [$ctext index "end - 1c"] + set curdifftag Comments + } + $ctext insert end "$line\n" filesep + } + } + $ctext conf -state disabled + if {[clock clicks -milliseconds] >= $nextupdate} { + incr nextupdate 100 + fileevent $bdf readable {} + update + fileevent $bdf readable "getblobdiffline $bdf {$ids}" + } +} + +proc nextfile {} { + global difffilestart ctext + set here [$ctext index @0,0] + for {set i 0} {[info exists difffilestart($i)]} {incr i} { + if {[$ctext compare $difffilestart($i) > $here]} { + if {![info exists pos] + || [$ctext compare $difffilestart($i) < $pos]} { + set pos $difffilestart($i) + } + } + } + if {[info exists pos]} { + $ctext yview $pos + } +} + +proc listboxsel {} { + global ctext cflist currentid + if {![info exists currentid]} return + set sel [lsort [$cflist curselection]] + if {$sel eq {}} return + set first [lindex $sel 0] + catch {$ctext yview fmark.$first} +} + +proc setcoords {} { + global linespc charspc canvx0 canvy0 mainfont + global xspc1 xspc2 lthickness + + set linespc [font metrics $mainfont -linespace] + set charspc [font measure $mainfont "m"] + set canvy0 [expr 3 + 0.5 * $linespc] + set canvx0 [expr 3 + 0.5 * $linespc] + set lthickness [expr {int($linespc / 9) + 1}] + set xspc1(0) $linespc + set xspc2 $linespc +} + +proc redisplay {} { + global stopped redisplaying phase + if {$stopped > 1} return + if {$phase == "getcommits"} return + set redisplaying 1 + if {$phase == "drawgraph" || $phase == "incrdraw"} { + set stopped 1 + } else { + drawgraph + } +} + +proc incrfont {inc} { + global mainfont namefont textfont ctext canv phase + global stopped entries + unmarkmatches + set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]] + set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]] + set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]] + setcoords + $ctext conf -font $textfont + $ctext tag conf filesep -font [concat $textfont bold] + foreach e $entries { + $e conf -font $mainfont + } + if {$phase == "getcommits"} { + $canv itemconf textitems -font $mainfont + } + redisplay +} + +proc clearsha1 {} { + global sha1entry sha1string + if {[string length $sha1string] == 40} { + $sha1entry delete 0 end + } +} + +proc sha1change {n1 n2 op} { + global sha1string currentid sha1but + if {$sha1string == {} + || ([info exists currentid] && $sha1string == $currentid)} { + set state disabled + } else { + set state normal + } + if {[$sha1but cget -state] == $state} return + if {$state == "normal"} { + $sha1but conf -state normal -relief raised -text "Goto: " + } else { + $sha1but conf -state disabled -relief flat -text "SHA1 ID: " + } +} + +proc gotocommit {} { + global sha1string currentid idline tagids + global lineid numcommits + + if {$sha1string == {} + || ([info exists currentid] && $sha1string == $currentid)} return + if {[info exists tagids($sha1string)]} { + set id $tagids($sha1string) + } else { + set id [string tolower $sha1string] + if {[regexp {^[0-9a-f]{4,39}$} $id]} { + set matches {} + for {set l 0} {$l < $numcommits} {incr l} { + if {[string match $id* $lineid($l)]} { + lappend matches $lineid($l) + } + } + if {$matches ne {}} { + if {[llength $matches] > 1} { + error_popup "Short SHA1 id $id is ambiguous" + return + } + set id [lindex $matches 0] + } + } + } + if {[info exists idline($id)]} { + selectline $idline($id) 1 + return + } + if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} { + set type "SHA1 id" + } else { + set type "Tag" + } + error_popup "$type $sha1string is not known" +} + +proc lineenter {x y id} { + global hoverx hovery hoverid hovertimer + global commitinfo canv + + if {![info exists commitinfo($id)]} return + set hoverx $x + set hovery $y + set hoverid $id + if {[info exists hovertimer]} { + after cancel $hovertimer + } + set hovertimer [after 500 linehover] + $canv delete hover +} + +proc linemotion {x y id} { + global hoverx hovery hoverid hovertimer + + if {[info exists hoverid] && $id == $hoverid} { + set hoverx $x + set hovery $y + if {[info exists hovertimer]} { + after cancel $hovertimer + } + set hovertimer [after 500 linehover] + } +} + +proc lineleave {id} { + global hoverid hovertimer canv + + if {[info exists hoverid] && $id == $hoverid} { + $canv delete hover + if {[info exists hovertimer]} { + after cancel $hovertimer + unset hovertimer + } + unset hoverid + } +} + +proc linehover {} { + global hoverx hovery hoverid hovertimer + global canv linespc lthickness + global commitinfo mainfont + + set text [lindex $commitinfo($hoverid) 0] + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax == {}} return + set yfrac [lindex [$canv yview] 0] + set x [expr {$hoverx + 2 * $linespc}] + set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}] + set x0 [expr {$x - 2 * $lthickness}] + set y0 [expr {$y - 2 * $lthickness}] + set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}] + set y1 [expr {$y + $linespc + 2 * $lthickness}] + set t [$canv create rectangle $x0 $y0 $x1 $y1 \ + -fill \#ffff80 -outline black -width 1 -tags hover] + $canv raise $t + set t [$canv create text $x $y -anchor nw -text $text -tags hover] + $canv raise $t +} + +proc clickisonarrow {id y} { + global mainline mainlinearrow sidelines lthickness + + set thresh [expr {2 * $lthickness + 6}] + if {[info exists mainline($id)]} { + if {$mainlinearrow($id) ne "none"} { + if {abs([lindex $mainline($id) 1] - $y) < $thresh} { + return "up" + } + } + } + if {[info exists sidelines($id)]} { + foreach ls $sidelines($id) { + set coords [lindex $ls 0] + set arrow [lindex $ls 2] + if {$arrow eq "first" || $arrow eq "both"} { + if {abs([lindex $coords 1] - $y) < $thresh} { + return "up" + } + } + if {$arrow eq "last" || $arrow eq "both"} { + if {abs([lindex $coords end] - $y) < $thresh} { + return "down" + } + } + } + } + return {} +} + +proc arrowjump {id dirn y} { + global mainline sidelines canv + + set yt {} + if {$dirn eq "down"} { + if {[info exists mainline($id)]} { + set y1 [lindex $mainline($id) 1] + if {$y1 > $y} { + set yt $y1 + } + } + if {[info exists sidelines($id)]} { + foreach ls $sidelines($id) { + set y1 [lindex $ls 0 1] + if {$y1 > $y && ($yt eq {} || $y1 < $yt)} { + set yt $y1 + } + } + } + } else { + if {[info exists sidelines($id)]} { + foreach ls $sidelines($id) { + set y1 [lindex $ls 0 end] + if {$y1 < $y && ($yt eq {} || $y1 > $yt)} { + set yt $y1 + } + } + } + } + if {$yt eq {}} return + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax eq {} || $ymax <= 0} return + set view [$canv yview] + set yspan [expr {[lindex $view 1] - [lindex $view 0]}] + set yfrac [expr {$yt / $ymax - $yspan / 2}] + if {$yfrac < 0} { + set yfrac 0 + } + $canv yview moveto $yfrac +} + +proc lineclick {x y id isnew} { + global ctext commitinfo children cflist canv thickerline + + unmarkmatches + unselectline + normalline + $canv delete hover + # draw this line thicker than normal + drawlines $id 1 + set thickerline $id + if {$isnew} { + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax eq {}} return + set yfrac [lindex [$canv yview] 0] + set y [expr {$y + $yfrac * $ymax}] + } + set dirn [clickisonarrow $id $y] + if {$dirn ne {}} { + arrowjump $id $dirn $y + return + } + + if {$isnew} { + addtohistory [list lineclick $x $y $id 0] + } + # fill the details pane with info about this line + $ctext conf -state normal + $ctext delete 0.0 end + $ctext tag conf link -foreground blue -underline 1 + $ctext tag bind link <Enter> { %W configure -cursor hand2 } + $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } + $ctext insert end "Parent:\t" + $ctext insert end $id [list link link0] + $ctext tag bind link0 <1> [list selbyid $id] + set info $commitinfo($id) + $ctext insert end "\n\t[lindex $info 0]\n" + $ctext insert end "\tAuthor:\t[lindex $info 1]\n" + $ctext insert end "\tDate:\t[lindex $info 2]\n" + if {[info exists children($id)]} { + $ctext insert end "\nChildren:" + set i 0 + foreach child $children($id) { + incr i + set info $commitinfo($child) + $ctext insert end "\n\t" + $ctext insert end $child [list link link$i] + $ctext tag bind link$i <1> [list selbyid $child] + $ctext insert end "\n\t[lindex $info 0]" + $ctext insert end "\n\tAuthor:\t[lindex $info 1]" + $ctext insert end "\n\tDate:\t[lindex $info 2]\n" + } + } + $ctext conf -state disabled + + $cflist delete 0 end +} + +proc normalline {} { + global thickerline + if {[info exists thickerline]} { + drawlines $thickerline 0 + unset thickerline + } +} + +proc selbyid {id} { + global idline + if {[info exists idline($id)]} { + selectline $idline($id) 1 + } +} + +proc mstime {} { + global startmstime + if {![info exists startmstime]} { + set startmstime [clock clicks -milliseconds] + } + return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]] +} + +proc rowmenu {x y id} { + global rowctxmenu idline selectedline rowmenuid + + if {![info exists selectedline] || $idline($id) eq $selectedline} { + set state disabled + } else { + set state normal + } + $rowctxmenu entryconfigure 0 -state $state + $rowctxmenu entryconfigure 1 -state $state + $rowctxmenu entryconfigure 2 -state $state + set rowmenuid $id + tk_popup $rowctxmenu $x $y +} + +proc diffvssel {dirn} { + global rowmenuid selectedline lineid + + if {![info exists selectedline]} return + if {$dirn} { + set oldid $lineid($selectedline) + set newid $rowmenuid + } else { + set oldid $rowmenuid + set newid $lineid($selectedline) + } + addtohistory [list doseldiff $oldid $newid] + doseldiff $oldid $newid +} + +proc doseldiff {oldid newid} { + global ctext cflist + global commitinfo + + $ctext conf -state normal + $ctext delete 0.0 end + $ctext mark set fmark.0 0.0 + $ctext mark gravity fmark.0 left + $cflist delete 0 end + $cflist insert end "Top" + $ctext insert end "From " + $ctext tag conf link -foreground blue -underline 1 + $ctext tag bind link <Enter> { %W configure -cursor hand2 } + $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } + $ctext tag bind link0 <1> [list selbyid $oldid] + $ctext insert end $oldid [list link link0] + $ctext insert end "\n " + $ctext insert end [lindex $commitinfo($oldid) 0] + $ctext insert end "\n\nTo " + $ctext tag bind link1 <1> [list selbyid $newid] + $ctext insert end $newid [list link link1] + $ctext insert end "\n " + $ctext insert end [lindex $commitinfo($newid) 0] + $ctext insert end "\n" + $ctext conf -state disabled + $ctext tag delete Comments + $ctext tag remove found 1.0 end + startdiff [list $newid $oldid] +} + +proc mkpatch {} { + global rowmenuid currentid commitinfo patchtop patchnum + + if {![info exists currentid]} return + set oldid $currentid + set oldhead [lindex $commitinfo($oldid) 0] + set newid $rowmenuid + set newhead [lindex $commitinfo($newid) 0] + set top .patch + set patchtop $top + catch {destroy $top} + toplevel $top + label $top.title -text "Generate patch" + grid $top.title - -pady 10 + label $top.from -text "From:" + entry $top.fromsha1 -width 40 -relief flat + $top.fromsha1 insert 0 $oldid + $top.fromsha1 conf -state readonly + grid $top.from $top.fromsha1 -sticky w + entry $top.fromhead -width 60 -relief flat + $top.fromhead insert 0 $oldhead + $top.fromhead conf -state readonly + grid x $top.fromhead -sticky w + label $top.to -text "To:" + entry $top.tosha1 -width 40 -relief flat + $top.tosha1 insert 0 $newid + $top.tosha1 conf -state readonly + grid $top.to $top.tosha1 -sticky w + entry $top.tohead -width 60 -relief flat + $top.tohead insert 0 $newhead + $top.tohead conf -state readonly + grid x $top.tohead -sticky w + button $top.rev -text "Reverse" -command mkpatchrev -padx 5 + grid $top.rev x -pady 10 + label $top.flab -text "Output file:" + entry $top.fname -width 60 + $top.fname insert 0 [file normalize "patch$patchnum.patch"] + incr patchnum + grid $top.flab $top.fname -sticky w + frame $top.buts + button $top.buts.gen -text "Generate" -command mkpatchgo + button $top.buts.can -text "Cancel" -command mkpatchcan + grid $top.buts.gen $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + grid $top.buts - -pady 10 -sticky ew + focus $top.fname +} + +proc mkpatchrev {} { + global patchtop + + set oldid [$patchtop.fromsha1 get] + set oldhead [$patchtop.fromhead get] + set newid [$patchtop.tosha1 get] + set newhead [$patchtop.tohead get] + foreach e [list fromsha1 fromhead tosha1 tohead] \ + v [list $newid $newhead $oldid $oldhead] { + $patchtop.$e conf -state normal + $patchtop.$e delete 0 end + $patchtop.$e insert 0 $v + $patchtop.$e conf -state readonly + } +} + +proc mkpatchgo {} { + global patchtop env + + set oldid [$patchtop.fromsha1 get] + set newid [$patchtop.tosha1 get] + set fname [$patchtop.fname get] + if {[catch {exec $env(HG) debug-diff-tree -p $oldid $newid >$fname &} err]} { + error_popup "Error creating patch: $err" + } + catch {destroy $patchtop} + unset patchtop +} + +proc mkpatchcan {} { + global patchtop + + catch {destroy $patchtop} + unset patchtop +} + +proc mktag {} { + global rowmenuid mktagtop commitinfo + + set top .maketag + set mktagtop $top + catch {destroy $top} + toplevel $top + label $top.title -text "Create tag" + grid $top.title - -pady 10 + label $top.id -text "ID:" + entry $top.sha1 -width 40 -relief flat + $top.sha1 insert 0 $rowmenuid + $top.sha1 conf -state readonly + grid $top.id $top.sha1 -sticky w + entry $top.head -width 60 -relief flat + $top.head insert 0 [lindex $commitinfo($rowmenuid) 0] + $top.head conf -state readonly + grid x $top.head -sticky w + label $top.tlab -text "Tag name:" + entry $top.tag -width 60 + grid $top.tlab $top.tag -sticky w + frame $top.buts + button $top.buts.gen -text "Create" -command mktaggo + button $top.buts.can -text "Cancel" -command mktagcan + grid $top.buts.gen $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + grid $top.buts - -pady 10 -sticky ew + focus $top.tag +} + +proc domktag {} { + global mktagtop env tagids idtags + + set id [$mktagtop.sha1 get] + set tag [$mktagtop.tag get] + if {$tag == {}} { + error_popup "No tag name specified" + return + } + if {[info exists tagids($tag)]} { + error_popup "Tag \"$tag\" already exists" + return + } + if {[catch { + set out [exec $env(HG) tag -r $id $tag] + } err]} { + error_popup "Error creating tag: $err" + return + } + + set tagids($tag) $id + lappend idtags($id) $tag + redrawtags $id +} + +proc redrawtags {id} { + global canv linehtag idline idpos selectedline + + if {![info exists idline($id)]} return + $canv delete tag.$id + set xt [eval drawtags $id $idpos($id)] + $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2] + if {[info exists selectedline] && $selectedline == $idline($id)} { + selectline $selectedline 0 + } +} + +proc mktagcan {} { + global mktagtop + + catch {destroy $mktagtop} + unset mktagtop +} + +proc mktaggo {} { + domktag + mktagcan +} + +proc writecommit {} { + global rowmenuid wrcomtop commitinfo wrcomcmd + + set top .writecommit + set wrcomtop $top + catch {destroy $top} + toplevel $top + label $top.title -text "Write commit to file" + grid $top.title - -pady 10 + label $top.id -text "ID:" + entry $top.sha1 -width 40 -relief flat + $top.sha1 insert 0 $rowmenuid + $top.sha1 conf -state readonly + grid $top.id $top.sha1 -sticky w + entry $top.head -width 60 -relief flat + $top.head insert 0 [lindex $commitinfo($rowmenuid) 0] + $top.head conf -state readonly + grid x $top.head -sticky w + label $top.clab -text "Command:" + entry $top.cmd -width 60 -textvariable wrcomcmd + grid $top.clab $top.cmd -sticky w -pady 10 + label $top.flab -text "Output file:" + entry $top.fname -width 60 + $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"] + grid $top.flab $top.fname -sticky w + frame $top.buts + button $top.buts.gen -text "Write" -command wrcomgo + button $top.buts.can -text "Cancel" -command wrcomcan + grid $top.buts.gen $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + grid $top.buts - -pady 10 -sticky ew + focus $top.fname +} + +proc wrcomgo {} { + global wrcomtop + + set id [$wrcomtop.sha1 get] + set cmd "echo $id | [$wrcomtop.cmd get]" + set fname [$wrcomtop.fname get] + if {[catch {exec sh -c $cmd > $fname &} err]} { + error_popup "Error writing commit: $err" + } + catch {destroy $wrcomtop} + unset wrcomtop +} + +proc wrcomcan {} { + global wrcomtop + + catch {destroy $wrcomtop} + unset wrcomtop +} + +proc listrefs {id} { + global idtags idheads idotherrefs + + set x {} + if {[info exists idtags($id)]} { + set x $idtags($id) + } + set y {} + if {[info exists idheads($id)]} { + set y $idheads($id) + } + set z {} + if {[info exists idotherrefs($id)]} { + set z $idotherrefs($id) + } + return [list $x $y $z] +} + +proc rereadrefs {} { + global idtags idheads idotherrefs + global tagids headids otherrefids + + set refids [concat [array names idtags] \ + [array names idheads] [array names idotherrefs]] + foreach id $refids { + if {![info exists ref($id)]} { + set ref($id) [listrefs $id] + } + } + foreach v {tagids idtags headids idheads otherrefids idotherrefs} { + catch {unset $v} + } + readrefs + set refids [lsort -unique [concat $refids [array names idtags] \ + [array names idheads] [array names idotherrefs]]] + foreach id $refids { + set v [listrefs $id] + if {![info exists ref($id)] || $ref($id) != $v} { + redrawtags $id + } + } +} + +proc showtag {tag isnew} { + global ctext cflist tagcontents tagids linknum + + if {$isnew} { + addtohistory [list showtag $tag 0] + } + $ctext conf -state normal + $ctext delete 0.0 end + set linknum 0 + if {[info exists tagcontents($tag)]} { + set text $tagcontents($tag) + } else { + set text "Tag: $tag\nId: $tagids($tag)" + } + appendwithlinks $text + $ctext conf -state disabled + $cflist delete 0 end +} + +proc doquit {} { + global stopped + set stopped 100 + destroy . +} + +# defaults... +set datemode 0 +set boldnames 0 +set diffopts "-U 5 -p" +set wrcomcmd "\"\$HG\" debug-diff-tree --stdin -p --pretty" + +set mainfont {Helvetica 9} +set textfont {Courier 9} +set findmergefiles 0 +set gaudydiff 0 +set maxgraphpct 50 +set maxwidth 16 + +set colors {green red blue magenta darkgrey brown orange} + +catch {source ~/.gitk} + +set namefont $mainfont +if {$boldnames} { + lappend namefont bold +} + +set revtreeargs {} +foreach arg $argv { + switch -regexp -- $arg { + "^$" { } + "^-b" { set boldnames 1 } + "^-d" { set datemode 1 } + default { + lappend revtreeargs $arg + } + } +} + +set history {} +set historyindex 0 + +set stopped 0 +set redisplaying 0 +set stuffsaved 0 +set patchnum 0 +setcoords +makewindow +readrefs +getcommits $revtreeargs
new file mode 100755 --- /dev/null +++ b/unixSoft/bin/magic_editor.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# "magically" pick the 'best' available editor for a given platform + +# use emacs if it is running a server +# disabled because I ended up not liking using emacs as $EDITOR, weird, I know +# tempuid=`id -u` +# temphost=`hostname` +# if [ -e "/tmp/esrv$tempuid-$temphost" ] +# then +# emacsclient "$@" +# exit $? +# fi + +# use subethaedit on OS X +if test "`uname`" = "Darwin" ; then + if test "x`whereis see`" != "x" ; then + see -w "$@" + exit $? + # no subetha, then try for textwrangler + elif test "x`whereis edit`" != "x" ; then + edit -w "$@" + exit $? + fi +fi + +# we're not on a mac (or preferred mac editors failed, so we like gvim +if test "x`whereis gvim`" != "x" && test "x$DISPLAY" != "x" ; then + gvim -f "$@" +# ...or vim, since either gvim wasn't there or display wasn't set +elif test "x`whereis vim`" != "x" ; then + vim -f "$@" +# wow, this is a weird host, use vi. if that doesn't exist, we're really screwed +else + vi "$@" +fi + +exit $?
new file mode 100755 --- /dev/null +++ b/unixSoft/bin/svn-hgmerge.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# ==================================================================== +# Copyright (c) 2007 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +# This software consists of voluntary contributions made by many +# individuals. For exact contribution history, see the revision +# history and logs, available at http://subversion.tigris.org/. +# ==================================================================== +HGMERGE_BINARY='hgmerge' + +'''This script allows using Mercurial's hgmerge script with Subversion. +''' + +import os +import sys +import shutil +import tempfile + +def do_hgmerge(base, repo, local, merged): + '''Runs an interactive three-way merge using Mercurial's hgmerge script. + + This function is designed to convert Subversion's four-file interactive merges + into Mercurial's three-file interactive merges so that hgmerge can be used for + interactive merging in subversion. + ''' + # We have to use a temporary directory because FileMerge on OS X fails to save + # the file if it has to write to a file in the CWD. As of now, there's no + # obvious reason for why that is, but this fixes it. + temp_dir = tempfile.mkdtemp(prefix='svn_hgmerge') + local_name = local.split('/')[-1] + temp_file = temp_dir+'/'+local_name + shutil.copyfile(local, temp_file) + args = [HGMERGE_BINARY, temp_file, base, repo] + status = os.spawnvp(os.P_WAIT, HGMERGE_BINARY, args) + print status + if status == 0: + os.unlink(merged) + shutil.copyfile(temp_file, merged) + os.unlink(temp_file) + os.rmdir(temp_dir) + return status + +if __name__ == '__main__': + status = do_hgmerge(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]) + sys.exit(status) +
new file mode 100644 --- /dev/null +++ b/unixSoft/lib/python/pdb.py @@ -0,0 +1,413 @@ +""" +Pdb++, a fancier version of pdb +=============================== + +This module extends the stdlib pdb in numerous ways, e.g. by providing +real completion of Python values instead of pdb's own commands, or by +adding few convenience commands like ``longlist``, ``interact`` or +``watch``. + +For a full explanation of each command, refer to the docstring or type +help <command> at the prompt. + +Installation +------------ + +This module is meant to replace stdlib's pdb.py from the outsite; +simply put it in a directory in your PYTHONPATH, and you can start +using it immediately. Since it's named pdb.py, every place which +imports pdb will now find the new module. + +Dependencies +------------ + +To work properly, this module needs `rlcompleter_ng`_ to be installed. + +To enable syntax highlighting, you must install `pygments`. + +.. _pygments: http://pygments.org/ +.. _`rlcompleter_ng`: http://codespeak.net/svn/user/antocuni/hack/rlcompleter_ng.py + +Configuration +------------- + +To customize the configuration of Pdb++, you need to put a file named +.pdbrc.py in your home directory. The file must contain a class named +``Config`` inheriting from ``DefaultConfig`` and overridding the +desired values. + +To know which options are available, look at the comment in the +source. + +You can find a sample configuration file, here: +http://codespeak.net/svn/user/antocuni/hack/pdbrc.py +""" + +__version__='0.1' +__author__ ='Antonio Cuni <anto.cuni@gmail.com>' +__url__='http://codespeak.net/svn/user/antocuni/hack/pdb.py' + +import sys +import os.path +import inspect +import code +import types +from rlcompleter_ng import Completer, ConfigurableClass, setcolor, colors + + +def import_from_stdlib(name): + import code # arbitrary module which stays in the same dir as pdb + stdlibdir, _ = os.path.split(code.__file__) + pyfile = os.path.join(stdlibdir, name + '.py') + result = types.ModuleType(name) + mydict = execfile(pyfile, result.__dict__) + return result + +pdb = import_from_stdlib('pdb') + + +class DefaultConfig: + prompt = '(Pdb++) ' + completekey = 'tab' + highlight = True + bg = 'dark' + colorscheme = None + + line_number_color = colors.turquoise + current_line_color = 44 # blue + +def getsourcelines(obj): + try: + return inspect.getsourcelines(obj) + except IOError: + pass + + if isinstance(obj, types.FrameType): + filename = obj.f_code.co_filename + if hasattr(filename, '__source__'): + first = max(1, obj.f_lineno - 5) + lines = [line + '\n' for line in filename.__source__.lines] + return lines, first + raise IOError('could not get source code') + +def setbgcolor(line, color): + # hack hack hack + # add a bgcolor attribute to all escape sequences found + import re + setbg = '\x1b[%dm' % color + regexbg = '\\1;%dm' % color + return setbg + re.sub('(\x1b\\[.*?)m', regexbg, line) + '\x1b[00m' + +CLEARSCREEN = '\033[2J\033[1;1H' + +def lasti2lineno(code, lasti): + import dis + linestarts = list(dis.findlinestarts(code)) + linestarts.reverse() + for i, lineno in linestarts: + if lasti >= i: + return lineno + assert False, 'Invalid instruction number: %s' % lasti + +class Undefined: + def __repr__(self): + return '<undefined>' +undefined = Undefined() + +class Pdb(pdb.Pdb, ConfigurableClass): + + DefaultConfig = DefaultConfig + config_filename = '.pdbrc.py' + + def __init__(self, *args, **kwds): + Config = kwds.pop('Config', None) + pdb.Pdb.__init__(self, *args, **kwds) + self.config = self.get_config(Config) + self.prompt = self.config.prompt + self.completekey = self.config.completekey + + self.mycompleter = None + self.watching = {} # frame --> (name --> last seen value) + self.sticky = False + self.sticky_ranges = {} # frame --> (start, end) + self.tb_lineno = {} # frame --> lineno where the exception raised + + def interaction(self, frame, traceback, orig_traceback=None): + self.setup(frame, traceback, orig_traceback) + self.print_stack_entry(self.stack[self.curindex]) + self.cmdloop() + self.forget() + + def setup(self, frame, tb, orig_tb=None): + pdb.Pdb.setup(self, frame, tb) + tb = orig_tb + while tb: + lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti) + self.tb_lineno[tb.tb_frame] = lineno + tb = tb.tb_next + + def forget(self): + pdb.Pdb.forget(self) + self.raise_lineno = {} + + def complete(self, text, state): + if state == 0: + mydict = self.curframe.f_globals.copy() + mydict.update(self.curframe.f_locals) + self.mycompleter = Completer(mydict) + return self.mycompleter.complete(text, state) + + def _init_pygments(self): + try: + from pygments.lexers import PythonLexer + from pygments.formatters import TerminalFormatter + except ImportError: + return False + + if hasattr(self, '_fmt'): + return True + + self._fmt = TerminalFormatter(bg=self.config.bg, + colorscheme=self.config.colorscheme) + self._lexer = PythonLexer() + return True + + def format_source(self, src): + if not self._init_pygments(): + return src + from pygments import highlight, lex + return highlight(src, self._lexer, self._fmt) + + def format_line(self, lineno, marker, line): + lineno = '%4d' % lineno + if self.config.highlight: + lineno = setcolor(lineno, self.config.line_number_color) + line = '%s %2s %s' % (lineno, marker, line) + if self.config.highlight and marker == '->': + line = setbgcolor(line, self.config.current_line_color) + return line + + def parseline(self, line): + cmd, arg, newline = pdb.Pdb.parseline(self, line) + # don't execute short disruptive commands if a variable with + # the name exits in the current contex; this prevents pdb to + # quit if you type e.g. 'r[0]' by mystake. + if cmd in ['c', 'r', 'q'] and (cmd in self.curframe.f_globals or + cmd in self.curframe.f_locals): + line = '!' + line + return pdb.Pdb.parseline(self, line) + return cmd, arg, newline + + def do_longlist(self, arg): + """ + {longlist|ll} + List source code for the current function. + + Differently that list, the whole function is displayed; the + current line is marked with '->'. In case of post-mortem + debugging, the line which effectively raised the exception is + marked with '>>'. + + If the 'highlight' config option is set and pygments is + installed, the source code is colorized. + """ + self.lastcmd = 'longlist' + self._printlonglist() + + def _printlonglist(self, linerange=None): + try: + lines, lineno = getsourcelines(self.curframe) + except IOError, e: + print '** Error: %s **' % e + return + if linerange: + start, end = linerange + start = max(start, lineno) + end = min(end, lineno+len(lines)) + lines = lines[start-lineno:end-lineno] + lineno = start + self._print_lines(lines, lineno) + + def _print_lines(self, lines, lineno, print_markers=True): + exc_lineno = self.tb_lineno.get(self.curframe, None) + lines = [line[:-1] for line in lines] # remove the trailing '\n' + if self.config.highlight: + maxlength = max(map(len, lines)) + lines = [line.ljust(maxlength) for line in lines] + src = self.format_source('\n'.join(lines)) + lines = src.splitlines() + for i, line in enumerate(lines): + marker = '' + if lineno == self.curframe.f_lineno and print_markers: + marker = '->' + elif lineno == exc_lineno and print_markers: + marker = '>>' + lines[i] = self.format_line(lineno, marker, line) + lineno += 1 + print '\n'.join(lines) + + do_ll = do_longlist + + def do_list(self, arg): + from StringIO import StringIO + oldstdout = sys.stdout + sys.stdout = StringIO() + pdb.Pdb.do_list(self, arg) + src = self.format_source(sys.stdout.getvalue()) + sys.stdout = oldstdout + print src, + + do_l = do_list + + def do_interact(self, arg): + """ + interact + + Start an interative interpreter whose global namespace + contains all the names found in the current scope. + """ + ns = self.curframe.f_globals.copy() + ns.update(self.curframe.f_locals) + code.interact("*interactive*", local=ns) + + def do_track(self, arg): + """ + track expression + + Display a graph showing which objects are referred by the + value of the expression. This command requires pypy to be in + the current PYTHONPATH. + """ + try: + from pypy.translator.tool.reftracker import track + except ImportError: + print '** cannot import pypy.translator.tool.reftracker **' + return + val = self._getval(arg) + track(val) + + def _get_watching(self): + return self.watching.setdefault(self.curframe, {}) + + def _getval_or_undefined(self, arg): + try: + return eval(arg, self.curframe.f_globals, + self.curframe.f_locals) + except NameError: + return undefined + + def do_watch(self, arg): + """ + watch expression + + Add expression to the watching list; expressions in this list + are evaluated at each step, and printed every time its value + changes. + + WARNING: since the expressions is evaluated multiple time, pay + attention not to put expressions with side-effects in the + watching list. + """ + try: + value = self._getval_or_undefined(arg) + except: + return + self._get_watching()[arg] = value + + def do_unwatch(self, arg): + """ + unwatch expression + + Remove expression from the watching list. + """ + try: + del self._get_watching()[arg] + except KeyError: + print '** not watching %s **' % arg + + def _print_if_sticky(self): + if self.sticky: + sys.stdout.write(CLEARSCREEN) + frame, lineno = self.stack[self.curindex] + filename = self.canonic(frame.f_code.co_filename) + s = '> %s(%r)' % (filename, lineno) + print s + print + sticky_range = self.sticky_ranges.get(self.curframe, None) + self._printlonglist(sticky_range) + + def do_sticky(self, arg): + """ + sticky [start end] + + Toggle sticky mode. When in sticky mode, it clear the screen + and longlist the current functions, making the source + appearing always in the same position. Useful to follow the + flow control of a function when doing step-by-step execution. + + If ``start`` and ``end`` are given, sticky mode is enabled and + only lines within that range (extremes included) will be + displayed. + """ + if arg: + try: + start, end = map(int, arg.split()) + except ValueError: + print '** Error when parsing argument: %s **' % arg + return + self.sticky = True + self.sticky_ranges[self.curframe] = start, end+1 + else: + self.sticky = not self.sticky + self.sticky_range = None + self._print_if_sticky() + + def preloop(self): + self._print_if_sticky() + watching = self._get_watching() + for expr, oldvalue in watching.iteritems(): + newvalue = self._getval_or_undefined(expr) + # check for identity first; this prevents custom __eq__ to + # be called at every loop, and also prevents instances + # whose fields are changed to be displayed + if newvalue is not oldvalue or newvalue != oldvalue: + watching[expr] = newvalue + print '%s: %r --> %r' % (expr, oldvalue, newvalue) + + def do_source(self, arg): + try: + obj = self._getval(arg) + except: + return + try: + lines, lineno = getsourcelines(obj) + except (IOError, TypeError), e: + print '** Error: %s **' % e + return + self._print_lines(lines, lineno, print_markers=False) + +# Simplified interface + +# copy some functions from pdb.py, but rebind the global dictionary +for name in 'run runeval runctx runcall set_trace pm'.split(): + func = getattr(pdb, name) + newfunc = types.FunctionType(func.func_code, globals(), func.func_name) + globals()[name] = newfunc +del name, func, newfunc + +def post_mortem(t, Pdb=Pdb): + p = Pdb() + p.reset() + orig_tb = t + while t.tb_next is not None: + t = t.tb_next + p.interaction(t.tb_frame, t, orig_tb) + +# pdb++ specific interface + +def xpm(Pdb=Pdb): + """ + To be used inside an except clause, enter a post-mortem pdb + related to the just catched exception. + """ + post_mortem(sys.exc_info()[2], Pdb)
new file mode 100644 --- /dev/null +++ b/unixSoft/lib/python/rlcompleter_ng.py @@ -0,0 +1,250 @@ +""" +rlcompleter_ng +============== + +This module represents an alternative to rlcompleter and rlcompleter2, +for those who don't like their default behaviour. + +There are two main differences between stdlib's rlcompleter and +rlcompleter_ng: + + - when doing something like a.b.c.<TAB>, rlcompleter prepends a.b.c + to all the completions it finds; rlcompleter_ng displays only the + attributes, making the screen less cluttered; + + - you can use the <TAB> key both to indent (when the current line is + blank) or to complete (when it's not blank); + + - more important, rlcompleter_ng prints the various attributes found + in different colors depending on their type. + +Unfortunately, the default version of libreadline don't support +colored completions, so you need to patch it to fully exploid +rlcompleter_ng capabilities. + +You can find the patch here: +http://codespeak.net/svn/user/antocuni/hack/readline-escape.patch + +Alternatively, you can download the Ubuntu Hardy i386 package from here (thanks +to Alexander Schremmer): +http://antosreadlineforhardy.alexanderweb.de/libreadline5_5.2-3build1pypy_i386.deb + +Installation +------------ + +Simply put the file rlcompleter_ng.py in a directory which is in your +PYTHONPATH. + +Configuration +------------- + +Since it requires a patched version of libreadline, coloured +completions are disabled by default. + +To customize the configuration of rlcompleter_ng, you need to put a +file named .rlcompleter_ngrc.py in your home directory. The file must +contain a class named ``Config`` inheriting from ``DefaultConfig`` and +overridding the desired values. + +You can find a sample configuration file, which enables colors, here: +http://codespeak.net/svn/user/antocuni/hack/rlcompleter_ngrc.py + +Usage +----- + +From the interactive prompt, import rlcompleter_ng and call setup(): + +>>> import rlcompleter_ng +>>> rlcompleter_ng.setup() + +Alternatively, you can put these lines in some file that it's +referenced by the PYTHONSTARTUP environment variable, so that +completions is always enabled. +""" + +__version__='0.1' +__author__ ='Antonio Cuni <anto.cuni@gmail.com>' +__url__='http://codespeak.net/svn/user/antocuni/hack/rlcompleter_ng.py' + + +import readline +import rlcompleter +import types +import os.path +from itertools import izip, count + +class colors: + black = '30' + darkred = '31' + darkgreen = '32' + brown = '33' + darkblue = '34' + purple = '35' + teal = '36' + lightgray = '37' + darkgray = '30;01' + red = '31;01' + green = '32;01' + yellow = '33;01' + blue = '34;01' + fuchsia = '35;01' + turquoise = '36;01' + white = '37;01' + + +class DefaultConfig: + + # WARNING: for this option to work properly, you need to patch readline with this: + # http://codespeak.net/svn/user/antocuni/hack/readline-escape.patch + use_colors = False + + color_by_type = { + types.BuiltinMethodType: colors.turquoise, + types.BuiltinMethodType: colors.turquoise, + types.MethodType: colors.turquoise, + type((42).__add__): colors.turquoise, + type(int.__add__): colors.turquoise, + type(str.replace): colors.turquoise, + + types.FunctionType: colors.blue, + types.BuiltinFunctionType: colors.blue, + + types.ClassType: colors.fuchsia, + type: colors.fuchsia, + + types.ModuleType: colors.teal, + types.NoneType: colors.lightgray, + str: colors.green, + unicode: colors.green, + int: colors.yellow, + float: colors.yellow, + complex: colors.yellow, + bool: colors.yellow, + } + + +def setcolor(s, color): + return '\x1b[%sm%s\x1b[00m' % (color, s) + + +class ConfigurableClass: + DefaultConfig = None + config_filename = None + + def get_config(self, Config): + if Config is not None: + return Config() + # try to load config from the ~/filename file + filename = '~/' + self.config_filename + rcfile = os.path.expanduser(filename) + if os.path.exists(rcfile): + mydict = {} + try: + execfile(rcfile, mydict) + return mydict['Config']() + except Exception, e: + print '** error when importing %s: %s **' % (s, e) + return self.DefaultConfig() + + +class Completer(rlcompleter.Completer, ConfigurableClass): + """ + When doing someting like a.b.<TAB>, display only the attributes of + b instead of the full a.b.attr string. + + Optionally, display the various completions in different colors + depending on the type. + """ + + DefaultConfig = DefaultConfig + config_filename = '.rlcompleter_ngrc.py' + + def __init__(self, namespace = None, Config=None): + rlcompleter.Completer.__init__(self, namespace) + self.config = self.get_config(Config) + if self.config.use_colors: + readline.parse_and_bind('set dont-escape-ctrl-chars on') + + def complete(self, text, state): + """ + stolen from: + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496812 + """ + if text == "": + return ['\t',None][state] + else: + return rlcompleter.Completer.complete(self,text,state) + + def global_matches(self, text): + import keyword + names = rlcompleter.Completer.global_matches(self, text) + prefix = commonprefix(names) + if prefix and prefix != text: + return [prefix] + + names.sort() + values = [] + for name in names: + if name in keyword.kwlist: + values.append(None) + else: + values.append(eval(name, self.namespace)) + matches = [self.color_for_obj(i, name, obj) + for i, name, obj + in izip(count(), names, values)] + return matches + [' '] + + def attr_matches(self, text): + import re + m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) + if not m: + return + expr, attr = m.group(1, 3) + object = eval(expr, self.namespace) + names = [] + values = [] + n = len(attr) + for word in dir(object): + if word[:n] == attr and word != "__builtins__": + names.append(word) + values.append(getattr(object, word)) + + prefix = commonprefix(names) + if prefix and prefix != attr: + return ['%s.%s' % (expr, prefix)] # autocomplete prefix + + matches = [self.color_for_obj(i, name, value) + for i, name, value + in izip(count(), names, values)] + return matches + [' '] + + def color_for_obj(self, i, name, value): + if not self.config.use_colors: + return name + t = type(value) + color = self.config.color_by_type.get(t, '00') + # hack hack hack + # prepend a fake escape sequence, so that readline can sort the matches correctly + return '\x1b[%03d;00m' % i + setcolor(name, color) + + +# stolen from rlcompleter2 +def commonprefix(names, base = ''): + """ return the common prefix of all 'names' starting with 'base' + """ + def commonfunc(s1,s2): + while not s2.startswith(s1): + s1=s1[:-1] + return s1 + + if base: + names = filter(lambda x, base=base: x.startswith(base), names) + if not names: + return '' + return reduce(commonfunc,names) + + +def setup(): + completer = Completer() + readline.parse_and_bind('tab: complete') + readline.set_completer(completer.complete)